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

  1. Reinstall/upgrade the AppManager root/adb server binary so client and server come from the same build (both embed the same PROTOCOL_VERSION).
  2. Check the FLog output lines 'Server protocol: ...' and 'Client protocol: ...' to see exactly which side is stale, then update that side.
  3. Ensure no other service or proxy is bound to the same port sending/receiving the first handshake message.
  4. 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

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


AI-assisted analysis of MuntashirAkon/AppManager@0152f468fc (2026-09-12). Data as JSON: /api/errors/b2d68b56bab57c56. Report an issue: GitHub.