Socket-mode close_notify reader that respects TLS record boundaries. Uses MSG_PEEK to inspect data before consuming, preventing accidental consumption of post-TLS cleartext data during STARTTLS transitions. Equivalent to OpenSSL's `SSL_set_read_ahead(ssl, 0)` — rustls has no such knob, so we enforce record-level reads manually via peek.
(
&self,
conn: &mut TlsConnection,
vm: &VirtualMachine,
)
| 4350 | /// Equivalent to OpenSSL's `SSL_set_read_ahead(ssl, 0)` — rustls has no |
| 4351 | /// such knob, so we enforce record-level reads manually via peek. |
| 4352 | fn try_read_close_notify_socket( |
| 4353 | &self, |
| 4354 | conn: &mut TlsConnection, |
| 4355 | vm: &VirtualMachine, |
| 4356 | ) -> PyResult<bool> { |
| 4357 | // Peek at the first 5 bytes (TLS record header size) |
| 4358 | let peeked_obj = match self.sock_peek(5, vm) { |
| 4359 | Ok(obj) => obj, |
| 4360 | Err(e) => { |
| 4361 | if is_blocking_io_error(&e, vm) { |
| 4362 | return Ok(false); |
| 4363 | } |
| 4364 | return Ok(true); |
| 4365 | } |
| 4366 | }; |
| 4367 | |
| 4368 | let peeked = ArgBytesLike::try_from_object(vm, peeked_obj)?; |
| 4369 | let peek_data = peeked.borrow_buf(); |
| 4370 | |
| 4371 | if peek_data.is_empty() { |
| 4372 | return Ok(true); // EOF |
| 4373 | } |
| 4374 | |
| 4375 | // TLS record content types: ChangeCipherSpec(20), Alert(21), |
| 4376 | // Handshake(22), ApplicationData(23) |
| 4377 | let content_type = peek_data[0]; |
| 4378 | if !(20..=23).contains(&content_type) { |
| 4379 | // Not a TLS record - post-TLS cleartext data. |
| 4380 | // Peer has completed TLS shutdown; don't consume this data. |
| 4381 | return Ok(true); |
| 4382 | } |
| 4383 | |
| 4384 | // Determine how many bytes to read for exactly one TLS record |
| 4385 | let recv_size = if peek_data.len() >= 5 { |
| 4386 | let record_length = u16::from_be_bytes([peek_data[3], peek_data[4]]) as usize; |
| 4387 | 5 + record_length |
| 4388 | } else { |
| 4389 | // Partial header available - read just these bytes for now |
| 4390 | peek_data.len() |
| 4391 | }; |
| 4392 | |
| 4393 | drop(peek_data); |
| 4394 | drop(peeked); |
| 4395 | |
| 4396 | // Now consume exactly one TLS record from the socket |
| 4397 | match self.sock_recv(recv_size, vm) { |
| 4398 | Ok(bytes_obj) => { |
| 4399 | let bytes = ArgBytesLike::try_from_object(vm, bytes_obj)?; |
| 4400 | let data = bytes.borrow_buf(); |
| 4401 | |
| 4402 | if data.is_empty() { |
| 4403 | return Ok(true); |
| 4404 | } |
| 4405 | |
| 4406 | let data_slice: &[u8] = data.as_ref(); |
| 4407 | let mut cursor = std::io::Cursor::new(data_slice); |
| 4408 | let _ = conn.read_tls(&mut cursor); |
| 4409 | let _ = conn.process_new_packets(); |
no test coverage detected