]> git.basschouten.com Git - openhab-addons.git/blob
4e9da808cda49f1b0f0a79a6cf34c93229a3f712
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.network.internal;
14
15 import java.io.IOException;
16 import java.net.*;
17 import java.util.Arrays;
18 import java.util.Objects;
19 import java.util.function.Consumer;
20 import java.util.stream.Stream;
21
22 import org.apache.commons.lang3.StringUtils;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.core.net.NetUtil;
26 import org.openhab.core.util.HexUtils;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
29
30 /**
31  * The {@link WakeOnLanPacketSender} broadcasts a magic packet to wake a device.
32  *
33  * @author Wouter Born - Initial contribution
34  */
35 @NonNullByDefault
36 public class WakeOnLanPacketSender {
37
38     private static final int WOL_UDP_PORT = 9;
39
40     // Wake-on-LAN magic packet constants
41     static final int PREFIX_BYTE_SIZE = 6;
42     static final int MAC_REPETITIONS = 16;
43     static final int MAC_BYTE_SIZE = 6;
44     static final int MAGIC_PACKET_BYTE_SIZE = PREFIX_BYTE_SIZE + MAC_REPETITIONS * MAC_BYTE_SIZE;
45     static final String[] MAC_SEPARATORS = new String[] { ":", "-" };
46
47     private final Logger logger = LoggerFactory.getLogger(WakeOnLanPacketSender.class);
48
49     private final String macAddress;
50
51     @Nullable
52     private final String hostname;
53
54     @Nullable
55     private final Integer port;
56
57     private byte @Nullable [] magicPacket;
58     private final Consumer<byte[]> magicPacketSender;
59
60     public WakeOnLanPacketSender(String macAddress, @Nullable String hostname, @Nullable Integer port) {
61         this.macAddress = macAddress;
62         this.hostname = hostname;
63         this.port = port;
64         this.magicPacketSender = this::sendMagicPacket;
65     }
66
67     public WakeOnLanPacketSender(String macAddress) {
68         this.macAddress = macAddress;
69         this.hostname = null;
70         this.port = null;
71         this.magicPacketSender = this::sendMagicPacket;
72     }
73
74     /**
75      * Used for testing only.
76      */
77     WakeOnLanPacketSender(String macAddress, Consumer<byte[]> magicPacketSender) {
78         this.macAddress = macAddress;
79         this.hostname = null;
80         this.port = null;
81         this.magicPacketSender = magicPacketSender;
82     }
83
84     public void sendPacket() {
85         byte[] localMagicPacket = magicPacket;
86         if (localMagicPacket == null) {
87             localMagicPacket = createMagicPacket(createMacBytes(macAddress));
88             magicPacket = localMagicPacket;
89         }
90
91         magicPacketSender.accept(localMagicPacket);
92     }
93
94     private byte[] createMacBytes(String macAddress) {
95         String hexString = macAddress;
96         for (String macSeparator : MAC_SEPARATORS) {
97             hexString = hexString.replaceAll(macSeparator, "");
98         }
99         if (hexString.length() != 2 * MAC_BYTE_SIZE) {
100             throw new IllegalStateException("Invalid MAC address: " + macAddress);
101         }
102         return HexUtils.hexToBytes(hexString);
103     }
104
105     private byte[] createMagicPacket(byte[] macBytes) {
106         byte[] bytes = new byte[MAGIC_PACKET_BYTE_SIZE];
107         Arrays.fill(bytes, 0, PREFIX_BYTE_SIZE, (byte) 0xff);
108         for (int i = PREFIX_BYTE_SIZE; i < MAGIC_PACKET_BYTE_SIZE; i += MAC_BYTE_SIZE) {
109             System.arraycopy(macBytes, 0, bytes, i, macBytes.length);
110         }
111         return bytes;
112     }
113
114     private void sendMagicPacket(byte[] magicPacket) {
115         try (DatagramSocket socket = new DatagramSocket()) {
116             if (StringUtils.isEmpty(hostname)) {
117                 broadcastMagicPacket(magicPacket, socket);
118             } else {
119                 SocketAddress socketAddress = new InetSocketAddress(this.hostname,
120                         Objects.requireNonNullElse(this.port, WOL_UDP_PORT));
121                 sendMagicPacketToIp(magicPacket, socket, socketAddress);
122             }
123         } catch (SocketException e) {
124             logger.error("Failed to open Wake-on-LAN datagram socket", e);
125         }
126     }
127
128     private void broadcastMagicPacket(byte[] magicPacket, DatagramSocket socket) {
129         broadcastAddressStream().forEach(broadcastAddress -> {
130             try {
131                 DatagramPacket packet = new DatagramPacket(magicPacket, MAGIC_PACKET_BYTE_SIZE, broadcastAddress,
132                         WOL_UDP_PORT);
133                 socket.send(packet);
134                 logger.debug("Wake-on-LAN packet sent (MAC address: {}, broadcast address: {})", macAddress,
135                         broadcastAddress.getHostAddress());
136             } catch (IOException e) {
137                 logger.debug("Failed to send Wake-on-LAN packet (MAC address: {}, broadcast address: {})", macAddress,
138                         broadcastAddress.getHostAddress(), e);
139             }
140         });
141         logger.info("Wake-on-LAN packets sent (MAC address: {})", macAddress);
142     }
143
144     private void sendMagicPacketToIp(byte[] magicPacket, DatagramSocket socket, SocketAddress ip) {
145         DatagramPacket packet = new DatagramPacket(magicPacket, MAGIC_PACKET_BYTE_SIZE, ip);
146         try {
147             socket.send(packet);
148         } catch (IOException e) {
149             logger.debug("Failed to send Wake-on-LAN packet (MAC address: {}, address: {})", macAddress, ip, e);
150         }
151         logger.info("Wake-on-LAN packets sent (MAC address: {}, IP address: {})", macAddress, ip);
152     }
153
154     private Stream<InetAddress> broadcastAddressStream() {
155         return NetUtil.getAllBroadcastAddresses().stream().map(address -> {
156             try {
157                 return InetAddress.getByName(address);
158             } catch (UnknownHostException e) {
159                 logger.debug("Failed to get broadcast address '{}' by name", address, e);
160                 return null;
161             }
162         }).filter(Objects::nonNull);
163     }
164 }