@@ -3,6 +3,8 @@ use std::sync::Arc;
33use std:: sync:: atomic:: AtomicBool ;
44use std:: time:: { Duration , Instant } ;
55
6+ use arrayvec:: ArrayVec ;
7+
68use super :: queue:: { QueueRx , QueueTx } ;
79use crate :: buffer:: { Buf , BufferPool , TmpBuf } ;
810use crate :: crypto:: { Aad , Iv , Nonce } ;
@@ -88,6 +90,12 @@ pub struct Engine {
8890
8991 /// Whether we are ready to release application data from poll_output.
9092 release_app_data : bool ,
93+
94+ /// Whether a close_notify alert has been received from the peer.
95+ close_notify_received : bool ,
96+
97+ /// Whether we have already emitted a CloseNotify output event.
98+ close_notify_reported : bool ,
9199}
92100
93101#[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
@@ -140,6 +148,8 @@ impl Engine {
140148 flight_timeout : Timeout :: Unarmed ,
141149 connect_timeout : Timeout :: Unarmed ,
142150 release_app_data : false ,
151+ close_notify_received : false ,
152+ close_notify_reported : false ,
143153 }
144154 }
145155
@@ -206,11 +216,12 @@ impl Engine {
206216
207217 /// Insert the Incoming using the logic:
208218 ///
209- /// 1. If it is a handshake, sort by the message_seq
210- /// 2. If it is not a handshake, sort by sequence_number
219+ /// 1. Handle alert records per-record (not just the first record).
220+ /// 2. If it is a handshake, sort by the message_seq
221+ /// 3. If it is not a handshake, sort by sequence_number
211222 ///
212223 fn insert_incoming ( & mut self , incoming : Incoming ) -> Result < ( ) , Error > {
213- // Capacity guard
224+ // Capacity guard before iterating records.
214225 if self . queue_rx . len ( ) >= self . config . max_queue_rx ( ) {
215226 warn ! (
216227 "Receive queue full (max {}): {:?}" ,
@@ -220,6 +231,80 @@ impl Engine {
220231 return Err ( Error :: ReceiveQueueFull ) ;
221232 }
222233
234+ // Handle Alert records individually; collect the rest for queuing.
235+ // A single UDP datagram can contain mixed record types, so we process
236+ // each record individually without discarding siblings.
237+ let mut remaining = ArrayVec :: new ( ) ;
238+ for record in incoming. into_records ( ) {
239+ if record. record ( ) . content_type == ContentType :: Alert {
240+ let epoch = record. record ( ) . sequence . epoch ;
241+ if epoch == 0 {
242+ if self . peer_encryption_enabled {
243+ // Post-handshake: epoch 0 alerts are unauthenticated, discard
244+ self . buffers_free . push ( record. into_buffer ( ) ) ;
245+ continue ;
246+ }
247+ // During handshake: accept fatal alerts (level==2)
248+ let fragment = record. record ( ) . fragment ( record. buffer ( ) ) ;
249+ if fragment. len ( ) >= 2 && fragment[ 0 ] == 2 {
250+ let description = fragment[ 1 ] ;
251+ self . buffers_free . push ( record. into_buffer ( ) ) ;
252+ return Err ( Error :: SecurityError ( format ! (
253+ "Received fatal alert: level=2, description={}" ,
254+ description
255+ ) ) ) ;
256+ }
257+ // Non-fatal epoch 0 alert during handshake: discard
258+ self . buffers_free . push ( record. into_buffer ( ) ) ;
259+ continue ;
260+ }
261+ if !self . peer_encryption_enabled {
262+ // Epoch ≥ 1 but peer encryption not yet enabled: keep for
263+ // re-parsing after enable_peer_encryption (ciphertext record).
264+ remaining. try_push ( record) . ok ( ) ;
265+ continue ;
266+ }
267+ // Authenticated alert (epoch ≥ 1, peer encryption enabled)
268+ let fragment = record. record ( ) . fragment ( record. buffer ( ) ) ;
269+ if fragment. len ( ) >= 2 {
270+ let level = fragment[ 0 ] ;
271+ let description = fragment[ 1 ] ;
272+ if description == 0 {
273+ // close_notify: signal graceful shutdown
274+ self . close_notify_received = true ;
275+ self . buffers_free . push ( record. into_buffer ( ) ) ;
276+ continue ;
277+ } else if level == 2 {
278+ // Fatal alert (non close_notify)
279+ self . buffers_free . push ( record. into_buffer ( ) ) ;
280+ return Err ( Error :: SecurityError ( format ! (
281+ "Received fatal alert: level={}, description={}" ,
282+ level, description
283+ ) ) ) ;
284+ }
285+ }
286+ // Warning alerts with non-zero description: discard
287+ self . buffers_free . push ( record. into_buffer ( ) ) ;
288+ continue ;
289+ }
290+
291+ // After close_notify (from this or a prior datagram), discard
292+ // any further ApplicationData — the read half is closed.
293+ if self . close_notify_received
294+ && record. record ( ) . content_type == ContentType :: ApplicationData
295+ {
296+ self . buffers_free . push ( record. into_buffer ( ) ) ;
297+ continue ;
298+ }
299+
300+ remaining. try_push ( record) . ok ( ) ;
301+ }
302+
303+ let incoming = match Incoming :: from_records ( remaining) {
304+ Some ( incoming) => incoming,
305+ None => return Ok ( ( ) ) ,
306+ } ;
307+
223308 // Dispatch to specialized handlers
224309 if incoming. first ( ) . first_handshake ( ) . is_some ( ) {
225310 self . insert_incoming_handshake ( incoming)
@@ -370,6 +455,11 @@ impl Engine {
370455 return Output :: Packet ( p) ;
371456 }
372457
458+ if self . release_app_data && self . close_notify_received && !self . close_notify_reported {
459+ self . close_notify_reported = true ;
460+ return Output :: CloseNotify ;
461+ }
462+
373463 let next_timeout = self . poll_timeout ( now) ;
374464
375465 Output :: Timeout ( next_timeout)
@@ -899,6 +989,27 @@ impl Engine {
899989 self . release_app_data = true ;
900990 }
901991
992+ /// Whether a close_notify alert has been received from the peer.
993+ pub fn close_notify_received ( & self ) -> bool {
994+ self . close_notify_received
995+ }
996+
997+ /// Discard all pending outgoing data.
998+ ///
999+ /// RFC 5246 §7.2.1: on receiving close_notify, discard any pending writes.
1000+ pub fn discard_pending_writes ( & mut self ) {
1001+ self . queue_tx . clear ( ) ;
1002+ }
1003+
1004+ /// Abort the connection: flush all queued output, retransmission state, and
1005+ /// disable timers so that no further packets are emitted.
1006+ pub fn abort ( & mut self ) {
1007+ self . queue_tx . clear ( ) ;
1008+ self . flight_saved_records . clear ( ) ;
1009+ self . flight_timeout = Timeout :: Disabled ;
1010+ self . connect_timeout = Timeout :: Disabled ;
1011+ }
1012+
9021013 /// Pop a buffer from the buffer pool for temporary use
9031014 pub ( crate ) fn pop_buffer ( & mut self ) -> Buf {
9041015 self . buffers_free . pop ( )
0 commit comments