
org.bidib.wizard.discovery.service.SimpleNetBidibDeviceSearchService Maven / Gradle / Ivy
package org.bidib.wizard.discovery.service;
import java.io.ByteArrayOutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.apache.commons.collections4.CollectionUtils;
import org.bidib.jbidibc.core.MessageParser;
import org.bidib.jbidibc.core.PlainMessageParser;
import org.bidib.jbidibc.messages.BidibLibrary;
import org.bidib.jbidibc.messages.MessageProcessor;
import org.bidib.jbidibc.messages.exception.ProtocolException;
import org.bidib.jbidibc.messages.message.BidibMessageInterface;
import org.bidib.jbidibc.messages.message.BidibRequestFactory;
import org.bidib.jbidibc.messages.message.LocalAnnounceMessage;
import org.bidib.jbidibc.messages.message.LocalDiscoverMessage;
import org.bidib.jbidibc.messages.message.RequestFactory;
import org.bidib.jbidibc.messages.message.netbidib.LocalLinkMessage;
import org.bidib.jbidibc.messages.message.netbidib.LocalProtocolSignatureMessage;
import org.bidib.jbidibc.messages.utils.ByteUtils;
import org.bidib.jbidibc.messages.utils.MessageUtils;
import org.bidib.jbidibc.messages.utils.ThreadFactoryBuilder;
import org.bidib.wizard.common.event.WizardApplicationReadyEvent;
import org.bidib.wizard.common.model.settings.NetBidibSettingsInterface;
import org.bidib.wizard.common.utils.NetworkAddressHelper;
import org.bidib.wizard.core.model.settings.NetBidibSettings;
import org.bidib.wizard.core.utils.AopUtils;
import org.bidib.wizard.discovery.listener.SimpleNetBidibServiceListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.event.EventListener;
public class SimpleNetBidibDeviceSearchService implements NetBidibDeviceSearchService {
private static final Logger LOGGER = LoggerFactory.getLogger(SimpleNetBidibDeviceSearchService.class);
private static final Logger MSG_RX_LOGGER = LoggerFactory.getLogger("RX");
public static final String EMITTER_PREFIX_BIDIB = "BiDiB";
protected static final int NETBIDIB_PORT = 62875;
private DatagramSocket socket;
private final SimpleNetBidibServiceListener netBidibServiceListener;
private final ScheduledExecutorService serviceWorker =
Executors
.newScheduledThreadPool(1, new ThreadFactoryBuilder().setNameFormat("searchUdpWorkers-thread-%d").build());
private final ScheduledExecutorService searchWorker =
Executors
.newScheduledThreadPool(1,
new ThreadFactoryBuilder().setNameFormat("searchDeviceWorkers-thread-%d").build());
private final org.bidib.jbidibc.messages.logger.Logger splitMessageLogger;
private final RequestFactory requestFactory;
private final NetBidibSettingsInterface netBidibSettings;
private final Semaphore semaphore = new Semaphore(1);
private ScheduledFuture> registerFuture;
public SimpleNetBidibDeviceSearchService(final SimpleNetBidibServiceListener netBidibServiceListener,
final NetBidibSettingsInterface netBidibSettings) {
this.netBidibServiceListener = netBidibServiceListener;
this.netBidibSettings = netBidibSettings;
// create local request factory
this.requestFactory = new BidibRequestFactory();
this.requestFactory.initialize();
splitMessageLogger = new org.bidib.jbidibc.messages.logger.Logger() {
@Override
public void debug(String format, Object... arguments) {
LOGGER.debug(format, arguments);
}
@Override
public void info(String format, Object... arguments) {
LOGGER.info(format, arguments);
}
@Override
public void warn(String format, Object... arguments) {
LOGGER.warn(format, arguments);
}
@Override
public void error(String format, Object... arguments) {
LOGGER.error(format, arguments);
}
};
}
@EventListener
public void handleContextStart(final WizardApplicationReadyEvent ase) {
LOGGER.info("The application is ready.");
addSettingsListener();
if (this.netBidibSettings.isDiscoveryUdpEnabled()) {
triggerStartSearch();
}
else {
LOGGER.info("Start search the netBiDiB devices with UDP is disabled in settings.");
}
}
private void addSettingsListener() {
try {
final NetBidibSettings nbs = AopUtils.getTargetObject(netBidibSettings);
nbs.addPropertyChangeListener(NetBidibSettings.PROPERTY_DISCOVERY_UDP_ENABLED, evt -> {
boolean isDiscoveryUdpEnabled = netBidibSettings.isDiscoveryUdpEnabled();
LOGGER
.info("The discovery UDP enabled flag has changed, isDiscoveryUdpEnabled: {}",
isDiscoveryUdpEnabled);
if (isDiscoveryUdpEnabled) {
triggerStartSearch();
}
else {
// do noting here
}
});
}
catch (Exception ex) {
LOGGER.warn("Register for changes of netBidib settings failed.", ex);
}
}
private synchronized void triggerStartSearch() {
if (this.registerFuture != null) {
LOGGER.info("Register is in progress already.");
return;
}
LOGGER.info("Schedule the search for UDP netBiDiB devices after 5s.");
this.registerFuture = serviceWorker.schedule(() -> {
startSearch();
this.registerFuture = null;
}, 5, TimeUnit.SECONDS);
}
protected void startSearch() {
LOGGER
.info("Start the search for a simple netBiDiB device. Current simpleNetBidibServiceListener: {}",
netBidibServiceListener);
MulticastReceiver receiver = null;
Future> future = null;
try {
if (!semaphore.tryAcquire()) {
LOGGER.warn("The discovery search for UDP netBiDiB devices is already running.");
return;
}
final InetAddress sourceAddress = NetworkAddressHelper.getLocalAddressOfType(Inet4Address.class);
LOGGER.info("Current source address: {}", sourceAddress);
final List broadcastAddresses = getBroadcastAddresses();
if (CollectionUtils.isNotEmpty(broadcastAddresses)) {
try {
final byte[] broadcastMessage = getBroadcastMessge();
// must initialize the DatagramSocket with null, otherwise the later bind will fail
this.socket = new DatagramSocket(null);
this.socket.setBroadcast(true);
this.socket.setReuseAddress(true);
this.socket
.bind(new InetSocketAddress(sourceAddress, SimpleNetBidibDeviceSearchService.NETBIDIB_PORT));
receiver = new MulticastReceiver(this.socket, broadcastMessage, packet -> {
byte[] receivedData = ByteUtils.subArray(packet.getData(), 0, packet.getLength());
final Supplier contextKeyProvider = () -> {
return packet.getAddress().getHostAddress() + ":" + packet.getPort();
};
LOGGER.info("Received data: {}", ByteUtils.bytesToHex(receivedData));
// parse the discovery packet
final MessageParser messageParser = new PlainMessageParser();
final MessageProcessor messageProcessor = new MessageProcessor() {
private boolean checkCRC = false;
private AtomicBoolean isFirstPacket = new AtomicBoolean();
@Override
public void processMessages(ByteArrayOutputStream messageData) throws ProtocolException {
if (messageData.size() < 1) {
LOGGER.info("No data in provided buffer, skip process messages.");
return;
}
final String newContextKey = contextKeyProvider.get();
// if a CRC error is detected in splitMessages the reading loop will terminate ...
MessageUtils
.splitBidibMessages(splitMessageLogger, messageData, checkCRC, isFirstPacket,
message -> processMessage(message, newContextKey));
}
protected void processMessage(byte[] messageArray, final String contextKey)
throws ProtocolException {
BidibMessageInterface message = null;
try {
// create the bidib message from the provided message data
message = requestFactory.createConcreteMessage(messageArray);
if (MSG_RX_LOGGER.isInfoEnabled()) {
StringBuilder sb = new StringBuilder("<< ");
sb.append(message);
sb.append(" : ");
sb.append(ByteUtils.bytesToHex(messageArray));
MSG_RX_LOGGER.info(sb.toString());
}
// process the message
processMessage(message, contextKey);
}
catch (ProtocolException ex) {
LOGGER
.warn("Process received messages failed: {}",
ByteUtils.bytesToHex(messageArray), ex);
StringBuilder sb = new StringBuilder("<< received invalid: ");
sb.append(message);
sb.append(" : ");
sb.append(ByteUtils.bytesToHex(messageArray));
MSG_RX_LOGGER.warn(sb.toString());
throw ex;
}
catch (Exception ex) {
LOGGER
.warn("Process received messages failed: {}",
ByteUtils.bytesToHex(messageArray), ex);
}
}
protected void processMessage(final BidibMessageInterface message, final String contextKey)
throws ProtocolException {
LOGGER.info("Process the message: {}, contextKey: {}", message, contextKey);
switch (ByteUtils.getInt(message.getType())) {
case BidibLibrary.MSG_LOCAL_PROTOCOL_SIGNATURE:
// respond the protocol signature
LocalProtocolSignatureMessage localProtocolSignatureMessage =
(LocalProtocolSignatureMessage) message;
String requestor = localProtocolSignatureMessage.getRequestorName();
LOGGER
.info("Received MSG_LOCAL_PROTOCOL_SIGNATURE from requestor: {}",
requestor);
break;
case BidibLibrary.MSG_LOCAL_LINK:
LocalLinkMessage localLinkMessage = (LocalLinkMessage) message;
LOGGER.info("Received LocalLinkMessage.");
if (localLinkMessage
.getLinkDescriptor() == BidibLibrary.BIDIB_LINK_DESCRIPTOR_UID) {
long senderUniqueId = localLinkMessage.getSenderUniqueId();
LOGGER
.info(
"Received MSG_LOCAL_LINK with BIDIB_LINK_DESCRIPTOR_UID, senderUniqueId: {}",
ByteUtils.formatHexUniqueId(senderUniqueId));
netBidibServiceListener.signalUniqueId(senderUniqueId, contextKey);
}
break;
case BidibLibrary.MSG_LOCAL_ANNOUNCE:
LocalAnnounceMessage localAnnounceMessage = (LocalAnnounceMessage) message;
LOGGER
.info("Received MSG_LOCAL_ANNOUNCE, opCode: {}, tcpPort: {}",
localAnnounceMessage.getOpCode(), localAnnounceMessage.getTcpPort());
netBidibServiceListener.signalLocalAnnounce(localAnnounceMessage, contextKey);
break;
case BidibLibrary.MSG_LOCAL_DISCOVER:
LOGGER.info("Received MSG_LOCAL_DISCOVER.");
break;
default:
LOGGER.warn("Received unhandled : {}", message);
break;
}
}
};
try {
messageParser.parseInput(messageProcessor, receivedData, receivedData.length);
}
catch (Exception ex) {
LOGGER.warn("Parse received data and publish to message listeners failed.", ex);
}
});
future = searchWorker.submit(receiver);
Thread.sleep(20);
for (InetAddress broadcastAddress : broadcastAddresses) {
performSearch(broadcastAddress, sourceAddress, broadcastMessage);
}
future.get(5, TimeUnit.SECONDS);
receiver.stopReceiver();
future.cancel(true);
}
catch (TimeoutException ex) {
LOGGER.warn("Search the simple netBiDiB device timed out.");
}
catch (Exception ex) {
LOGGER.warn("Search the simple netBiDiB device failed.", ex);
}
finally {
if (receiver != null) {
LOGGER.info("The receiver is assigned. Stop the receiver.");
receiver.stopReceiver();
}
if (future != null) {
try {
LOGGER.info("Cancel the future.");
future.cancel(true);
}
catch (Exception ex) {
LOGGER.warn("Cancel future failed.", ex);
}
}
try {
LOGGER.info("Close the socket.");
this.socket.close();
}
catch (Exception ex) {
LOGGER.warn("Close socket failed.", ex);
}
}
}
else {
LOGGER.info("No broadcast addresses available.");
}
}
catch (Exception ex) {
LOGGER.warn("Search simple bidib devices failed.", ex);
}
finally {
LOGGER.info("Release the semaphore.");
this.semaphore.release();
}
}
protected static byte[] getBroadcastMessge() {
final byte[] broadcastMessage =
ByteUtils
.concat(new byte[] { 0x08, 0x00, 0x00, (byte) 0xFE },
ByteUtils
.concat(EMITTER_PREFIX_BIDIB.getBytes(StandardCharsets.UTF_8),
new LocalDiscoverMessage().getContent()));
return broadcastMessage;
}
private void performSearch(
final InetAddress broadcastAddress, final InetAddress sourceAddress, final byte[] broadcastMessage) {
LOGGER
.info("Perform search with broadcast address: {}, sourceAddress: {}, broadcastMessage: {}",
broadcastAddress, sourceAddress, ByteUtils.bytesToHex(broadcastMessage));
try {
LOGGER.info("Send the broadcastMessage from port: {}", this.socket.getLocalPort());
final DatagramPacket packet =
new DatagramPacket(broadcastMessage, broadcastMessage.length, broadcastAddress, NETBIDIB_PORT);
socket.send(packet);
}
catch (Exception ex) {
LOGGER.warn("Search the simple netBiDiB device failed.", ex);
}
}
@Override
public void triggerSearch() {
// TODO Auto-generated method stub
}
private static class MulticastReceiver extends Thread {
private final DatagramSocket socket;
private final byte[] buf = new byte[2048];
private final AtomicBoolean runReceiver = new AtomicBoolean();
private final byte[] broadcastMessage;
private final Consumer packetConsumer;
public MulticastReceiver(final DatagramSocket socket, final byte[] broadcastMessage,
final Consumer packetConsumer) {
this.socket = socket;
this.broadcastMessage = broadcastMessage;
this.packetConsumer = packetConsumer;
}
public void stopReceiver() {
LOGGER.info("Stop the receiver.");
this.runReceiver.set(false);
}
@Override
public void run() {
try {
runReceiver.set(true);
LOGGER.info("The receiver will wait for data.");
while (runReceiver.get()) {
DatagramPacket packet = new DatagramPacket(buf, buf.length);
try {
socket.receive(packet);
LOGGER
.info("Received paket: {}, address: {}, port: {}",
ByteUtils.bytesToHex(packet.getData(), packet.getLength()), packet.getAddress(),
packet.getPort());
if (packetConsumer != null) {
packetConsumer.accept(packet);
}
}
catch (SocketException ex) {
if (runReceiver.get()) {
LOGGER.warn("Receive data from broadcast socket failed with socket exception.", ex);
}
else {
LOGGER.info("Receive data from broadcast socket failed with socket exception during stop.");
}
}
catch (Exception ex) {
LOGGER.warn("Receive data from broadcast socket failed.", ex);
// TODO: handle exception
}
String received = new String(packet.getData(), 0, packet.getLength());
if ("end".equals(received)) {
break;
}
}
}
catch (Exception e) {
LOGGER.warn("Open receiver socket failed.", e);
}
LOGGER.info("The receiver has terminated.");
}
}
private List getBroadcastAddresses() throws SocketException, UnknownHostException {
final List broadcastAddresses = new ArrayList<>();
broadcastAddresses.add(InetAddress.getByName("255.255.255.255"));
broadcastAddresses.add(InetAddress.getByName("224.0.0.1"));
Enumeration interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
NetworkInterface networkInterface = interfaces.nextElement();
if (!NetworkAddressHelper.isUsableNetworkInterface(networkInterface, null)) {
continue; // Do not want to use the unusable interface.
}
for (InterfaceAddress interfaceAddress : networkInterface.getInterfaceAddresses()) {
InetAddress broadcast = interfaceAddress.getBroadcast();
if (broadcast == null) {
continue;
}
// Do something with the address.
LOGGER.info("Found broadcast address: {}", broadcast);
broadcastAddresses.add(broadcast);
}
}
return broadcastAddresses;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy