MuntashirAkon/AppManager · error · ProtocolVersionException
Client protocol version:
Error message
Client protocol version:
What it means
DataTransmission.shakeHands performs a mutual HMAC challenge-response handshake. The server first reads the client's protocol version string and compares it against its own PROTOCOL_VERSION; on mismatch it throws ProtocolVersionException, embedding both versions so the operator can see which side is outdated. It exists to prevent two incompatible AppManager server/client builds from speaking a protocol the other cannot parse.
Source
Thrown at libserver/src/main/java/io/github/muntashirakon/AppManager/server/common/DataTransmission.java:196
byte[] nonceS = readMessage();
// Validate server (HMAC_S == HMAC(token, Nonce_C)?)
byte[] expectedServerHmac = AuthUtils.calculateHmac(token, nonceC);
if (!MessageDigest.isEqual(serverHmac, expectedServerHmac)) {
FLog.log("DataTransmission#shakeHands: Rogue server detected! Connection dropped.");
throw new IOException("Unauthorized server: HMAC mismatch.");
}
// Prove legitimacy of client to the server (HMAC_C = HMAC(token, Nonce_S)
byte[] clientHmac = AuthUtils.calculateHmac(token, nonceS);
sendMessage(clientHmac);
} else if (role == Role.Server) {
FLog.log("DataTransmission#shakeHands: Server protocol: " + PROTOCOL_VERSION);
// Receive protocol version and client nonce (Nonce_C)
String clientProtocol = new String(readMessage(), StandardCharsets.UTF_8);
if (!PROTOCOL_VERSION.equals(clientProtocol)) {
throw new ProtocolVersionException("Client protocol version: " + clientProtocol + ", " +
"Server protocol version: " + PROTOCOL_VERSION);
}
byte[] nonceC = readMessage();
// Prove legitimacy of server to the client (HMAC_S = HMAC(token, Nonce_C))
byte[] serverHmac = AuthUtils.calculateHmac(token, nonceC);
sendMessage(serverHmac);
// Send challenge to client (Nonce_S)
byte[] nonceS = AuthUtils.generateNonce();
sendMessage(nonceS);
// Receive client's HMAC (HMAC_C)
byte[] clientHmac = readMessage();
// Validate client (HMAC_C == HMAC(token, Nonce_S)?)
byte[] expectedClientHmac = AuthUtils.calculateHmac(token, nonceS);
if (MessageDigest.isEqual(clientHmac, expectedClientHmac)) {View on GitHub (pinned to 0152f468fc)
Solutions
- Reinstall/upgrade the AppManager root/adb server binary so client and server come from the same build (both embed the same PROTOCOL_VERSION).
- Check the FLog output lines 'Server protocol: ...' and 'Client protocol: ...' to see exactly which side is stale, then update that side.
- Ensure no other service or proxy is bound to the same port sending/receiving the first handshake message.
- If you maintain a custom client, send PROTOCOL_VERSION (UTF-8) as the very first message before the client nonce.
Example fix
// before: stale server binary after app update adb shell su -c 'rm /data/local/tmp/am_server' // remove old binary // after // reinstall so AppManager pushes its current matching server binary, then reconnect
Defensive patterns
Strategy: validation
Validate before calling
if (!Objects.equals(clientProtocolVersion, DataTransmission.PROTOCOL_VERSION)) {
throw new IllegalStateException("Version mismatch: client=" + clientProtocolVersion + " server=" + DataTransmission.PROTOCOL_VERSION);
}
// best: always install the server binary shipped by the same app build
Try / catch
try {
transmission.shakeHands(token, Role.Client);
} catch (DataTransmission.ProtocolVersionException e) {
// log both versions, prompt user to reinstall matching server binary
Log.e(TAG, "Protocol mismatch, update server", e);
}
Prevention
- Always pair client and server from the same AppManager build; never copy an old am_server binary manually.
- Log both protocol versions on mismatch and surface an 'update the app/server' message.
- Keep the server socket bound to localhost/firewalled so foreign clients cannot send garbage into the handshake.
When it happens
Trigger: The server side of shakeHands() reads the first message from a connecting client and its value does not byte-equal PROTOCOL_VERSION — i.e. a client built from a different (older/newer) AppManager version connects, a non-AppManager client connects and sends garbage, or the client sends its nonce/other data before the version string.
Common situations: AppManager app updated (or downgraded) on the device while the adb/root server binary is still the previous build; mixing a debug client build with a release server build; a port scanner or unrelated process connecting to the server socket and sending non-protocol bytes; a MITM proxy mangling the first frame.
Related errors
- Invalid userId
- Can't unparcel type " + actual.getName() + " in list of type
- ERROR_MSG (constant; invalid/unknown file handle for calling
- IOException(ERROR_MSG)
- IOException(REMOTE_ERR_MSG, throwable)
AI-assisted analysis of MuntashirAkon/AppManager@0152f468fc (2026-09-12).
Data as JSON: /api/errors/b2d68b56bab57c56.
Report an issue: GitHub.