summaryrefslogtreecommitdiffstats
path: root/src/client/transport.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/transport.rs')
-rw-r--r--src/client/transport.rs171
1 files changed, 109 insertions, 62 deletions
diff --git a/src/client/transport.rs b/src/client/transport.rs
index 97c9227..60e7ab6 100644
--- a/src/client/transport.rs
+++ b/src/client/transport.rs
@@ -7,20 +7,27 @@
7use std::fmt; 7use std::fmt;
8use std::io::{Read, Write}; 8use std::io::{Read, Write};
9use std::net::TcpStream; 9use std::net::TcpStream;
10use std::sync::mpsc;
11use std::sync::Arc; 10use std::sync::Arc;
11use std::sync::mpsc;
12use std::thread; 12use std::thread;
13use std::time::{Duration, Instant}; 13use std::time::{Duration, Instant};
14 14
15use parking_lot::Mutex; 15use parking_lot::Mutex;
16use rustls::client::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
17use rustls::DigitallySignedStruct;
18use rusqlite::Connection; 16use rusqlite::Connection;
17use rustls::DigitallySignedStruct;
18use rustls::client::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
19 19
20use crate::client::database; 20use crate::client::database;
21use crate::protocol::LineBuffer; 21use crate::protocol::{
22use crate::tofu::{decide, spki_fingerprint, TrustDecision}; 22 FrameBuffer, Packet, parse_packet, request_line, write_frame as write_protocol_frame,
23use crate::version::{parse_hello, HelloReply, VERSION}; 23};
24use crate::tofu::{TrustDecision, decide, spki_fingerprint};
25use crate::version::{HelloReply, VERSION, parse_hello};
26
27/// Reserved request ids for the transport handshake. Application requests
28/// start at 1, so the two ranges cannot collide on one connection.
29const HELLO_REQUEST_ID: u64 = u64::MAX;
30const AUTH_REQUEST_ID: u64 = u64::MAX - 1;
24 31
25/// How long to wait for TCP + TLS before giving up. 32/// How long to wait for TCP + TLS before giving up.
26const CONNECT_TIMEOUT: Duration = Duration::from_secs(10); 33const CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
@@ -43,6 +50,8 @@ pub struct Connected {
43 pub server_version: String, 50 pub server_version: String,
44 pub latest: String, 51 pub latest: String,
45 pub url: String, 52 pub url: String,
53 /// Server Unix time reported by `HELLO`, milliseconds.
54 pub server_time: u64,
46 /// Round trip of the HELLO exchange. 55 /// Round trip of the HELLO exchange.
47 pub elapsed: Duration, 56 pub elapsed: Duration,
48} 57}
@@ -51,10 +60,7 @@ pub struct Connected {
51pub enum TransportError { 60pub enum TransportError {
52 Timeout, 61 Timeout,
53 TrustDeclined, 62 TrustDeclined,
54 KeyChanged { 63 KeyChanged { expected: String, observed: String },
55 expected: String,
56 observed: String,
57 },
58 VersionMismatch(String), 64 VersionMismatch(String),
59 Closed, 65 Closed,
60 Unexpected(String), 66 Unexpected(String),
@@ -121,7 +127,7 @@ pub fn connect(
121 return Err(TransportError::KeyChanged { 127 return Err(TransportError::KeyChanged {
122 expected, 128 expected,
123 observed: actual, 129 observed: actual,
124 }) 130 });
125 } 131 }
126 } 132 }
127 } 133 }
@@ -131,12 +137,7 @@ pub fn connect(
131 .map_err(io_failure)?; 137 .map_err(io_failure)?;
132 138
133 let start = Instant::now(); 139 let start = Instant::now();
134 tls.write_line(&format!("HELLO {VERSION}")) 140 let response = exchange(&mut tls, HELLO_REQUEST_ID, &format!("HELLO {VERSION}"))?;
135 .map_err(io_failure)?;
136 let mut response = String::new();
137 if tls.read_line(&mut response).map_err(io_failure)? == 0 {
138 return Err(TransportError::Closed);
139 }
140 let elapsed = start.elapsed(); 141 let elapsed = start.elapsed();
141 142
142 let response = response.trim(); 143 let response = response.trim();
@@ -151,10 +152,11 @@ pub fn connect(
151 let mut user = None; 152 let mut user = None;
152 let mut auth_error = None; 153 let mut auth_error = None;
153 if let Some((username, password)) = database::get_credentials(conn, &observed) { 154 if let Some((username, password)) = database::get_credentials(conn, &observed) {
154 tls.write_line(&format!("AUTH {username} {password}")) 155 let auth = exchange(
155 .map_err(io_failure)?; 156 &mut tls,
156 let mut auth = String::new(); 157 AUTH_REQUEST_ID,
157 tls.read_line(&mut auth).map_err(io_failure)?; 158 &format!("AUTH {username} {password}"),
159 )?;
158 if auth.trim().starts_with("OK") { 160 if auth.trim().starts_with("OK") {
159 user = Some(username); 161 user = Some(username);
160 } else { 162 } else {
@@ -169,19 +171,31 @@ pub fn connect(
169 server_version: hello.version, 171 server_version: hello.version,
170 latest: hello.latest, 172 latest: hello.latest,
171 url: hello.url, 173 url: hello.url,
174 server_time: hello.time,
172 elapsed, 175 elapsed,
173 }) 176 })
174} 177}
175 178
179/// Send one request and wait for the reply with the same id, ignoring events
180/// that arrive in between. Used only for the handshake.
181fn exchange(stream: &mut TlsStream, id: u64, payload: &str) -> Result<String, TransportError> {
182 stream
183 .write_request(id, None, payload)
184 .map_err(io_failure)?;
185 loop {
186 match stream.read_packet().map_err(io_failure)? {
187 None => return Err(TransportError::Closed),
188 Some(Packet::Reply { id: reply_id, body }) if reply_id == id => return Ok(body),
189 Some(Packet::Reply { .. }) | Some(Packet::Event { .. }) => continue,
190 }
191 }
192}
193
176/// TCP + TLS on a thread so a blocked handshake can time out. 194/// TCP + TLS on a thread so a blocked handshake can time out.
177fn tls_connect(addr: &str) -> Result<(TlsStream, Option<String>), TransportError> { 195fn tls_connect(addr: &str) -> Result<(TlsStream, Option<String>), TransportError> {
178 let (sender, receiver) = mpsc::channel(); 196 let (sender, receiver) = mpsc::channel();
179 let addr = addr.to_string(); 197 let addr = addr.to_string();
180 let server_name = addr 198 let server_name = addr.split(':').next().unwrap_or("127.0.0.1").to_string();
181 .split(':')
182 .next()
183 .unwrap_or("127.0.0.1")
184 .to_string();
185 199
186 thread::spawn(move || { 200 thread::spawn(move || {
187 let outcome = (|| -> Result<(TlsStream, Option<String>), TransportError> { 201 let outcome = (|| -> Result<(TlsStream, Option<String>), TransportError> {
@@ -241,7 +255,7 @@ impl TlsConnector {
241 session: Arc::new(Mutex::new(Session { 255 session: Arc::new(Mutex::new(Session {
242 sock, 256 sock,
243 tls: connection, 257 tls: connection,
244 lines: LineBuffer::new(), 258 frames: FrameBuffer::new(),
245 read_timeout: Some(READ_TIMEOUT), 259 read_timeout: Some(READ_TIMEOUT),
246 })), 260 })),
247 }) 261 })
@@ -268,8 +282,9 @@ impl ServerCertVerifier for RecordingVerifier {
268 _ocsp_response: &[u8], 282 _ocsp_response: &[u8],
269 _now: std::time::SystemTime, 283 _now: std::time::SystemTime,
270 ) -> Result<ServerCertVerified, rustls::Error> { 284 ) -> Result<ServerCertVerified, rustls::Error> {
271 let fingerprint = spki_fingerprint(&end_entity.0) 285 let fingerprint = spki_fingerprint(&end_entity.0).map_err(|error| {
272 .map_err(|error| rustls::Error::General(format!("certificate parse failed: {error}")))?; 286 rustls::Error::General(format!("certificate parse failed: {error}"))
287 })?;
273 *self.observed.lock() = Some(fingerprint); 288 *self.observed.lock() = Some(fingerprint);
274 Ok(ServerCertVerified::assertion()) 289 Ok(ServerCertVerified::assertion())
275 } 290 }
@@ -313,7 +328,7 @@ impl ServerCertVerifier for RecordingVerifier {
313struct Session { 328struct Session {
314 sock: TcpStream, 329 sock: TcpStream,
315 tls: rustls::ClientConnection, 330 tls: rustls::ClientConnection,
316 lines: LineBuffer, 331 frames: FrameBuffer,
317 read_timeout: Option<Duration>, 332 read_timeout: Option<Duration>,
318} 333}
319 334
@@ -329,37 +344,58 @@ pub struct TlsStream {
329} 344}
330 345
331impl TlsStream { 346impl TlsStream {
332 pub fn read_line(&mut self, buf: &mut String) -> std::io::Result<usize> { 347 /// Write one enveloped request. Application code allocates `id`.
348 pub fn write_request(
349 &mut self,
350 id: u64,
351 idem: Option<&str>,
352 payload: &str,
353 ) -> std::io::Result<()> {
354 self.write_frame(&request_line(id, idem, payload))
355 }
356
357 /// Write one raw frame payload.
358 pub fn write_frame(&mut self, payload: &str) -> std::io::Result<()> {
359 let mut s = self.session.lock();
360 write_protocol_frame(&mut s.tls.writer(), payload)?;
361 s.drive()
362 }
363
364 /// Block until one whole frame arrives.
365 pub fn read_frame(&mut self) -> std::io::Result<Option<String>> {
333 let mut s = self.session.lock(); 366 let mut s = self.session.lock();
334 loop { 367 loop {
335 if let Some(line) = s.lines.next_line() { 368 match s.frames.next_frame() {
336 buf.push_str(&line); 369 Some(Ok(frame)) => return Ok(Some(frame)),
337 buf.push('\n'); 370 Some(Err(error)) => {
338 return Ok(line.len() + 1); 371 return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, error));
372 }
373 None => {}
339 } 374 }
340 375
341 let mut chunk = [0u8; 4096]; 376 let mut chunk = [0u8; 4096];
342 match s.tls.reader().read(&mut chunk) { 377 match s.tls.reader().read(&mut chunk) {
343 Ok(0) => { 378 Ok(0) => return Ok(None),
344 if let Some(rest) = s.lines.take_rest() { 379 Ok(n) => s.frames.push(&chunk[..n]),
345 let read = rest.len();
346 buf.push_str(&rest);
347 return Ok(read);
348 }
349 return Ok(0);
350 }
351 Ok(n) => s.lines.push(&chunk[..n]),
352 Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => s.drive()?, 380 Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => s.drive()?,
353 Err(error) => return Err(error), 381 Err(error) => return Err(error),
354 } 382 }
355 } 383 }
356 } 384 }
357 385
358 pub fn write_line(&mut self, message: &str) -> std::io::Result<()> { 386 /// Block until one `RES` or `EVT` arrives.
359 let mut s = self.session.lock(); 387 pub fn read_packet(&mut self) -> std::io::Result<Option<Packet>> {
360 s.tls.writer().write_all(message.as_bytes())?; 388 loop {
361 s.tls.writer().write_all(b"\n")?; 389 let Some(frame) = self.read_frame()? else {
362 s.drive() 390 return Ok(None);
391 };
392 match parse_packet(&frame) {
393 Ok(packet) => return Ok(Some(packet)),
394 Err(error) => {
395 return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, error));
396 }
397 }
398 }
363 } 399 }
364 400
365 pub fn set_read_timeout(&self, duration: Option<Duration>) -> std::io::Result<()> { 401 pub fn set_read_timeout(&self, duration: Option<Duration>) -> std::io::Result<()> {
@@ -368,24 +404,36 @@ impl TlsStream {
368 s.sock.set_read_timeout(duration) 404 s.sock.set_read_timeout(duration)
369 } 405 }
370 406
371 /// Return one complete line if the socket already has one, without 407 /// Return one packet if the socket already has one, without blocking.
372 /// blocking. Used to pick up server pushes between requests. 408 /// Used to pick up server pushes between requests.
373 pub fn read_available_line(&mut self) -> std::io::Result<Option<String>> { 409 pub fn read_available_packet(&mut self) -> std::io::Result<Option<Packet>> {
374 let mut s = self.session.lock(); 410 let mut s = self.session.lock();
375 if let Some(line) = s.lines.next_line() {
376 return Ok(Some(line));
377 }
378 411
379 // `set_read_timeout(Some(0))` is rejected by std, so poll with the 412 // `set_read_timeout(Some(0))` is rejected by std, so poll with the
380 // shortest timeout the socket accepts instead of a nonblocking mode. 413 // shortest timeout the socket accepts instead of a nonblocking mode.
381 s.sock 414 s.sock
382 .set_read_timeout(Some(std::time::Duration::from_millis(1)))?; 415 .set_read_timeout(Some(std::time::Duration::from_millis(1)))?;
383 let drained = (|| -> std::io::Result<()> { 416 let drained = (|| -> std::io::Result<Option<Packet>> {
384 loop { 417 loop {
418 match s.frames.next_frame() {
419 Some(Ok(frame)) => {
420 return match parse_packet(&frame) {
421 Ok(packet) => Ok(Some(packet)),
422 Err(error) => {
423 Err(std::io::Error::new(std::io::ErrorKind::InvalidData, error))
424 }
425 };
426 }
427 Some(Err(error)) => {
428 return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, error));
429 }
430 None => {}
431 }
432
385 let mut buf = [0u8; 4096]; 433 let mut buf = [0u8; 4096];
386 match s.tls.reader().read(&mut buf) { 434 match s.tls.reader().read(&mut buf) {
387 Ok(0) => return Ok(()), 435 Ok(0) => return Ok(None),
388 Ok(n) => s.lines.push(&buf[..n]), 436 Ok(n) => s.frames.push(&buf[..n]),
389 // The reader only drains plaintext rustls has already 437 // The reader only drains plaintext rustls has already
390 // decrypted; `drive` moves bytes off the socket. 438 // decrypted; `drive` moves bytes off the socket.
391 Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => { 439 Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => {
@@ -397,7 +445,7 @@ impl TlsStream {
397 std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut 445 std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut
398 ) => 446 ) =>
399 { 447 {
400 return Ok(()); 448 return Ok(None);
401 } 449 }
402 Err(error) => return Err(error), 450 Err(error) => return Err(error),
403 } 451 }
@@ -408,15 +456,14 @@ impl TlsStream {
408 std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut 456 std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut
409 ) => 457 ) =>
410 { 458 {
411 return Ok(()); 459 return Ok(None);
412 } 460 }
413 Err(error) => return Err(error), 461 Err(error) => return Err(error),
414 } 462 }
415 } 463 }
416 })(); 464 })();
417 s.sock.set_read_timeout(s.read_timeout)?; 465 s.sock.set_read_timeout(s.read_timeout)?;
418 drained?; 466 drained
419 Ok(s.lines.next_line())
420 } 467 }
421} 468}
422 469