]> git.basschouten.com Git - openhab-addons.git/blob
91e77c0d0844188256b409b8506ae7f1459f7888
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.irobot.internal.utils;
14
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;
18
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;
30
31 import javax.net.ssl.SSLContext;
32
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;
37
38 import com.google.gson.Gson;
39 import com.google.gson.stream.JsonReader;
40
41 /**
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.
46  *
47  * @author Pavel Fedin - Initial contribution
48  * @author Alexander Falkenstern - Fix password fetching
49  *
50  */
51 @NonNullByDefault
52 public class LoginRequester {
53     private static final Gson GSON = new Gson();
54
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);
59
60         final byte[] bRequest = "irobotmcs".getBytes(StandardCharsets.UTF_8);
61         DatagramPacket request = new DatagramPacket(bRequest, bRequest.length, InetAddress.getByName(ip), UDP_PORT);
62         socket.send(request);
63
64         byte[] reply = new byte[1024];
65         try {
66             DatagramPacket packet = new DatagramPacket(reply, reply.length);
67             socket.receive(packet);
68             reply = Arrays.copyOfRange(packet.getData(), packet.getOffset(), packet.getLength());
69         } finally {
70             socket.close();
71         }
72
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);
76
77         @Nullable
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) {
82                 blid = parts[1];
83             }
84         }
85
86         return blid;
87     }
88
89     public static @Nullable String getPassword(final String ip)
90             throws KeyManagementException, NoSuchAlgorithmException, IOException {
91         String password = null;
92
93         SSLContext context = SSLContext.getInstance("SSL");
94         context.init(null, TRUST_MANAGERS, new java.security.SecureRandom());
95
96         Socket socket = context.getSocketFactory().createSocket(ip, MQTT_PORT);
97         socket.setSoTimeout(3000);
98
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);
104
105         ByteArrayOutputStream buffer = new ByteArrayOutputStream();
106         try {
107             socket.getInputStream().transferTo(buffer);
108         } catch (IOException exception) {
109             // Roomba 980 send no properly EOF, so eat the exception
110         } finally {
111             socket.close();
112             buffer.flush();
113         }
114
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);
120             }
121         }
122
123         return password;
124     }
125 }