From 765bfb25fdaf7e4494994d412751d864185687b0 Mon Sep 17 00:00:00 2001 From: ericek111 Date: Wed, 23 Sep 2026 23:21:44 +0000 Subject: [PATCH] Fall back to the handshake key when a packet fails to decrypt The server encrypts initivexpand2 (COMMAND 0 and 1) with the handshake key. If our ACK for one of those fragments is lost, its retransmit arrives after we switched to the session key, fails with "mac check in EAX failed", is never acknowledged and blocks the server's command stream until it drops us. Common on lossy links such as mobile data. Retry generation 0 packets that fail under the session key with the handshake key. Co-Authored-By: Claude Opus 5.5 --- .../client/AbstractTeamspeakClientSocket.java | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/github/manevolent/ts3j/protocol/socket/client/AbstractTeamspeakClientSocket.java b/src/main/java/com/github/manevolent/ts3j/protocol/socket/client/AbstractTeamspeakClientSocket.java index 35bd662..59bb55e 100644 --- a/src/main/java/com/github/manevolent/ts3j/protocol/socket/client/AbstractTeamspeakClientSocket.java +++ b/src/main/java/com/github/manevolent/ts3j/protocol/socket/client/AbstractTeamspeakClientSocket.java @@ -75,7 +75,8 @@ public abstract class AbstractTeamspeakClientSocket private Identity identity = null; private ClientConnectionState connectionState = null; - private PacketTransformation transformation = new InitPacketTransformation(); + private final PacketTransformation initTransformation = new InitPacketTransformation(); + private PacketTransformation transformation = initTransformation; private PacketHandler handler; private int clientId = 0; @@ -543,15 +544,11 @@ public abstract class AbstractTeamspeakClientSocket + " generation=" + networkPacket.getHeader().getGeneration()); PacketTransformation transformation = getTransformation(); + int dataLen = networkPacket.getDatagram().getLength() - networkPacket.getHeader().getSize(); try { packetBuffer = ByteBuffer.wrap( - transformation.decrypt( - networkPacket.getHeader(), - packetBuffer, - networkPacket.getDatagram().getLength() - - networkPacket.getHeader().getSize() - ) + decrypt(transformation, networkPacket.getHeader(), packetBuffer, dataLen) ).order(ByteOrder.BIG_ENDIAN); } catch (InvalidCipherTextException e) { throw new IOException( @@ -604,6 +601,20 @@ public abstract class AbstractTeamspeakClientSocket return packet; } + /** + * Decrypts with the session key, falling back to the handshake key: the server encrypts initivexpand2 with + * the latter, so its retransmits (sent when our ACK was lost) still arrive under it after the key switch. + */ + private byte[] decrypt(PacketTransformation transformation, PacketHeader header, ByteBuffer buffer, int dataLen) + throws InvalidCipherTextException { + try { + return transformation.decrypt(header, buffer, dataLen); + } catch (InvalidCipherTextException e) { + if (transformation instanceof InitPacketTransformation || header.getGeneration() != 0) throw e; + return initTransformation.decrypt(header, buffer, dataLen); + } + } + @Override public Packet readPacket() throws IOException,