]> git.basschouten.com Git - openhab-addons.git/blob
4741a077776a23aaa180af89fd29bad940dd0c09
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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 final Consumer<byte[]> magicPacketMacSender;
58     private final Consumer<byte[]> magicPacketIpSender;
59
60     public WakeOnLanPacketSender(String macAddress, @Nullable String hostname, @Nullable Integer port) {
61         logger.debug("initialized WOL Packet Sender (mac: {}, hostname: {}, port: {}", macAddress, hostname, port);
62         this.macAddress = macAddress;
63         this.hostname = hostname;
64         this.port = port;
65         this.magicPacketMacSender = this::sendMagicPacketViaMac;
66         this.magicPacketIpSender = this::sendMagicPacketViaIp;
67     }
68
69     /**
70      * Used for testing only.
71      */
72     public WakeOnLanPacketSender(String macAddress) {
73         logger.debug("initialized WOL Packet Sender (mac: {}", macAddress);
74         this.macAddress = macAddress;
75         this.hostname = null;
76         this.port = null;
77         this.magicPacketMacSender = this::sendMagicPacketViaMac;
78         this.magicPacketIpSender = this::sendMagicPacketViaIp;
79     }
80
81     /**
82      * Used for testing only.
83      */
84     WakeOnLanPacketSender(String macAddress, Consumer<byte[]> magicPacketSender) {
85         this.macAddress = macAddress;
86         this.hostname = null;
87         this.port = null;
88         this.magicPacketMacSender = magicPacketSender;
89         this.magicPacketIpSender = this::sendMagicPacketViaIp;
90     }
91
92     public void sendWakeOnLanPacketViaMac() {
93         byte[] magicPacket = createMagicPacket();
94         this.magicPacketMacSender.accept(magicPacket);
95     }
96
97     public void sendWakeOnLanPacketViaIp() {
98         byte[] magicPacket = createMagicPacket();
99         this.magicPacketIpSender.accept(magicPacket);
100     }
101
102     private byte[] createMagicPacket() {
103         byte[] macBytes = createMacBytes(this.macAddress);
104         byte[] magicPacket = new byte[MAGIC_PACKET_BYTE_SIZE];
105         Arrays.fill(magicPacket, 0, PREFIX_BYTE_SIZE, (byte) 0xff);
106         for (int i = PREFIX_BYTE_SIZE; i < MAGIC_PACKET_BYTE_SIZE; i += MAC_BYTE_SIZE) {
107             System.arraycopy(macBytes, 0, magicPacket, i, macBytes.length);
108         }
109         return magicPacket;
110     }
111
112     private byte[] createMacBytes(String macAddress) {
113         String hexString = macAddress;
114         for (String macSeparator : MAC_SEPARATORS) {
115             hexString = hexString.replaceAll(macSeparator, "");
116         }
117         if (hexString.length() != 2 * MAC_BYTE_SIZE) {
118             throw new IllegalStateException("Invalid MAC address: " + macAddress);
119         }
120         return HexUtils.hexToBytes(hexString);
121     }
122
123     private void sendMagicPacketViaMac(byte[] magicPacket) {
124         try (DatagramSocket socket = new DatagramSocket()) {
125             logger.debug("Sending Wake-on-LAN Packet via Broadcast");
126             broadcastMagicPacket(magicPacket, socket);
127         } catch (SocketException e) {
128             logger.error("Failed to open Wake-on-LAN datagram socket", e);
129         }
130     }
131
132     private void sendMagicPacketViaIp(byte[] magicPacket) {
133         try (DatagramSocket socket = new DatagramSocket()) {
134             if (!StringUtils.isEmpty(this.hostname)) {
135                 logger.debug("Sending Wake-on-LAN Packet via IP Address");
136                 SocketAddress socketAddress = new InetSocketAddress(this.hostname,
137                         Objects.requireNonNullElse(this.port, WOL_UDP_PORT));
138                 sendMagicPacketToIp(magicPacket, socket, socketAddress);
139             } else {
140                 throw new IllegalStateException("Hostname is not set!");
141             }
142         } catch (SocketException e) {
143             logger.error("Failed to open Wake-on-LAN datagram socket", e);
144         }
145     }
146
147     private void broadcastMagicPacket(byte[] magicPacket, DatagramSocket socket) {
148         broadcastAddressStream().forEach(broadcastAddress -> {
149             try {
150                 DatagramPacket packet = new DatagramPacket(magicPacket, MAGIC_PACKET_BYTE_SIZE, broadcastAddress,
151                         WOL_UDP_PORT);
152                 socket.send(packet);
153                 logger.debug("Wake-on-LAN packet sent (MAC address: {}, broadcast address: {})", this.macAddress,
154                         broadcastAddress.getHostAddress());
155             } catch (IOException e) {
156                 logger.error("Failed to send Wake-on-LAN packet (MAC address: {}, broadcast address: {})",
157                         this.macAddress, broadcastAddress.getHostAddress(), e);
158             }
159         });
160         logger.info("Wake-on-LAN packets sent (MAC address: {})", this.macAddress);
161     }
162
163     private void sendMagicPacketToIp(byte[] magicPacket, DatagramSocket socket, SocketAddress ip) {
164         DatagramPacket packet = new DatagramPacket(magicPacket, MAGIC_PACKET_BYTE_SIZE, ip);
165         try {
166             socket.send(packet);
167         } catch (IOException e) {
168             logger.error("Failed to send Wake-on-LAN packet (IP address: {})", ip, e);
169         }
170         logger.info("Wake-on-LAN packets sent (IP address: {})", ip);
171     }
172
173     private Stream<InetAddress> broadcastAddressStream() {
174         return NetUtil.getAllBroadcastAddresses().stream().map(address -> {
175             try {
176                 return InetAddress.getByName(address);
177             } catch (UnknownHostException e) {
178                 logger.error("Failed to get broadcast address '{}' by name", address, e);
179                 return null;
180             }
181         }).filter(Objects::nonNull);
182     }
183 }