kevy_replicate/
replica_secure.rs1use std::io::{self, Read, Write};
5use std::net::TcpStream;
6
7use kevy_noise::{Frames, Initiator, Keypair, MAX_MESSAGE, Transport, frame};
8
9use crate::replica::{ReplicaClient, connect_stream, encode_replicate_from, parse_ack_line};
10use crate::replica_error::ReplicaError;
11
12const PROLOGUE: &[u8] = b"kevy-replicate\x001";
14const TAG: usize = 16;
15
16#[derive(Debug, Clone)]
27#[non_exhaustive]
28pub struct ReplicaSecurity {
29 pub local: Keypair,
37 pub primary_key: [u8; 32],
49}
50
51impl ReplicaSecurity {
52 pub fn new(local: Keypair, primary_key: [u8; 32]) -> Self {
64 Self { local, primary_key }
65 }
66}
67
68pub(crate) struct ClientNoise {
69 transport: Transport,
70 frames: Frames,
71}
72
73impl core::fmt::Debug for ClientNoise {
74 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
75 f.write_str("ClientNoise")
76 }
77}
78
79fn invalid(e: impl std::fmt::Display) -> io::Error {
80 io::Error::new(io::ErrorKind::InvalidData, e.to_string())
81}
82
83#[cfg(not(target_arch = "wasm32"))]
84fn fresh_secret() -> io::Result<[u8; 32]> {
85 let mut secret = [0u8; 32];
86 kevy_sys::fill_random(&mut secret)?;
87 Ok(secret)
88}
89
90#[cfg(target_arch = "wasm32")]
92fn fresh_secret() -> io::Result<[u8; 32]> {
93 Err(io::ErrorKind::Unsupported.into())
94}
95
96pub(crate) fn handshake(
97 sock: &mut TcpStream,
98 sec: &ReplicaSecurity,
99) -> Result<ClientNoise, ReplicaError> {
100 let ephemeral = Keypair::from_secret(fresh_secret()?);
101 let (m1, init) = Initiator::start(&sec.local, &sec.primary_key, ephemeral, PROLOGUE, b"")
102 .map_err(invalid)?;
103 sock.write_all(&frame(&m1).map_err(invalid)?)?;
104 let mut frames = Frames::default();
105 let mut chunk = [0u8; 256];
106 let reply = loop {
107 if let Some(m) = frames.next() {
108 break m;
109 }
110 let n = sock.read(&mut chunk)?;
111 if n == 0 {
112 return Err(ReplicaError::HandshakeRejected);
113 }
114 frames.push(&chunk[..n]);
115 };
116 let (_, transport) = init.finish(&reply).map_err(invalid)?;
119 Ok(ClientNoise { transport, frames })
120}
121
122impl ClientNoise {
123 pub(crate) fn write(&mut self, sock: &mut TcpStream, plain: &[u8]) -> io::Result<()> {
124 let mut out = Vec::with_capacity(plain.len() + 2 * TAG + 4);
125 for chunk in plain.chunks(MAX_MESSAGE - TAG) {
126 let sealed = self.transport.seal(chunk).map_err(invalid)?;
127 out.extend(frame(&sealed).map_err(invalid)?);
128 }
129 sock.write_all(&out)
130 }
131
132 pub(crate) fn read(
135 &mut self,
136 sock: &mut TcpStream,
137 chunk: &mut [u8],
138 out: &mut Vec<u8>,
139 ) -> io::Result<usize> {
140 let n = sock.read(chunk)?;
141 self.frames.push(&chunk[..n]);
142 while let Some(m) = self.frames.next() {
143 out.extend(self.transport.open(&m).map_err(invalid)?);
144 }
145 Ok(n)
146 }
147}
148
149impl ReplicaClient {
150 pub(crate) fn connect_noise<A: std::net::ToSocketAddrs>(
154 addr: A,
155 opts: &crate::replica::ConnectOptions,
156 security: &ReplicaSecurity,
157 ) -> Result<Self, ReplicaError> {
158 let connect_timeout = opts.timeout;
159 let replica_id = opts.replica_id.as_str();
160 let mut sock = connect_stream(addr, connect_timeout)?;
161 sock.set_read_timeout(Some(connect_timeout))?;
162 let mut noise = handshake(&mut sock, security)?;
163 noise.write(&mut sock, &encode_replicate_from(opts.from, replica_id))?;
164 let mut plain = Vec::new();
165 let mut chunk = [0u8; 256];
166 let line_end = loop {
167 if let Some(i) = plain.windows(2).position(|w| w == b"\r\n") {
168 break i + 2;
169 }
170 if plain.len() > 256 {
171 return Err(ReplicaError::AckMalformed);
172 }
173 if noise.read(&mut sock, &mut chunk, &mut plain)? == 0 {
174 return Err(ReplicaError::HandshakeRejected);
175 }
176 };
177 let primary_at_handshake = parse_ack_line(&plain[..line_end])?;
178 sock.set_read_timeout(None)?;
179 sock.set_nonblocking(false)?;
180 let mut buf = Vec::with_capacity(8 * 1024);
181 buf.extend_from_slice(&plain[line_end..]);
183 Ok(ReplicaClient {
184 sock,
185 buf,
186 cursor: 0,
187 primary_at_handshake,
188 expected_offset: opts.from.offset,
189 in_snapshot: false,
190 noise: Some(noise),
191 })
192 }
193}