44import asyncio
55import binascii
66from collections .abc import Coroutine
7+ import contextlib
78import dataclasses
89import enum
910import logging
@@ -62,7 +63,7 @@ class Reserved(enum.IntEnum):
6263# Maximum number of consecutive timeouts allowed while waiting to receive an ACK before
6364# going to the FAILED state. The value 0 prevents the NCP from entering the error state
6465# due to timeouts.
65- ACK_TIMEOUTS = 4
66+ ACK_TIMEOUTS = 5
6667
6768
6869def generate_random_sequence (length : int ) -> bytes :
@@ -368,14 +369,26 @@ def connection_made(self, transport):
368369 self ._ezsp_protocol .connection_made (self )
369370
370371 def connection_lost (self , exc ):
372+ self ._transport = None
373+ self ._cancel_pending_data_frames ()
371374 self ._ezsp_protocol .connection_lost (exc )
372375
373376 def eof_received (self ):
374377 self ._ezsp_protocol .eof_received ()
375378
379+ def _cancel_pending_data_frames (
380+ self , exc : BaseException = RuntimeError ("Connection has been closed" )
381+ ):
382+ for fut in self ._pending_data_frames .values ():
383+ if not fut .done ():
384+ fut .set_exception (exc )
385+
376386 def close (self ):
387+ self ._cancel_pending_data_frames ()
388+
377389 if self ._transport is not None :
378390 self ._transport .close ()
391+ self ._transport = None
379392
380393 @staticmethod
381394 def _stuff_bytes (data : bytes ) -> bytes :
@@ -399,7 +412,9 @@ def _unstuff_bytes(data: bytes) -> bytes:
399412 for c in data :
400413 if escaped :
401414 byte = c ^ 0b00100000
402- assert byte in RESERVED_BYTES
415+ if byte not in RESERVED_BYTES :
416+ raise ParsingError (f"Invalid escaped byte: 0x{ byte :02X} " )
417+
403418 out .append (byte )
404419 escaped = False
405420 elif c == Reserved .ESCAPE :
@@ -417,7 +432,7 @@ def data_received(self, data: bytes) -> None:
417432 _LOGGER .debug (
418433 "Truncating buffer to %s bytes, it is growing too fast" , MAX_BUFFER_SIZE
419434 )
420- self ._buffer = self ._buffer [: MAX_BUFFER_SIZE ]
435+ self ._buffer = self ._buffer [- MAX_BUFFER_SIZE : ]
421436
422437 while self ._buffer :
423438 if self ._discarding_until_next_flag :
@@ -447,14 +462,19 @@ def data_received(self, data: bytes) -> None:
447462 if not frame_bytes :
448463 continue
449464
450- data = self ._unstuff_bytes (frame_bytes )
451-
452465 try :
466+ data = self ._unstuff_bytes (frame_bytes )
453467 frame = parse_frame (data )
454468 except Exception :
455469 _LOGGER .debug (
456470 "Failed to parse frame %r" , frame_bytes , exc_info = True
457471 )
472+
473+ with contextlib .suppress (NcpFailure ):
474+ self ._write_frame (
475+ NakFrame (res = 0 , ncp_ready = 0 , ack_num = self ._rx_seq ),
476+ prefix = (Reserved .CANCEL ,),
477+ )
458478 else :
459479 self .frame_received (frame )
460480 elif reserved_byte == Reserved .CANCEL :
@@ -479,7 +499,7 @@ def data_received(self, data: bytes) -> None:
479499 f"Unexpected reserved byte found: 0x{ reserved_byte :02X} "
480500 ) # pragma: no cover
481501
482- def _handle_ack (self , frame : DataFrame | AckFrame ) -> None :
502+ def _handle_ack (self , frame : DataFrame | AckFrame | NakFrame ) -> None :
483503 # Note that ackNum is the number of the next frame the receiver expects and it
484504 # is one greater than the last frame received.
485505 for ack_num_offset in range (- TX_K , 0 ):
@@ -494,14 +514,19 @@ def _handle_ack(self, frame: DataFrame | AckFrame) -> None:
494514 def frame_received (self , frame : AshFrame ) -> None :
495515 _LOGGER .debug ("Received frame %r" , frame )
496516
517+ # If a frame has ACK information (DATA, ACK, or NAK), it should be used even if
518+ # the frame is out of sequence or invalid
497519 if isinstance (frame , DataFrame ):
520+ self ._handle_ack (frame )
498521 self .data_frame_received (frame )
499- elif isinstance (frame , RStackFrame ):
500- self .rstack_frame_received (frame )
501522 elif isinstance (frame , AckFrame ):
523+ self ._handle_ack (frame )
502524 self .ack_frame_received (frame )
503525 elif isinstance (frame , NakFrame ):
526+ self ._handle_ack (frame )
504527 self .nak_frame_received (frame )
528+ elif isinstance (frame , RStackFrame ):
529+ self .rstack_frame_received (frame )
505530 elif isinstance (frame , RstFrame ):
506531 self .rst_frame_received (frame )
507532 elif isinstance (frame , ErrorFrame ):
@@ -513,7 +538,6 @@ def data_frame_received(self, frame: DataFrame) -> None:
513538 # The Host may not piggyback acknowledgments and should promptly send an ACK
514539 # frame when it receives a DATA frame.
515540 if frame .frm_num == self ._rx_seq :
516- self ._handle_ack (frame )
517541 self ._rx_seq = (frame .frm_num + 1 ) % 8
518542 self ._write_frame (AckFrame (res = 0 , ncp_ready = 0 , ack_num = self ._rx_seq ))
519543
@@ -536,14 +560,10 @@ def rstack_frame_received(self, frame: RStackFrame) -> None:
536560 self ._ezsp_protocol .reset_received (frame .reset_code )
537561
538562 def ack_frame_received (self , frame : AckFrame ) -> None :
539- self . _handle_ack ( frame )
563+ pass
540564
541565 def nak_frame_received (self , frame : NakFrame ) -> None :
542- err = NotAcked (frame = frame )
543-
544- for fut in self ._pending_data_frames .values ():
545- if not fut .done ():
546- fut .set_exception (err )
566+ self ._cancel_pending_data_frames (NotAcked (frame = frame ))
547567
548568 def rst_frame_received (self , frame : RstFrame ) -> None :
549569 self ._ncp_reset_code = None
@@ -558,12 +578,8 @@ def error_frame_received(self, frame: ErrorFrame) -> None:
558578 self ._enter_failed_state (self ._ncp_reset_code )
559579
560580 def _enter_failed_state (self , reset_code : t .NcpResetCode ) -> None :
561- exc = NcpFailure (code = reset_code )
562-
563- for fut in self ._pending_data_frames .values ():
564- if not fut .done ():
565- fut .set_exception (exc )
566-
581+ self ._ncp_state = NcpState .FAILED
582+ self ._cancel_pending_data_frames (NcpFailure (code = reset_code ))
567583 self ._ezsp_protocol .reset_received (reset_code )
568584
569585 def _write_frame (
@@ -573,6 +589,9 @@ def _write_frame(
573589 prefix : tuple [Reserved ] = (),
574590 suffix : tuple [Reserved ] = (Reserved .FLAG ,),
575591 ) -> None :
592+ if self ._transport is None or self ._transport .is_closing ():
593+ raise NcpFailure ("Transport is closed, cannot send frame" )
594+
576595 if _LOGGER .isEnabledFor (logging .DEBUG ):
577596 prefix_str = "" .join ([f"{ r .name } + " for r in prefix ])
578597 suffix_str = "" .join ([f" + { r .name } " for r in suffix ])
@@ -631,7 +650,9 @@ async def _send_data_frame(self, frame: AshFrame) -> None:
631650 await ack_future
632651 except NotAcked :
633652 _LOGGER .debug (
634- "NCP responded with NAK. Retrying (attempt %d)" , attempt + 1
653+ "NCP responded with NAK to %r. Retrying (attempt %d)" ,
654+ frame ,
655+ attempt + 1 ,
635656 )
636657
637658 # For timing purposes, NAK can be treated as an ACK
@@ -650,9 +671,10 @@ async def _send_data_frame(self, frame: AshFrame) -> None:
650671 raise
651672 except asyncio .TimeoutError :
652673 _LOGGER .debug (
653- "No ACK received in %0.2fs (attempt %d)" ,
674+ "No ACK received in %0.2fs (attempt %d) for %r " ,
654675 self ._t_rx_ack ,
655676 attempt + 1 ,
677+ frame ,
656678 )
657679 # If a DATA frame acknowledgement is not received within the
658680 # current timeout value, then t_rx_ack is doubled.
0 commit comments