Read exactly one TLS record from the TCP socket. OpenSSL reads one TLS record at a time (no read-ahead by default). Rustls, however, consumes all available TCP data when fed via read_tls(). If a close_notify or other control record arrives alongside application data, the eager read drains the TCP buffer, leaving the control record in rustls's internal buffer where select() cannot see it. This ca
(socket: &PySSLSocket, vm: &VirtualMachine)
| 1178 | /// and recv() only that many bytes. Any remaining data stays in the kernel |
| 1179 | /// buffer and remains visible to select(). |
| 1180 | fn recv_one_tls_record(socket: &PySSLSocket, vm: &VirtualMachine) -> SslResult<PyObjectRef> { |
| 1181 | // Peek at what is available without consuming it. |
| 1182 | let peeked_obj = match socket.sock_peek(SSL3_RT_MAX_PLAIN_LENGTH, vm) { |
| 1183 | Ok(d) => d, |
| 1184 | Err(e) => { |
| 1185 | if is_blocking_io_error(&e, vm) { |
| 1186 | return Err(SslError::WantRead); |
| 1187 | } |
| 1188 | return Err(SslError::Py(e)); |
| 1189 | } |
| 1190 | }; |
| 1191 | |
| 1192 | let peeked = ArgBytesLike::try_from_object(vm, peeked_obj) |
| 1193 | .map_err(|_| SslError::Syscall("Expected bytes-like object from peek".to_string()))?; |
| 1194 | let peeked_bytes = peeked.borrow_buf(); |
| 1195 | |
| 1196 | if peeked_bytes.is_empty() { |
| 1197 | // Empty peek means the peer has closed the TCP connection (FIN). |
| 1198 | // Non-blocking sockets would have returned EAGAIN/EWOULDBLOCK |
| 1199 | // (caught above as WantRead), so empty bytes here always means EOF. |
| 1200 | return Err(SslError::Eof); |
| 1201 | } |
| 1202 | |
| 1203 | if peeked_bytes.len() < TLS_RECORD_HEADER_SIZE { |
| 1204 | // Not enough data for a TLS record header yet. |
| 1205 | // Read all available bytes so rustls can buffer the partial header; |
| 1206 | // this avoids busy-waiting because the kernel buffer is now empty |
| 1207 | // and select() will only wake us when new data arrives. |
| 1208 | return socket.sock_recv(peeked_bytes.len(), vm).map_err(|e| { |
| 1209 | if is_blocking_io_error(&e, vm) { |
| 1210 | SslError::WantRead |
| 1211 | } else { |
| 1212 | SslError::Py(e) |
| 1213 | } |
| 1214 | }); |
| 1215 | } |
| 1216 | |
| 1217 | // Parse the TLS record length from the header. |
| 1218 | let record_body_len = u16::from_be_bytes([peeked_bytes[3], peeked_bytes[4]]) as usize; |
| 1219 | let total_record_size = TLS_RECORD_HEADER_SIZE + record_body_len; |
| 1220 | |
| 1221 | let recv_size = if peeked_bytes.len() >= total_record_size { |
| 1222 | // Complete record available — consume exactly one record. |
| 1223 | total_record_size |
| 1224 | } else { |
| 1225 | // Incomplete record — consume everything so the kernel buffer is |
| 1226 | // drained and select() will block until more data arrives. |
| 1227 | peeked_bytes.len() |
| 1228 | }; |
| 1229 | |
| 1230 | // Must drop the borrow before calling sock_recv (which re-enters Python). |
| 1231 | drop(peeked_bytes); |
| 1232 | drop(peeked); |
| 1233 | |
| 1234 | socket.sock_recv(recv_size, vm).map_err(|e| { |
| 1235 | if is_blocking_io_error(&e, vm) { |
| 1236 | SslError::WantRead |
| 1237 | } else { |
no test coverage detected