Negotiate the native protocol version with a newly-connected client. Reads a `HelloFrame`, validates it, picks a `proto_ver` in the overlap of `[client_proto_min, client_proto_max]` and `[PROTO_VERSION_MIN, PROTO_VERSION_MAX]`, then sends a `HelloAckFrame` with `limits` embedded. On any error, a `HelloErrorFrame` is written, the write side is shut down, and `Err` is returned so the caller can cl
(stream: &mut S, limits: &Limits)
| 34 | /// On any error, a `HelloErrorFrame` is written, the write side is shut down, |
| 35 | /// and `Err` is returned so the caller can close the connection. |
| 36 | pub async fn perform_server_handshake<S>(stream: &mut S, limits: &Limits) -> crate::Result<u16> |
| 37 | where |
| 38 | S: AsyncRead + AsyncWrite + Unpin, |
| 39 | { |
| 40 | // ── 1. Read HelloFrame with timeout ───────────────────────────────── |
| 41 | let mut buf = [0u8; HelloFrame::WIRE_SIZE]; |
| 42 | let read_result = tokio::time::timeout(HELLO_READ_TIMEOUT, stream.read_exact(&mut buf)).await; |
| 43 | |
| 44 | let hello = match read_result { |
| 45 | Err(_timeout) => { |
| 46 | send_error(stream, HelloErrorCode::Malformed, "hello read timeout").await; |
| 47 | return Err(crate::Error::BadRequest { |
| 48 | detail: "hello frame read timed out".into(), |
| 49 | }); |
| 50 | } |
| 51 | Ok(Err(io)) => { |
| 52 | // Don't bother sending an error frame on a connection-reset/EOF — |
| 53 | // the peer is already gone. |
| 54 | return Err(crate::Error::Io(io)); |
| 55 | } |
| 56 | Ok(Ok(_)) => match HelloFrame::decode(&buf) { |
| 57 | Some(f) => f, |
| 58 | None => { |
| 59 | let msg = "bad hello frame: BadMagic"; |
| 60 | send_error(stream, HelloErrorCode::BadMagic, msg).await; |
| 61 | return Err(crate::Error::BadRequest { detail: msg.into() }); |
| 62 | } |
| 63 | }, |
| 64 | }; |
| 65 | |
| 66 | debug!( |
| 67 | proto_min = hello.proto_min, |
| 68 | proto_max = hello.proto_max, |
| 69 | capabilities = hello.capabilities, |
| 70 | "hello received" |
| 71 | ); |
| 72 | |
| 73 | // ── 2. Negotiate version ───────────────────────────────────────────── |
| 74 | let proto_ver = match negotiate_version(hello.proto_min, hello.proto_max) { |
| 75 | Some(v) => v, |
| 76 | None => { |
| 77 | let msg = format!( |
| 78 | "client speaks [{}, {}], server speaks [{}, {}]", |
| 79 | hello.proto_min, hello.proto_max, PROTO_VERSION_MIN, PROTO_VERSION_MAX |
| 80 | ); |
| 81 | send_error(stream, HelloErrorCode::VersionMismatch, &msg).await; |
| 82 | return Err(crate::Error::VersionCompat { detail: msg }); |
| 83 | } |
| 84 | }; |
| 85 | |
| 86 | // ── 3. Send HelloAck ───────────────────────────────────────────────── |
| 87 | let ack = HelloAckFrame { |
| 88 | proto_version: proto_ver, |
| 89 | capabilities: hello.capabilities, // echo back what client offered (server supports all for v1) |
| 90 | server_version: format!("NodeDB/{}", crate::version::VERSION), |
| 91 | limits: limits.clone(), |
| 92 | }; |
| 93 | let ack_bytes = ack.encode(); |
no test coverage detected