From 6d1e9212e9ef0f30a23d0441d190b8735c5f097e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A7=8B=20=E5=A5=88=E6=9C=88?= <43605695+akinazuki@users.noreply.github.com> Date: Thu, 12 Mar 2026 15:53:54 +0900 Subject: [PATCH] Fix reconnection with a cached base token When a previously obtained base token is passed to `create_apns_connection`, reconnection would hang or crash due to two issues: 1. The ConnectAck filter required `ack.token == cached_token`, but the server may return a different token or omit the token field entirely (when it accepts the cached one). This caused `_receive` to block forever. 2. `base_token` property skipped `_connected.wait()` when `_base_token` was pre-set, allowing callers to send commands before the TCP connection and APNs handshake were actually established. 3. When the server accepts a cached token, ConnectAck does not include a token field (item 0x03 is absent), causing `ack.token` to be None. The old code would crash on the `assert ack.token == self._base_token`. Changes: - Accept any ConnectAck regardless of token value - Always await `_connected` event before exposing `base_token` - Handle all token scenarios: None (accepted), same, changed, or new This matches real `apsd` behavior where the daemon persists the base token to `com.apple.apsd.plist` and reuses it across device reboots. --- pypush/apns/lifecycle.py | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/pypush/apns/lifecycle.py b/pypush/apns/lifecycle.py index 1a2e9f9..20d2a3e 100644 --- a/pypush/apns/lifecycle.py +++ b/pypush/apns/lifecycle.py @@ -76,8 +76,7 @@ def __init__( @property async def base_token(self) -> bytes: - if self._base_token is None: - await self._connected.wait() + await self._connected.wait() assert self._base_token is not None return self._base_token @@ -134,25 +133,21 @@ async def reconnect(self): self._tg.start_soon(self._receive_task) ack = await self._receive( - filters.chain( - filters.cmd(protocol.ConnectAck), - lambda c: ( - c - if ( - c.token == self._base_token - if self._base_token is not None - else True - ) - else None - ), - ) + filters.cmd(protocol.ConnectAck), ) logging.debug(f"Connected with ack: {ack}") assert ack.status == 0 - if self._base_token is None: + if ack.token is None: + # Server accepted the cached token without returning a new one + logging.debug(f"Base token accepted by server: {self._base_token.hex()}") + elif self._base_token is None: + self._base_token = ack.token + logging.debug(f"Base token assigned: {ack.token.hex()}") + elif ack.token != self._base_token: + logging.warning(f"Base token changed: {self._base_token.hex()} -> {ack.token.hex()}") self._base_token = ack.token else: - assert ack.token == self._base_token + logging.debug(f"Base token confirmed: {ack.token.hex()}") if not self._connected.is_set(): self._connected.set()