{"record":{"id":"b2d68b56bab57c56","repo":"MuntashirAkon/AppManager","slug":"client-protocol-version","errorCode":null,"errorMessage":"Client protocol version: ","messagePattern":"Client protocol version: ","errorType":"exception","errorClass":"ProtocolVersionException","httpStatus":null,"severity":"error","filePath":"libserver/src/main/java/io/github/muntashirakon/AppManager/server/common/DataTransmission.java","lineNumber":196,"sourceCode":"            byte[] nonceS = readMessage();\n\n            // Validate server (HMAC_S == HMAC(token, Nonce_C)?)\n            byte[] expectedServerHmac = AuthUtils.calculateHmac(token, nonceC);\n            if (!MessageDigest.isEqual(serverHmac, expectedServerHmac)) {\n                FLog.log(\"DataTransmission#shakeHands: Rogue server detected! Connection dropped.\");\n                throw new IOException(\"Unauthorized server: HMAC mismatch.\");\n            }\n\n            // Prove legitimacy of client to the server (HMAC_C = HMAC(token, Nonce_S)\n            byte[] clientHmac = AuthUtils.calculateHmac(token, nonceS);\n            sendMessage(clientHmac);\n\n        } else if (role == Role.Server) {\n            FLog.log(\"DataTransmission#shakeHands: Server protocol: \" + PROTOCOL_VERSION);\n            // Receive protocol version and client nonce (Nonce_C)\n            String clientProtocol = new String(readMessage(), StandardCharsets.UTF_8);\n            if (!PROTOCOL_VERSION.equals(clientProtocol)) {\n                throw new ProtocolVersionException(\"Client protocol version: \" + clientProtocol + \", \" +\n                        \"Server protocol version: \" + PROTOCOL_VERSION);\n            }\n            byte[] nonceC = readMessage();\n\n            // Prove legitimacy of server to the client (HMAC_S = HMAC(token, Nonce_C))\n            byte[] serverHmac = AuthUtils.calculateHmac(token, nonceC);\n            sendMessage(serverHmac);\n\n            // Send challenge to client (Nonce_S)\n            byte[] nonceS = AuthUtils.generateNonce();\n            sendMessage(nonceS);\n\n            // Receive client's HMAC (HMAC_C)\n            byte[] clientHmac = readMessage();\n\n            // Validate client (HMAC_C == HMAC(token, Nonce_S)?)\n            byte[] expectedClientHmac = AuthUtils.calculateHmac(token, nonceS);\n            if (MessageDigest.isEqual(clientHmac, expectedClientHmac)) {","sourceCodeStart":178,"sourceCodeEnd":214,"githubUrl":"https://github.com/MuntashirAkon/AppManager/blob/0152f468fc9463ee02dc2ca83f6fe4989a2c4ca5/libserver/src/main/java/io/github/muntashirakon/AppManager/server/common/DataTransmission.java#L178-L214","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: stale server binary after app update\nadb shell su -c 'rm /data/local/tmp/am_server'  // remove old binary\n// after\n// reinstall so AppManager pushes its current matching server binary, then reconnect\n","handlingStrategy":"validation","validationCode":"if (!Objects.equals(clientProtocolVersion, DataTransmission.PROTOCOL_VERSION)) {\n    throw new IllegalStateException(\"Version mismatch: client=\" + clientProtocolVersion + \" server=\" + DataTransmission.PROTOCOL_VERSION);\n}\n// best: always install the server binary shipped by the same app build\n","typeGuard":null,"tryCatchPattern":"try {\n    transmission.shakeHands(token, Role.Client);\n} catch (DataTransmission.ProtocolVersionException e) {\n    // log both versions, prompt user to reinstall matching server binary\n    Log.e(TAG, \"Protocol mismatch, update server\", e);\n}\n","preventionTips":["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."],"tags":["handshake","protocol-version-mismatch","ipc"],"backgroundTag":"incompatible-protocol-version","analyzedSha":"0152f468fc9463ee02dc2ca83f6fe4989a2c4ca5","analyzedAt":"2026-09-12T14:03:37.243Z","contentChangedAt":"2026-09-12T14:03:37.243Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}