Files
DP44/Common/DTS.Common/Utils/PingUtils.cs
2026-04-17 14:55:32 -04:00

871 lines
36 KiB
C#

using DTS.Common.Interface.StatusAndProgressBar;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using DTS.Common.Utilities.Logging;
namespace DTS.Common.Utils
{
public class PingUtils
{
public static void EliminateBadHosts(ref List<HostInfo> list, string[] ips)
{
var hosts = list.ToArray();
foreach (var host in hosts)
{
if (!HostMatchesAnyIPs(host, ips))
{
APILogger.Log($"Removing host {host.HostIpAddress} as it can't reach any ips");
list.Remove(host);
}
}
}
/// <summary>
/// returns false if the host can not access any ips provided after looking at it's ip address and netmask
/// returns true if it can reach an ip or it can't be determined if it can reach an IP this might happen if someone used
/// a host name instead of a xxx.yyy.zzz.aaa style address or an empty string somewhere
/// </summary>
/// <param name="host"></param>
/// <param name="ips"></param>
/// <returns></returns>
protected static bool HostMatchesAnyIPs(HostInfo host, string[] ips)
{
if (ips.Length == 0) { return true; }
if (string.Empty.Equals(host.StartAddress) || string.Empty.Equals(host.EndAddress)) { return true; }
//FB 43919
List<bool> result = new List<bool>();
foreach (var ip in ips)
{
try
{
if (!IPAddress.TryParse(ip, out var ipToConnectTo))
{
//it's not a traditional format ip, give up trying to match it
return true;
}
if (!IPAddress.TryParse(host.StartAddress, out var startAddress))
{
//start address is not a traditional format, give up trying to match
return true;
}
if (!IPAddress.TryParse(host.EndAddress, out var endAddress))
{
//start address is not in traditional format, give up trying to match
return true;
}
var startBytes = startAddress.GetAddressBytes();
var endBytes = endAddress.GetAddressBytes();
var ipByte = ipToConnectTo.GetAddressBytes();
for (var i = 0; i < startBytes.Length && i < endBytes.Length && i < ipByte.Length; i++)
{
//FB 43919 If this condition is true as first iteration then break immediately and result collection should have only one false
//which means we need to remove this ip since it's not in the network
if (ipByte[i] < startBytes[i] || ipByte[i] > endBytes[i])
{
result.Add(false);
break;
}
else
{
result.Add(true);
}
}
//FB 43919 if any of the segment was true then don't remove the ip
return result.Exists(p => p);
}
catch (Exception ex)
{
APILogger.Log(ex);
}
}
//FB 43919
return false;
}
//FB 18152 & 25642 This dictionary keeps the host info for each DAS
public static ConcurrentDictionary<string, HostInfo> DasToHost { get; set; } = new ConcurrentDictionary<string, HostInfo>();
public class PingDevice
{
private const int MAX_PING_ATTEMPTS = 3;
private const string ALPHABET = "abcdefghijklmnopqrstuvwxyz";
#region Ping
public bool PingDevices(List<string> ipList)
{
//FB 18152 Get the avilable hosts
var hostInfos = NetworkUtils.GetAvailableHosts();
EliminateBadHosts(ref hostInfos, ipList?.ToArray());
return PingAllDevices(hostInfos, ipList);
}
#region Ping All Devices
/// <summary>
/// ICMP ping
/// </summary>
/// <param name="hostIpAddress">host IP address</param>
/// <param name="ipList">List of connected devices IP addresses </param>
/// <returns>false - if as least one device disconnected</returns>
private bool PingAllDevices(List<HostInfo> hostInfos, List<string> ipList)
{
var hostStringList = new List<string>();
foreach (var host in hostInfos)
{
hostStringList.Add(host.HostIpAddress);
}
APILogger.Log($"trying to ping {string.Join(", ", ipList.ToArray())} using hosts: {string.Join(", ", hostStringList.ToArray())}");
var allConnected = true;
Parallel.ForEach(ipList, (item, state) =>
{
if (hostInfos.Any())
{
var lastHost = hostInfos.Last();
//FB 18152 & 25642 Perform the ping on all the devices in all hosts
foreach (var hostInfo in hostInfos)
{
var eachConnected = PingOneDevice(item, hostInfo.HostIpAddress);
if (eachConnected)
{
//We found the host update the dictionary and break from the loop
DasToHost[item] = hostInfo;
allConnected = eachConnected;
APILogger.Log($"pinging {string.Join(", ", ipList.ToArray())} worked for {hostInfo.HostIpAddress}");
break;
}
if (hostInfo.Equals(lastHost))
{
//all the hosts are processed
allConnected = eachConnected;
}
}
if (!allConnected) state.Break();
}
});
if (allConnected) { APILogger.Log("An interface could ping"); }
else { APILogger.Log("no intefaces could ping"); }
return allConnected;
}
/// <summary>
/// TCP ping
/// </summary>
/// <param name="ipList">List of connected devices IP addresses </param>
/// <returns>false - if as least one device disconnected</returns>
private bool PingAllDevices(List<string> ipList)
{
var allConnected = true;
Parallel.ForEach(ipList, (item, state) =>
{
allConnected = PingOneDevice(item);
if (!allConnected) state.Break();
});
return allConnected;
}
#endregion Ping All Devices
#region Ping One Device
/// <summary>
/// TCP ping
/// </summary>
/// <param name="deviceIpAddress">device IP addresses </param>
/// <returns>false - if device disconnected</returns>
private bool PingOneDevice(string deviceIpAddress)
{
for (var i = 0; i < MAX_PING_ATTEMPTS; i++)
{
var pingSender = new Ping();
var reply = pingSender.Send(deviceIpAddress);
if (reply != null && reply.Status == IPStatus.Success) return true;
}
return false;
}
/// <summary>
/// ICMP ping
/// </summary>
/// <param name="deviceIp">device IP addresses </param>
/// <param name="hostIpAddress">host IP address</param>
/// <returns>false - if device disconnected</returns>
private bool PingOneDevice(string deviceIp, string hostIpAddress)
{
for (var i = 0; i < MAX_PING_ATTEMPTS; i++)
{
if (PingICMP(IPAddress.Parse(hostIpAddress), IPAddress.Parse(deviceIp), Constants.PING_ICMP_TIMEOUT, Encoding.ASCII.GetBytes(ALPHABET)).Status == IPStatus.Success)
return true;
}
return false;
}
#endregion Ping One Device
#region ICMP Ping
/// <summary>
/// ICMP ping
/// </summary>
/// <param name="srcAddress">host address</param>
/// <param name="destAddress">device address</param>
/// <param name="timeout">timeout</param>
/// <param name="buffer">Function will fail without buffer (used alphabet)</param>
/// <param name="po">Ping options - not been usexd</param>
/// <returns>PingReplyUtils class</returns>
private static PingReplyUtils PingICMP(IPAddress srcAddress, IPAddress destAddress, int timeout = 5000, byte[] buffer = null, PingOptions po = null)
{
var ipSrc = string.Empty;
var ipDest = string.Empty;
try
{
var ipSrcBytes = srcAddress?.GetAddressBytes() ?? new byte[0];
if (4 <= ipSrcBytes.Length) { ipSrc = $"{ipSrcBytes[0]}.{ipSrcBytes[1]}.{ipSrcBytes[2]}.{ipSrcBytes[3]}"; }
var destBytes = destAddress?.GetAddressBytes() ?? new byte[0];
if (4 <= destBytes.Length) { ipDest = $"{destBytes[0]}.{destBytes[1]}.{destBytes[2]}.{destBytes[3]}"; }
}
catch (Exception ex) { APILogger.Log(ex); }
if (destAddress == null || destAddress.AddressFamily != AddressFamily.InterNetwork ||
destAddress.Equals(IPAddress.Any))
{
APILogger.Log($"Ping -S {ipSrc} {ipDest} failed - Argument exception");
throw new ArgumentException(string.Format("PingAll failed on IP:{0}", srcAddress));
}
//Defining pinvoke args
var source = srcAddress == null ? 0 : BitConverter.ToUInt32(srcAddress.GetAddressBytes(), 0);
var destination = BitConverter.ToUInt32(destAddress.GetAddressBytes(), 0);
var sendbuffer = buffer ?? new byte[] { };
var options = new Interop.Option
{
Ttl = (po == null ? (byte)255 : (byte)po.Ttl),
Flags = (po == null ? (byte)0 : po.DontFragment ? (byte)0x02 : (byte)0) //0x02
};
var fullReplyBufferSize = Interop.ReplyMarshalLength + sendbuffer.Length;
//Size of Reply struct and the transmitted buffer length.
var allocSpace = Marshal.AllocHGlobal(fullReplyBufferSize);
// unmanaged allocation of reply size. TODO Maybe should be allocated on stack
try
{
var start = DateTime.Now;
var nativeCode = Interop.IcmpSendEcho2Ex(
/* _In_ HANDLE IcmpHandle, */
Interop.IcmpHandle,
/* _In_opt_ HANDLE Event, */
default(IntPtr),
/* _In_opt_ PIO_APC_ROUTINE ApcRoutine, */
default(IntPtr),
/* _In_opt_ PVOID ApcContext */
default(IntPtr),
/* _In_ IPAddr SourceAddress, */
source,
/* _In_ IPAddr DestinationAddress, */
destination,
/* _In_ LPVOID RequestData, */
sendbuffer,
/* _In_ WORD RequestSize, */
(short)sendbuffer.Length,
/* _In_opt_ PIP_OPTION_INFORMATION RequestOptions, */
ref options,
/* _Out_ LPVOID ReplyBuffer, */
allocSpace,
/* _In_ DWORD ReplySize, */
fullReplyBufferSize,
/* _In_ DWORD Timeout */
timeout);
var duration = DateTime.Now - start;
var reply = (Interop.Reply)Marshal.PtrToStructure(allocSpace, typeof(Interop.Reply));
// Parse the beginning of reply memory to reply struct
byte[] replyBuffer = null;
if (sendbuffer.Length != 0)
{
replyBuffer = new byte[sendbuffer.Length];
Marshal.Copy(allocSpace + Interop.ReplyMarshalLength, replyBuffer, 0, sendbuffer.Length);
//copy the rest of the reply memory to managed byte[]
}
var errorCode = 0;
if (0 != nativeCode)
{
errorCode = Marshal.GetLastWin32Error();
}
APILogger.Log($"Ping -S {ipSrc} {ipDest} result: {reply.Status} LastError: {errorCode}");
return nativeCode == 0
? new PingReplyUtils(nativeCode, reply.Status, new IPAddress(reply.Address), duration)
: new PingReplyUtils(nativeCode, reply.Status, new IPAddress(reply.Address), reply.RoundTripTime,
replyBuffer);
}
finally { Marshal.FreeHGlobal(allocSpace); /*free allocated space*/ }
}
#endregion ICMP Ping
#region Support ICMP Ping
/// <summary>Interoperability Helper
/// <see cref="http://msdn.microsoft.com/en-us/library/windows/desktop/bb309069(v=vs.85).aspx" />
/// </summary>
private static class Interop
{
private static IntPtr? _icmpHandle;
private static int? _replyStructLength;
/// <summary>Returns the application legal icmp handle. Should be close by IcmpCloseHandle
/// <see cref="http://msdn.microsoft.com/en-us/library/windows/desktop/aa366045(v=vs.85).aspx" />
/// </summary>
public static IntPtr IcmpHandle
{
get
{
if (_icmpHandle == null)
{
_icmpHandle = IcmpCreateFile();
//TODO Close Icmp Handle appropiate
}
return _icmpHandle.GetValueOrDefault();
}
}
/// <summary>Returns the the marshaled size of the reply struct.</summary>
public static int ReplyMarshalLength
{
get
{
if (_replyStructLength == null)
{
_replyStructLength = Marshal.SizeOf(typeof(Reply));
}
return _replyStructLength.GetValueOrDefault();
}
}
[DllImport("Iphlpapi.dll", SetLastError = true)]
private static extern IntPtr IcmpCreateFile();
[DllImport("Iphlpapi.dll", SetLastError = true)]
private static extern bool IcmpCloseHandle(IntPtr handle);
[DllImport("Iphlpapi.dll", SetLastError = true)]
public static extern uint IcmpSendEcho2Ex(IntPtr icmpHandle, IntPtr Event, IntPtr apcroutine,
IntPtr apccontext, uint sourceAddress, uint destinationAddress, byte[] requestData,
short requestSize, ref Option requestOptions, IntPtr replyBuffer, int replySize, int timeout);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct Option
{
public byte Ttl;
public readonly byte Tos;
public byte Flags;
public readonly byte OptionsSize;
public readonly IntPtr OptionsData;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct Reply
{
public readonly uint Address;
public readonly int Status;
public readonly int RoundTripTime;
public readonly short DataSize;
public readonly short Reserved;
public readonly IntPtr DataPtr;
public readonly Option Options;
}
}
#endregion Support ICMP Ping
#endregion Ping
private static readonly object LogLock = new object();
public static void PingLog(params string[] args)
{
var sb = new StringBuilder(200);
sb.AppendFormat("=== {0} ", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"));
foreach (var arg in args)
{
sb.Append(" ");
sb.Append(arg);
}
sb.Append(Environment.NewLine);
lock (LogLock)
{
try
{
File.AppendAllText("Logs/ping.log", sb.ToString());
}
catch (Exception ex)
{
Trace.WriteLine(ex);
}
}
}
#region OldPing
public void PingFunction(object o)
{
var td = o as PingThreadData;
if (td == null) return;
td.UpdateProgress(0D);
if (null == td.DoneEvent)
{
td.DoneEvent = new ManualResetEvent(false);
}
if (null == td.CancelEvent)
{
td.CancelEvent = new ManualResetEvent(false);
}
var sb = new StringBuilder();
sb.AppendFormat("PingFunction ");
foreach (var item in td.Entries)
{
sb.AppendFormat("{0}, ", item.IPAddress);
}
PingLog(sb.ToString());
if (0 == td.Entries.Length)
{
td.UpdateProgress(100D);
Thread.Sleep(100);
td.Complete();
return;
}
// Kick off the background thread while we monitor/simulate progress.
Task.Run(() => CheckOnline(td)).ConfigureAwait(false);
// FB 25642 Consider the number of hosts as well
var maxTime = Constants.PING_ICMP_TIMEOUT * td.Entries.Length * NetworkUtils.GetAvailableHosts().Count;
if (maxTime > 0 && maxTime < 1000)
{
maxTime = 1000; //minimum of 1 second
}
// Tick along progress as if it will take MAXPINGTIME. If we finish early, great.
var timeWaited = 0D;
while (!td.DoneEvent.WaitOne(150, false) && !td.CancelEvent.WaitOne(0, false))
{
timeWaited += 150D;
if (timeWaited >= maxTime)
{
PingLog("WARNING timewaited >= maxpingtime: ", timeWaited.ToString(), ">=", maxTime.ToString());
}
}
if (td.CancelEvent.WaitOne(0, false))
{
td.Complete();
}
//else if (timeWaited >= Maxpingtime)
//{
// PingLog("WARNING timewaited >= maxpingtime: ", timeWaited.ToString(), ">=",
// Maxpingtime.ToString());
// //td.CancelEvent.Set();
// //td.Complete();
// //td.DoneEvent.WaitOne();
// Thread.Sleep(100);
//}
else
{
td.UpdateProgress(100D);
Thread.Sleep(100);
td.Complete();
}
}
//used to hold the number of ips pinged completed while actively pinging
private volatile int _pingsDone = 0;
/// <summary>
/// Pinging device from specific IP address function will use IMC protocol, TC Protocol will be used if host IP address is empty
/// </summary>
/// <param name="o">A <see cref="PingThreadData"/> object</param>
private void CheckOnline(object o)
{
var td = o as PingThreadData;
if (td != null && null == td.CancelEvent)
{
td.CancelEvent = new ManualResetEvent(false);
}
try
{
var maxPingTime = Convert.ToInt32(Constants.PING_ICMP_TIMEOUT);
if (td == null) return;
var ipList = new List<string>();
foreach (var entry in td.Entries)
{
ipList.Add(entry.IPAddress);
}
var hostInfos = NetworkUtils.GetAvailableHosts();
EliminateBadHosts(ref hostInfos, ipList.ToArray());
if (!hostInfos.Any()) { return; }
var lastHost = hostInfos.Last();
var alphabet = Encoding.ASCII.GetBytes(ALPHABET);
var toPing = td.Entries.Length;
_pingsDone = 0;
_ = Parallel.ForEach(td.Entries, item =>
{
if (td.CancelEvent.WaitOne(0, false))
{
td.UpdateEntry(item, PingProgressStates.Cancel, 0);
return;
}
td.UpdateEntry(item, PingProgressStates.Pinging, 0);
var succeded = false;
var attempt = 0;
//FB 25642 Get available hosts
//avoid having to re-parse the ip address x thousand of times
var lookup = new Dictionary<HostInfo, IPAddress>();
foreach (var hostInfo in hostInfos)
{
lookup[hostInfo] = IPAddress.Parse(hostInfo.HostIpAddress);
}
while (!succeded && attempt < MAX_PING_ATTEMPTS && !td.CancelEvent.WaitOne(0, false))
{
if (td.CancelEvent.WaitOne(0, false))
{
td.UpdateEntry(item, PingProgressStates.Cancel, 0);
return;
}
try
{
PingReplyUtils allReply = null;
//FB 25642 we need to go through all the hosts
foreach (var hostInfo in hostInfos)
{
if (!IPAddress.TryParse(item.IPAddress, out var ip))
{
attempt = MAX_PING_ATTEMPTS;
continue;
}
//won't wpork if buffer is empty
var eachReply = PingICMP(lookup[hostInfo], ip, Constants.PING_ICMP_TIMEOUT, alphabet);
if ((eachReply.Status == IPStatus.Success) && (eachReply.NativeCode != 0))
{
//we found the host, update the dictionary & break
DasToHost[item.IPAddress] = hostInfo;
allReply = eachReply;
break;
}
if (hostInfo.Equals(lastHost))
{
//if it's the last host update the overall reply
allReply = eachReply;
}
}
//If the default gateway of the network interface isn't set appropriately, the call to PingICMP will
//return with a Status of IPStatus.Success. So, to differentiate this from a valid successful ping, we check NativeCode
if (allReply != null)
{
//FB 25642 allReply shows the ping was successfull or not
if ((allReply.Status == IPStatus.Success) && (allReply.NativeCode != 0))
{
succeded = true;
td.UpdateEntry(item, PingProgressStates.Ping_Good, allReply.RoundTripTime.Milliseconds);
PingLog("ping succeeded: ", DasToHost[item.IPAddress]?.HostIpAddress, " -> ", item.IPAddress);
}
else
{
attempt++;
}
}
else
{
PingLog("couldn't parse ip: ", item.IPAddress);
attempt++;
}
}
catch (Exception)
{
td.UpdateEntry(item, PingProgressStates.InvalidIPAddress, 0);
break;
}
}
if (succeded)
{
_pingsDone++;
td.UpdateProgress(100D * _pingsDone / toPing);
}
else if (MAX_PING_ATTEMPTS <= attempt)
{
_pingsDone++;
td.UpdateEntry(item, PingProgressStates.NoReply, 0);
td.UpdateProgress(100D * _pingsDone / toPing);
PingLog("failed (maxpings <= attempt: ", item.IPAddress, " - ", MAX_PING_ATTEMPTS.ToString(), "<=", attempt.ToString());
}
if (td.CancelEvent.WaitOne(0, false))
{
td.UpdateEntry(item, PingProgressStates.Cancel, 0);
}
});
}
catch (Exception ex)
{
APILogger.Log(ex);
throw;
}
finally
{
_ = td.DoneEvent.Set();
}
}
#endregion OldPing
}
}
#region PingReply
[Serializable]
public class PingReplyUtils
{
private Win32Exception _exception;
public PingReplyUtils()
{
}
internal PingReplyUtils(uint nativeCode, int replystatus, IPAddress ipAddress, TimeSpan duration)
{
NativeCode = nativeCode;
IpAddress = ipAddress;
if (Enum.IsDefined(typeof(IPStatus), replystatus))
{
Status = (IPStatus)replystatus;
}
}
internal PingReplyUtils(uint nativeCode, int replystatus, IPAddress ipAddress, int roundTripTime,
byte[] buffer)
{
NativeCode = nativeCode;
IpAddress = ipAddress;
RoundTripTime = TimeSpan.FromMilliseconds(roundTripTime);
Buffer = buffer;
if (Enum.IsDefined(typeof(IPStatus), replystatus))
{
Status = (IPStatus)replystatus;
}
}
/// <summary>Native result from <code>IcmpSendEcho2Ex</code>.</summary>
public uint NativeCode { get; } = 0;
public IPStatus Status { get; } = IPStatus.Unknown;
/// <summary>The source address of the reply.</summary>
public IPAddress IpAddress { get; } = null;
public byte[] Buffer { get; } = null;
public TimeSpan RoundTripTime { get; } = TimeSpan.Zero;
/// <summary>Resolves the <code>Win32Exception</code> from native code</summary>
public Win32Exception Exception
{
get
{
if (Status != IPStatus.Success)
{
return _exception ?? (_exception = new Win32Exception((int)NativeCode, Status.ToString()));
}
return null;
}
}
public override string ToString()
{
if (Status == IPStatus.Success)
{
return Status + " from " + IpAddress + " in " + RoundTripTime + " ms with " + Buffer.Length +
" bytes";
}
if (Status != IPStatus.Unknown)
{
return Status + " from " + IpAddress;
}
return Exception.Message + " from " + IpAddress;
}
}
#endregion PingReply
#region Entry
/// <summary>
/// Trivial class to represent an IP address entry, its type, and a visual representation
/// </summary>
public class Entry
{
public string IPAddress { get; set; }
public int DasType { get; set; }
public DataRow DR { get; set; }
public object UserData { get; set; }
}
#endregion Entry
#region HostInfo
//FB 25642 & 25590 & 18152 This class encapsulate information for the host in one place
public class HostInfo
{
public string HostIpAddress { get; set; } = string.Empty;
public string HostMacAddress { get; set; } = string.Empty;
public string HostNetworkId { get; set; } = string.Empty;
public string StartAddress { get; set; } = string.Empty;
public string EndAddress { get; set; } = string.Empty;
}
#endregion
#region PingProgressStates
/// <summary>
/// The possible states for a given <see cref="Entry"/>.
/// </summary>
public enum PingProgressStates
{
Pinging,
Connecting,
Querying,
Added,
Updated,
InvalidIPAddress,
NoReply,
Ping_Good,
Online,
Connected,
NoConnection,
Cancel,
Ready,
Armed,
QueryFailed,
QueryTimedOut,
QueryComplete,
UnexpectedMaxMemory,
UnexpectedFirmwareVersion,
Passed,
ExpiredCalDate,
ChannelCountMismatch,
MissingModules,
Canceled,
NoMemory,
AutoArmed,
Realtime,
ReadyToStream,
Streaming,
LostDock,
Rebooting,
UnexpectedFirstUseDate,
InvalidRecordingMode,
InvalidStreamingMode,
StreamingNotAvailable
}
#endregion PingProgressStates
#region PingThreadData
/// <summary>
/// A wrapper class to represent the progress and feedback for ping activity from background threads.
/// </summary>
public class PingThreadData
{
/// <summary>
/// <see cref="Entry"/> list to act on.
/// </summary>
public Entry[] Entries { get; set; }
/// <summary>
/// Optional <see cref="System.Threading.ManualResetEvent"/> to tell the process to cancel.
/// </summary>
public ManualResetEvent CancelEvent { get; set; }
/// <summary>
/// Optional <see cref="System.Threading.ManualResetEvent"/> to fire when the entire process is done.
/// </summary>
public ManualResetEvent DoneEvent { get; set; }
/// <summary>
/// An optional delgate to be called whenever status text has changed.
/// </summary>
public SetStatusTextDelegate setStatusTextCallback;
/// <summary>
/// An optional delgate to be called whenever progress has changed.
/// </summary>
public SetProgressValueDelegate progressCallback;
/// <summary>
/// A delegate type used for reporting progress on an individual entry
/// </summary>
/// <param name="entry"><see cref="Entry"/> that is being reported on</param>
/// <param name="state"><see cref="PingProgressStates"/> indicating the new state</param>
public delegate void EntryProgressCallback(Entry entry, PingProgressStates state, long msRoundTrip);
/// <summary>
/// An optional delegate to be called whenever an individual entry has changed.
/// </summary>
public EntryProgressCallback entryProgressCallback;
/// <summary>
/// An optional delegate to be called when the entire process is complete. Called before <seealso cref="DoneEvent"/> is fired.
/// </summary>
public ActionCompleteDelegate completeCallback;
/// <summary>
/// Wrapper for posting progress
/// </summary>
/// <param name="Percent">From 0 to 100 to indicate progress</param>
public void UpdateProgress(double Percent)
{
progressCallback?.Invoke(Percent);
}
/// <summary>
/// Wrapper for posting entry progress
/// </summary>
/// <param name="entry">The entry being updated</param>
/// <param name="state">The new state</param>
public void UpdateEntry(Entry entry, PingProgressStates state, long msResponseTime)
{
entryProgressCallback?.Invoke(entry, state, msResponseTime);
}
/// <summary>
/// Wrapper for posting complete
/// </summary>
public void Complete()
{
completeCallback?.Invoke();
DoneEvent?.Set();
}
public bool AvoidConnectPortion { get; set; } = false;
public bool PingFailed { get; set; } = false;
}
#endregion PingThreadData
}