[miio] validate response id matching command id (#9966)
* [miio] validate response id matching command id This PR prevents out of sync issues in case devices are too slow to respond or the timeout is set too short. Signed-off-by: Marcel Verpaalen <marcel@verpaalen.com> * [miio] spotless adding space Signed-off-by: Marcel Verpaalen <marcel@verpaalen.com> * [miio] update based on review Signed-off-by: Marcel Verpaalen <marcel@verpaalen.com>
This commit is contained in:
parent
7a867c4b08
commit
f58df8fd25
|
@ -18,6 +18,8 @@ import java.net.DatagramSocket;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.SocketException;
|
import java.net.SocketException;
|
||||||
import java.net.SocketTimeoutException;
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.time.Instant;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -187,15 +189,33 @@ public class MiIoAsyncCommunication {
|
||||||
decryptedResponse = decryptedResponse.replace(",,", ",");
|
decryptedResponse = decryptedResponse.replace(",,", ",");
|
||||||
JsonElement response;
|
JsonElement response;
|
||||||
response = parser.parse(decryptedResponse);
|
response = parser.parse(decryptedResponse);
|
||||||
if (response.isJsonObject()) {
|
if (!response.isJsonObject()) {
|
||||||
|
errorMsg = "Received message is not a JSON object ";
|
||||||
|
} else {
|
||||||
needPing = false;
|
needPing = false;
|
||||||
logger.trace("Received JSON message {}", response.toString());
|
logger.trace("Received JSON message {}", response.toString());
|
||||||
miIoSendCommand.setResponse(response.getAsJsonObject());
|
JsonObject resJson = response.getAsJsonObject();
|
||||||
return miIoSendCommand;
|
if (resJson.has("id")) {
|
||||||
} else {
|
int id = resJson.get("id").getAsInt();
|
||||||
errorMsg = "Received message is invalid JSON";
|
if (id == miIoSendCommand.getId()) {
|
||||||
logger.debug("{}: {}", errorMsg, decryptedResponse);
|
miIoSendCommand.setResponse(response.getAsJsonObject());
|
||||||
|
return miIoSendCommand;
|
||||||
|
} else {
|
||||||
|
if (id < miIoSendCommand.getId()) {
|
||||||
|
errorMsg = String.format(
|
||||||
|
"Received message out of sync, extend timeout time. Expected id: %d, received id: %d",
|
||||||
|
miIoSendCommand.getId(), id);
|
||||||
|
} else {
|
||||||
|
errorMsg = String.format("Received message out of sync. Expected id: %d, received id: %d",
|
||||||
|
miIoSendCommand.getId(), id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errorMsg = "Received message is without id";
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
logger.debug("{}: {}", errorMsg, decryptedResponse);
|
||||||
} catch (MiIoCryptoException | IOException e) {
|
} catch (MiIoCryptoException | IOException e) {
|
||||||
logger.debug("Send command '{}' -> {} (Device: {}) gave error {}", miIoSendCommand.getCommandString(), ip,
|
logger.debug("Send command '{}' -> {} (Device: {}) gave error {}", miIoSendCommand.getCommandString(), ip,
|
||||||
Utils.getHex(deviceId), e.getMessage());
|
Utils.getHex(deviceId), e.getMessage());
|
||||||
|
@ -272,10 +292,13 @@ public class MiIoAsyncCommunication {
|
||||||
|
|
||||||
private String sendCommand(String command, byte[] token, String ip, byte[] deviceId)
|
private String sendCommand(String command, byte[] token, String ip, byte[] deviceId)
|
||||||
throws MiIoCryptoException, IOException {
|
throws MiIoCryptoException, IOException {
|
||||||
byte[] encr;
|
byte[] sendMsg = new byte[0];
|
||||||
encr = MiIoCrypto.encrypt(command.getBytes(), token);
|
if (!command.isBlank()) {
|
||||||
timeStamp = (int) TimeUnit.MILLISECONDS.toSeconds(Calendar.getInstance().getTime().getTime());
|
byte[] encr;
|
||||||
byte[] sendMsg = Message.createMsgData(encr, token, deviceId, timeStamp + timeDelta);
|
encr = MiIoCrypto.encrypt(command.getBytes(StandardCharsets.UTF_8), token);
|
||||||
|
timeStamp = (int) Instant.now().getEpochSecond();
|
||||||
|
sendMsg = Message.createMsgData(encr, token, deviceId, timeStamp + timeDelta);
|
||||||
|
}
|
||||||
Message miIoResponseMsg = sendData(sendMsg, ip);
|
Message miIoResponseMsg = sendData(sendMsg, ip);
|
||||||
if (miIoResponseMsg == null) {
|
if (miIoResponseMsg == null) {
|
||||||
if (logger.isTraceEnabled()) {
|
if (logger.isTraceEnabled()) {
|
||||||
|
@ -374,12 +397,14 @@ public class MiIoAsyncCommunication {
|
||||||
DatagramPacket receivePacket = new DatagramPacket(new byte[MSG_BUFFER_SIZE], MSG_BUFFER_SIZE);
|
DatagramPacket receivePacket = new DatagramPacket(new byte[MSG_BUFFER_SIZE], MSG_BUFFER_SIZE);
|
||||||
try {
|
try {
|
||||||
logger.trace("Connection {}:{}", ip, clientSocket.getLocalPort());
|
logger.trace("Connection {}:{}", ip, clientSocket.getLocalPort());
|
||||||
byte[] sendData = new byte[MSG_BUFFER_SIZE];
|
if (message.length > 0) {
|
||||||
sendData = message;
|
byte[] sendData = new byte[MSG_BUFFER_SIZE];
|
||||||
DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, ipAddress,
|
sendData = message;
|
||||||
MiIoBindingConstants.PORT);
|
DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, ipAddress,
|
||||||
clientSocket.send(sendPacket);
|
MiIoBindingConstants.PORT);
|
||||||
sendPacket.setData(new byte[MSG_BUFFER_SIZE]);
|
clientSocket.send(sendPacket);
|
||||||
|
sendPacket.setData(new byte[MSG_BUFFER_SIZE]);
|
||||||
|
}
|
||||||
clientSocket.receive(receivePacket);
|
clientSocket.receive(receivePacket);
|
||||||
byte[] response = Arrays.copyOfRange(receivePacket.getData(), receivePacket.getOffset(),
|
byte[] response = Arrays.copyOfRange(receivePacket.getData(), receivePacket.getOffset(),
|
||||||
receivePacket.getOffset() + receivePacket.getLength());
|
receivePacket.getOffset() + receivePacket.getLength());
|
||||||
|
|
Loading…
Reference in New Issue