1 package org.metricshub.ipmi.core.transport;
2
3 /*-
4 * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
5 * IPMI Java Client
6 * ჻჻჻჻჻჻
7 * Copyright 2023 Verax Systems, MetricsHub
8 * ჻჻჻჻჻჻
9 * This program is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU Lesser General Public License as
11 * published by the Free Software Foundation, either version 3 of the
12 * License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Lesser Public License for more details.
18 *
19 * You should have received a copy of the GNU General Lesser Public
20 * License along with this program. If not, see
21 * <http://www.gnu.org/licenses/lgpl-3.0.html>.
22 * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
23 */
24
25 import org.slf4j.Logger;
26 import org.slf4j.LoggerFactory;
27
28 import java.io.IOException;
29 import java.net.DatagramPacket;
30 import java.net.DatagramSocket;
31 import java.net.InetAddress;
32 import java.net.SocketException;
33 import java.net.UnknownHostException;
34 import java.util.ArrayList;
35 import java.util.List;
36
37 /**
38 * Handles the UDP connection.
39 */
40 public class UdpMessenger extends Thread implements Messenger {
41
42 private int port;
43
44 private DatagramSocket socket;
45
46 private List<UdpListener> listeners;
47
48 private boolean closing = false;
49
50 private static final String DEFAULT_ADDRESS = "0.0.0.0";
51
52 /**
53 * Size of the message data buffer. Default
54 * {@link UdpMessenger#DEFAULTBUFFERSIZE}.
55 */
56 private int bufferSize;
57
58 private static final int DEFAULTBUFFERSIZE = 512;
59
60 private static Logger logger = LoggerFactory.getLogger(UdpMessenger.class);
61
62 public int getPort() {
63 return port;
64 }
65
66 /**
67 * Initiates UdpMessenger, binds it to the specified port and starts
68 * listening. Wildcard IP address will be used.
69 *
70 * @param port
71 * - port to bind socket to.
72 * @throws SocketException
73 * if the socket could not be opened, or the socket could not
74 * bind to the specified local port.
75 * @throws UnknownHostException
76 */
77 public UdpMessenger(int port) throws SocketException, UnknownHostException {
78 this(port, InetAddress.getByName(DEFAULT_ADDRESS));
79 }
80
81 /**
82 * Initiates UdpMessenger, binds it to the specified port and IP address and
83 * starts listening.
84 *
85 * @param port
86 * - port to bind socket to.
87 * @param address
88 * - IP address to bind socket to.
89 * @throws SocketException
90 * if the socket could not be opened, or the socket could not
91 * bind to the specified local port.
92 */
93 public UdpMessenger(int port, InetAddress address) throws SocketException {
94 sentPackets = 0;
95 this.port = port;
96 listeners = new ArrayList<UdpListener>();
97 bufferSize = DEFAULTBUFFERSIZE;
98 socket = new DatagramSocket(this.port, address);
99 socket.setSoTimeout(0);
100 this.start();
101 }
102
103 /**
104 * Sets message data buffer size to bufferSize.
105 */
106 public void setBufferSize(int bufferSize) {
107 this.bufferSize = bufferSize;
108 }
109
110 /**
111 * @return Size of the message data buffer
112 */
113 public int getBufferSize() {
114 return bufferSize;
115 }
116
117 @Override
118 public void run() {
119 super.run();
120
121 boolean run = true;
122
123 while (run) {
124 DatagramPacket response = new DatagramPacket(new byte[512], 512);
125
126 try {
127 socket.receive(response);
128 UdpMessage message = new UdpMessage();
129 message.setAddress(response.getAddress());
130 message.setPort(response.getPort());
131 byte[] buffer = new byte[response.getLength()];
132 System.arraycopy(response.getData(), 0, buffer, 0,
133 buffer.length);
134 message.setMessage(buffer);
135
136 notifyListeners(message);
137
138 } catch (SocketException se) {
139 if (closing) {
140 run = false;
141 } else {
142 logger.error(se.getMessage(), se);
143 }
144 } catch (Exception e) {
145 logger.error(e.getMessage(), e);
146 } finally {
147 if (socket.isClosed()) {
148 run = false;
149 }
150 }
151 }
152 }
153
154 private void notifyListeners(UdpMessage message) {
155 synchronized (listeners) {
156 for (UdpListener listener : listeners) {
157 if (listener != null) {
158 listener.notifyMessage(message);
159 }
160 }
161 }
162 }
163
164 /**
165 * Closes the socket and releases port.
166 */
167 public void closeConnection() {
168 closing = true;
169 socket.close();
170 }
171
172 /**
173 * Registers listener in the UdpMessenger so it will be notified via
174 * {@link UdpListener#notifyMessage(UdpMessage)} when new message arrives.
175 *
176 * @param listener
177 * - {@link UdpListener} to register.
178 */
179 public void register(UdpListener listener) {
180 synchronized (listeners) {
181 listeners.add(listener);
182 }
183 }
184
185 /**
186 * Unregisters listener from UdpMessenger so it no longer will be notified.
187 *
188 * @param listener
189 * - {@link UdpListener} to unregister
190 */
191 public void unregister(UdpListener listener) {
192 synchronized (listeners) {
193 listeners.remove(listener);
194 }
195 }
196
197 private static int sentPackets = 0;
198
199 /**
200 * Returns number of packets sent since last creation of the instance of
201 * {@link UdpMessenger}. For debug/testing purposes only.
202 */
203 public static int getSentPackets() {
204 return sentPackets;
205 }
206
207 /**
208 * Sends {@link UdpMessage}.
209 *
210 * @param message
211 * - {@link UdpMessage} to send.
212 * @throws IOException
213 * when sending of the message fails
214 */
215 public synchronized void send(UdpMessage message) throws IOException {
216 DatagramPacket packet = new DatagramPacket(message.getMessage(),
217 message.getMessage().length, message.getAddress(),
218 message.getPort());
219 socket.send(packet);
220 try {
221 Thread.sleep(1);
222 } catch (InterruptedException e) {
223 // TODO: log
224 }
225 ++sentPackets;
226 }
227 }