2 * Copyright (c) 2010-2022 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.irobot.internal.utils;
15 import static org.openhab.binding.irobot.internal.IRobotBindingConstants.MQTT_PORT;
16 import static org.openhab.binding.irobot.internal.IRobotBindingConstants.TRUST_MANAGERS;
17 import static org.openhab.binding.irobot.internal.IRobotBindingConstants.UDP_PORT;
19 import java.io.ByteArrayOutputStream;
20 import java.io.IOException;
21 import java.io.StringReader;
22 import java.net.DatagramPacket;
23 import java.net.DatagramSocket;
24 import java.net.InetAddress;
25 import java.net.Socket;
26 import java.nio.charset.StandardCharsets;
27 import java.security.KeyManagementException;
28 import java.security.NoSuchAlgorithmException;
29 import java.util.Arrays;
31 import javax.net.ssl.SSLContext;
33 import org.eclipse.jdt.annotation.NonNullByDefault;
34 import org.eclipse.jdt.annotation.Nullable;
35 import org.openhab.binding.irobot.internal.discovery.IRobotDiscoveryService;
36 import org.openhab.binding.irobot.internal.dto.MQTTProtocol.BlidResponse;
38 import com.google.gson.Gson;
39 import com.google.gson.stream.JsonReader;
42 * Helper functions to get blid and password. Seems pretty much reinventing a bicycle,
43 * but it looks like HiveMq doesn't provide for sending and receiving custom packets.
44 * The {@link LoginRequester#getBlid} and {@link IRobotDiscoveryService} are heavily
45 * related to each other.
47 * @author Pavel Fedin - Initial contribution
48 * @author Alexander Falkenstern - Fix password fetching
52 public class LoginRequester {
53 private static final Gson GSON = new Gson();
55 public static @Nullable String getBlid(final String ip) throws IOException {
56 DatagramSocket socket = new DatagramSocket();
57 socket.setSoTimeout(1000); // One second
58 socket.setReuseAddress(true);
60 final byte[] bRequest = "irobotmcs".getBytes(StandardCharsets.UTF_8);
61 DatagramPacket request = new DatagramPacket(bRequest, bRequest.length, InetAddress.getByName(ip), UDP_PORT);
64 byte[] reply = new byte[1024];
66 DatagramPacket packet = new DatagramPacket(reply, reply.length);
67 socket.receive(packet);
68 reply = Arrays.copyOfRange(packet.getData(), packet.getOffset(), packet.getLength());
73 final String json = new String(reply, 0, reply.length, StandardCharsets.UTF_8);
74 JsonReader jsonReader = new JsonReader(new StringReader(json));
75 BlidResponse msg = GSON.fromJson(jsonReader, BlidResponse.class);
78 String blid = msg.robotid;
79 if (((blid == null) || blid.isEmpty()) && ((msg.hostname != null) && !msg.hostname.isEmpty())) {
80 String[] parts = msg.hostname.split("-");
81 if (parts.length == 2) {
89 public static @Nullable String getPassword(final String ip)
90 throws KeyManagementException, NoSuchAlgorithmException, IOException {
91 String password = null;
93 SSLContext context = SSLContext.getInstance("SSL");
94 context.init(null, TRUST_MANAGERS, new java.security.SecureRandom());
96 Socket socket = context.getSocketFactory().createSocket(ip, MQTT_PORT);
97 socket.setSoTimeout(3000);
99 // 1st byte: MQTT reserved message: 0xF0
100 // 2nd byte: Data length: 0x05
101 // from 3d byte magic packet data: 0xEFCC3B2900
102 final byte[] request = { (byte) 0xF0, (byte) 0x05, (byte) 0xEF, (byte) 0xCC, (byte) 0x3B, (byte) 0x29, 0x00 };
103 socket.getOutputStream().write(request);
105 ByteArrayOutputStream buffer = new ByteArrayOutputStream();
107 socket.getInputStream().transferTo(buffer);
108 } catch (IOException exception) {
109 // Roomba 980 send no properly EOF, so eat the exception
115 final byte[] reply = buffer.toByteArray();
116 if ((reply.length > request.length) && (reply.length == reply[1] + 2)) { // Add 2 bytes, see request doc above
117 reply[1] = request[1]; // Hack, that we can find request packet in reply
118 if (Arrays.equals(request, 0, request.length, reply, 0, request.length)) {
119 password = new String(Arrays.copyOfRange(reply, request.length, reply.length), StandardCharsets.UTF_8);