]> git.basschouten.com Git - openhab-addons.git/blob
01eb82f5b205e093fc5ab7eda38adb2c0461902d
[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.freeboxos.internal.api.rest;
14
15 import java.time.ZonedDateTime;
16 import java.util.ArrayList;
17 import java.util.List;
18 import java.util.Optional;
19
20 import javax.ws.rs.core.UriBuilder;
21
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.binding.freeboxos.internal.api.FreeboxException;
25 import org.openhab.binding.freeboxos.internal.api.Response;
26 import org.openhab.binding.freeboxos.internal.api.rest.APManager.LanAccessPoint;
27 import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager.InterfacesResponse;
28
29 import inet.ipaddr.IPAddress;
30 import inet.ipaddr.IPAddressString;
31 import inet.ipaddr.mac.MACAddress;
32
33 /**
34  * The {@link LanBrowserManager} is the Java class used to handle api requests related to lan
35  *
36  * https://dev.freebox.fr/sdk/os/system/#
37  *
38  * @author GaĆ«l L'hopital - Initial contribution
39  */
40 @NonNullByDefault
41 public class LanBrowserManager extends ListableRest<LanBrowserManager.Interface, InterfacesResponse> {
42     private static final IPAddress NULL_IP = new IPAddressString("0.0.0.0").getAddress();
43     private static final String PATH = "browser";
44     private static final String INTERFACES = "interfaces";
45     private static final String WOL_ACTION = "wol";
46
47     protected static class HostsResponse extends Response<LanHost> {
48     }
49
50     protected static class InterfacesResponse extends Response<Interface> {
51     }
52
53     public enum Source {
54         DHCP,
55         NETBIOS,
56         MDNS,
57         MDNS_SRV,
58         UPNP,
59         WSD,
60         UNKNOWN
61     }
62
63     public record HostName(@Nullable String name, Source source) {
64     }
65
66     protected static record Interface(String name, int hostCount) {
67     }
68
69     private static record WakeOnLineData(String mac, String password) {
70     }
71
72     private enum Type {
73         MAC_ADDRESS,
74         UNKNOWN
75     }
76
77     private static record L2Ident(MACAddress id, Type type) {
78     }
79
80     private static record L3Connectivity(String addr, Af af, boolean active, boolean reachable,
81             ZonedDateTime lastActivity, ZonedDateTime lastTimeReachable, String model) {
82
83         private enum Af {
84             IPV4,
85             IPV6,
86             UNKNOWN
87         }
88
89         public IPAddress getIPAddress() {
90             if (af != Af.UNKNOWN) {
91                 return new IPAddressString(addr).getAddress();
92             }
93             return NULL_IP;
94         }
95     }
96
97     public static record HostIntf(LanHost host, Interface intf) {
98     }
99
100     private enum HostType {
101         WORKSTATION,
102         LAPTOP,
103         SMARTPHONE,
104         TABLET,
105         PRINTER,
106         VG_CONSOLE,
107         TELEVISION,
108         NAS,
109         IP_CAMERA,
110         IP_PHONE,
111         FREEBOX_PLAYER,
112         FREEBOX_HD,
113         FREEBOX_CRYSTAL,
114         FREEBOX_MINI,
115         FREEBOX_DELTA,
116         FREEBOX_ONE,
117         FREEBOX_WIFI,
118         FREEBOX_POP,
119         NETWORKING_DEVICE,
120         MULTIMEDIA_DEVICE,
121         CAR,
122         OTHER,
123         UNKNOWN
124     }
125
126     public static record LanHost(String id, @Nullable String primaryName, HostType hostType, boolean primaryNameManual,
127             L2Ident l2ident, @Nullable String vendorName, boolean persistent, boolean reachable,
128             @Nullable ZonedDateTime lastTimeReachable, boolean active, @Nullable ZonedDateTime lastActivity,
129             @Nullable ZonedDateTime firstActivity, @Nullable List<HostName> names,
130             List<L3Connectivity> l3connectivities, @Nullable LanAccessPoint accessPoint) {
131
132         public @Nullable LanAccessPoint accessPoint() {
133             return accessPoint;
134         }
135
136         public String vendorName() {
137             String localVendor = vendorName;
138             return localVendor == null || localVendor.isEmpty() ? "Unknown" : localVendor;
139         }
140
141         public Optional<String> getPrimaryName() {
142             return Optional.ofNullable(primaryName);
143         }
144
145         public List<HostName> getNames() {
146             List<HostName> localNames = names;
147             return localNames != null ? localNames : List.of();
148         }
149
150         public Optional<String> getName(Source searchedSource) {
151             return getNames().stream().filter(name -> name.source == searchedSource).findFirst().map(HostName::name);
152         }
153
154         public MACAddress getMac() {
155             if (Type.MAC_ADDRESS.equals(l2ident.type)) {
156                 return l2ident.id;
157             }
158             throw new IllegalArgumentException("This host does not seem to have a Mac Address. Weird.");
159         }
160
161         public @Nullable IPAddress getIpv4() {
162             return l3connectivities.stream().filter(L3Connectivity::reachable).map(L3Connectivity::getIPAddress)
163                     .filter(ip -> !ip.equals(NULL_IP) && ip.isIPv4()).findFirst().orElse(null);
164         }
165
166         public @Nullable ZonedDateTime getLastSeen() {
167             ZonedDateTime localLastActivity = lastActivity;
168             if (lastTimeReachable == null && localLastActivity == null) {
169                 return null;
170             }
171             if (lastTimeReachable == null) {
172                 return lastActivity;
173             }
174             if (localLastActivity == null) {
175                 return lastTimeReachable;
176             } else {
177                 return localLastActivity.isAfter(lastTimeReachable) ? lastActivity : lastTimeReachable;
178             }
179         }
180     }
181
182     private final List<Interface> interfaces = new ArrayList<>();
183
184     public LanBrowserManager(FreeboxOsSession session, UriBuilder uriBuilder) throws FreeboxException {
185         super(session, LoginManager.Permission.NONE, InterfacesResponse.class, uriBuilder.path(PATH));
186         listSubPath = INTERFACES;
187     }
188
189     private List<LanHost> getInterfaceHosts(String lanInterface) throws FreeboxException {
190         return get(HostsResponse.class, lanInterface);
191     }
192
193     private @Nullable LanHost getHost(String lanInterface, String hostId) throws FreeboxException {
194         return getSingle(HostsResponse.class, lanInterface, hostId);
195     }
196
197     // As the list of interfaces on the box may not change, we cache the result
198     private List<Interface> getInterfaces() throws FreeboxException {
199         if (interfaces.isEmpty()) {
200             interfaces.addAll(getDevices());
201         }
202         return interfaces;
203     }
204
205     public synchronized List<LanHost> getHosts() throws FreeboxException {
206         List<LanHost> hosts = new ArrayList<>();
207
208         for (Interface intf : getInterfaces()) {
209             hosts.addAll(getInterfaceHosts(intf.name()));
210         }
211         return hosts;
212     }
213
214     public Optional<HostIntf> getHost(MACAddress searched) throws FreeboxException {
215         for (Interface intf : getInterfaces()) {
216             LanHost host = getHost(intf.name(), "ether-" + searched.toColonDelimitedString());
217             if (host != null) {
218                 return Optional.of(new HostIntf(host, intf));
219             }
220         }
221         return Optional.empty();
222     }
223
224     public Optional<LanHost> getHost(HostName identifier) throws FreeboxException {
225         List<LanHost> hosts = getHosts();
226         LanHost result = null;
227         boolean multiple = false;
228         for (LanHost host : hosts) {
229             Optional<String> sourcedName = host.getName(identifier.source);
230             if (sourcedName.isPresent() && sourcedName.get().equals(identifier.name)) {
231                 // We will not return something if multiple hosts are found. This can happen in case of IP change that
232                 // a previous name remains attached to a different host.
233                 if (result == null) {
234                     result = host;
235                 } else if (!result.getMac().equals(host.getMac())) {
236                     // Multiple hosts with different macs
237                     multiple = true;
238                 }
239             }
240         }
241         if (multiple) {
242             result = null;
243         }
244         return Optional.ofNullable(result);
245     }
246
247     public boolean wakeOnLan(MACAddress mac, String password) throws FreeboxException {
248         Optional<HostIntf> target = getHost(mac);
249         if (target.isPresent()) {
250             post(new WakeOnLineData(mac.toColonDelimitedString(), password), GenericResponse.class, WOL_ACTION,
251                     target.get().intf.name);
252             return true;
253         }
254         return false;
255     }
256 }