/*
  This file is a part of Angry IP Scanner source code,
  see http://www.angryip.org/ for more information.
  Licensed under GPLv2.
 */
package net.azib.ipscan.core.net;

import net.azib.ipscan.config.LoggerFactory;
import net.azib.ipscan.config.ScannerConfig;
import net.azib.ipscan.core.ScanningSubject;

import java.io.IOException;
import java.net.*;
import java.nio.ByteBuffer;
import java.util.logging.Logger;

import static java.util.logging.Level.FINER;
import static net.azib.ipscan.util.IOUtils.closeQuietly;

/**
 * UDP Pinger. Uses a UDP port to ping, doesn't require root privileges.
 *
 * @author Anton Keks
 */
public class UDPPinger implements Pinger {
	public static final String ID = "pinger.udp";
	private static final Logger LOG = LoggerFactory.getLogger();

	private static final int PROBE_UDP_PORT = 33435;

	private int timeout;

	public UDPPinger(ScannerConfig config) {
		this.timeout = config.pingTimeout;
	}

	@Override public String getId() {
		return ID;
	}

	public PingResult ping(ScanningSubject subject, int count) throws IOException {
		var result = new PingResult(subject.getAddress(), count);

		DatagramSocket socket = null;
		try {
			socket = new DatagramSocket();
			socket.setSoTimeout(timeout);
			socket.connect(subject.getAddress(), PROBE_UDP_PORT);

			for (var i = 0; i < count && !Thread.currentThread().isInterrupted(); i++) {
				var payload = new byte[8];
				var startTime = System.currentTimeMillis();
				ByteBuffer.wrap(payload).putLong(startTime);
				var packet = new DatagramPacket(payload, payload.length);
				try {
					socket.send(packet);
					socket.receive(packet);
				}
				catch (PortUnreachableException e) {
					result.addReply(System.currentTimeMillis() - startTime);
				}
				catch (SocketTimeoutException ignore) {
				}
				catch (NoRouteToHostException e) {
					// this means that the host is down
					break;
				}
				catch (SocketException e) {
					if (e.getMessage().contains(/*No*/"route to host")) {
						// sometimes 'No route to host' also gets here...
						break;
					}
				}
				catch (IOException e) {
					if (e.getMessage().startsWith("Network is unreachable"))
						break;
					LOG.log(FINER, subject.toString(), e);
				}
			}
			return result;
		}
		finally {
			closeQuietly(socket);
		}
	}
}
