Commit ca5e6d7e7dbc023e7e8da3e33f6d7288e08c172a

Authored by Schwirg László
1 parent 82130b9f

v1.8.0

- Pinger beépítése a MaintenanceTool csoportba
Vrh.Log4Pro.MaintenanceConsole/ConsoleFunction - ColorConsole.cs
... ... @@ -148,6 +148,7 @@ namespace Vrh.Log4Pro.MaintenanceConsole.ColorConsoleNS
148 148 if (silentMode) { return; }
149 149 Console.SetCursorPosition(remembercursorleft, remembercursortop);
150 150 }
  151 + public static void SetCursorStartOfLine() { SetCursorPosition(0, ColorConsole.CursorTop); }
151 152  
152 153 public static int CursorLeft { get { return Console.CursorLeft; } }
153 154 public static int CursorTop { get { return Console.CursorTop; } }
... ...
Vrh.Log4Pro.MaintenanceConsole/ConsoleFunction - CommandLineParser.cs
... ... @@ -302,6 +302,7 @@ namespace Vrh.Log4Pro.MaintenanceConsole.CommandLineParserNS
302 302 public static class Functions
303 303 {
304 304 public static class RegexTester { public const string KEY = "RGX"; }
  305 + public static class TCPIPTester { public const string KEY = "TCP"; }
305 306 public static class Tool { public const string KEY = "TOL"; }
306 307 }
307 308 }
... ...
Vrh.Log4Pro.MaintenanceConsole/ConsoleFunction - Tools - TcpIp.cs 0 → 100644
... ... @@ -0,0 +1,957 @@
  1 +using System;
  2 +using System.Collections.Generic;
  3 +using System.Diagnostics;
  4 +using System.IO;
  5 +using System.Linq;
  6 +using System.Net;
  7 +using System.Net.NetworkInformation;
  8 +using System.Net.Sockets;
  9 +using System.Text;
  10 +using System.Threading.Tasks;
  11 +using System.Timers;
  12 +using System.Xml.Linq;
  13 +
  14 +namespace Vrh.Log4Pro.MaintenanceConsole.ToolsNS
  15 +{
  16 + /// <summary>
  17 + /// A pinger objektum ping ciklusokat indít (egy ping ciklusban több ping kérés van/lehet), és ezek eredményét
  18 + /// tárolja tömörített formátumban elsősorban abból a célból, hogy a tárolt adatok alapján trend diagramot lehessen
  19 + /// készíteni a kapcsolat minőségének kimutatására.
  20 + /// </summary>
  21 + public class Pinger : IDisposable
  22 + {
  23 + /// <summary>
  24 + /// Egy Pinger objektum létrehozása xml-ből
  25 + /// </summary>
  26 + /// <param name="hostNameOrAddress">a ping célállomása</param>
  27 + /// <param name="pingerconfig">a pinger konfigurációs paramétereit tartalmazó xml struktúra</param>
  28 + public Pinger(string hostNameOrAddress, XElement pingerconfig) : this(hostNameOrAddress, new PingerConfig(pingerconfig)) { }
  29 +
  30 + /// <summary>
  31 + /// Egy Pinger objektum létrehozása PrinterConfig osztályból
  32 + /// </summary>
  33 + /// <param name="hostNameOrAddress">a ping célállomása</param>
  34 + /// <param name="pc">a konfigurációt tartalmazó pinger objektum</param>
  35 + private Pinger(string hostNameOrAddress, PingerConfig pc)
  36 + {
  37 + this.HostNameOrAddress = hostNameOrAddress;
  38 + this.History = new PingHistory(hostNameOrAddress, pc.pingerhistorylength, pc.pingcyclefrequency);
  39 + this.PingTimeout = pc.pingtimeout;
  40 + this.PingCycleFrequency = pc.pingcyclefrequency;
  41 + this.PingCycleNumberOfPackages = pc.pingcyclenumofpackages;
  42 + this.PingerActiveLength = pc.pingeractivlength;
  43 + this.Configuration = pc;
  44 + }
  45 +
  46 + /// <summary>
  47 + /// A pinger indítása/újraindítása; elindítja a ping ciklus időzítőt, és elkezdi/folytatja a ping státusz adatok letárolását
  48 + /// </summary>
  49 + /// <param name="pingerconfig">pinger konfig xml struktúra, amely
  50 + /// (jellemzően újraindítás esetén) a Pinger részleges átkonfigurálását teszi lehetővé</param>
  51 + public void Start(XElement pingerconfig = null)
  52 + {
  53 + lock (pingcyclelocker)
  54 + {
  55 + CycleTimerStop();
  56 + Reconfigure(pingerconfig);
  57 + CycleTimerStart();
  58 +
  59 + if (this.PingerActiveLength > 0)
  60 + {
  61 + AutoStopTimerStop();
  62 + AutoStopTimerStart();
  63 + }
  64 + }
  65 + }
  66 +
  67 + /// <summary>
  68 + /// A pinger leállítása; kikapcsolja a ping ciklus időzítőt, és leállítja a ping státusz adatok letárolását
  69 + /// </summary>
  70 + public void Stop()
  71 + {
  72 + CycleTimerStop();
  73 + }
  74 +
  75 + /// <summary>
  76 + /// A Pinge objektum átkonfigurálás a a megadott xml struktúra alapján
  77 + /// </summary>
  78 + /// <param name="pingerconfig"></param>
  79 + private void Reconfigure(XElement pingerconfig = null)
  80 + {
  81 + lock (pingcyclelocker)
  82 + {
  83 + if (pingerconfig != null)
  84 + {
  85 + var pc = new PingerConfig(pingerconfig);
  86 + this.PingTimeout = pc.pingtimeout;
  87 + this.PingCycleFrequency = pc.pingcyclefrequency;
  88 + this.PingCycleNumberOfPackages = pc.pingcyclenumofpackages;
  89 + this.PingerActiveLength = pc.pingeractivlength;
  90 + this.History.SetHistoryLength(pc.pingerhistorylength);
  91 + }
  92 + }
  93 + }
  94 +
  95 + /// <summary>
  96 + /// A pinger autostop lejártakor végrehajtandó metódus
  97 + /// </summary>
  98 + /// <param name="source"></param>
  99 + /// <param name="e"></param>
  100 + private void AutoStop(Object source, ElapsedEventArgs e) { CycleTimerStop(); }
  101 +
  102 + private void CycleTimerStop()
  103 + {
  104 + lock (pingcyclelocker) { if (this.CycleTimer != null) { this.CycleTimer.Enabled = false; } this.Operating = false; }
  105 + }
  106 + private void CycleTimerStart()
  107 + {
  108 + lock (pingcyclelocker)
  109 + {
  110 + if (this.CycleTimer == null) { this.CycleTimer = new System.Timers.Timer(); this.CycleTimer.Elapsed += StartPingCycle; this.CycleTimer.AutoReset = true; }
  111 + this.CycleTimer.Interval = this.PingCycleFrequency;
  112 + this.CycleTimer.Enabled = true;
  113 + this.Operating = true;
  114 + }
  115 + }
  116 + private void AutoStopTimerStop()
  117 + {
  118 + lock (pingcyclelocker)
  119 + {
  120 + if (this.AutoStopTimer != null) { this.AutoStopTimer.Enabled = false; }
  121 + }
  122 + }
  123 + private void AutoStopTimerStart()
  124 + {
  125 + lock (pingcyclelocker)
  126 + {
  127 + if (this.AutoStopTimer == null) { this.AutoStopTimer = new System.Timers.Timer(); this.AutoStopTimer.Elapsed += AutoStop; this.AutoStopTimer.AutoReset = false; }
  128 + this.AutoStopTimer.Interval = this.PingerActiveLength;
  129 + this.AutoStopTimer.Enabled = true;
  130 + }
  131 + }
  132 + /// <summary>
  133 + /// Dispose
  134 + /// </summary>
  135 + public void Dispose()
  136 + {
  137 + Stop();
  138 + if (this.CycleTimer != null) { this.CycleTimer.Dispose(); this.CycleTimer = null; }
  139 + if (this.AutoStopTimer != null) { this.AutoStopTimer.Dispose(); this.AutoStopTimer = null; }
  140 + this.History.Dispose();
  141 + }
  142 +
  143 + /// <summary>
  144 + /// Visszadja a history listát
  145 + /// </summary>
  146 + /// <returns></returns>
  147 + public Pinger.PingHistory GetHistory()
  148 + {
  149 + lock (pingcyclelocker) { this.History.PingerState = this.CycleTimer != null && this.CycleTimer.Enabled ? PingerStatus.Operating : PingerStatus.Still; return this.History; }
  150 + }
  151 +
  152 + #region public fields
  153 + /// <summary>
  154 + /// A ping célállomása
  155 + /// </summary>
  156 + public string HostNameOrAddress;
  157 + /// <summary>
  158 + /// true: a pingcycle timer működik, a pingelés folyamataos
  159 + /// false: a pingcycle timer NEM működik, a pingelés áll
  160 + /// </summary>
  161 + public bool Operating;
  162 + /// <summary>
  163 + /// Egy ciklusban kiadott ping kérések száma.
  164 + /// </summary>
  165 + public PingerConfig Configuration;
  166 + #endregion public fields
  167 +
  168 + #region private metódusok
  169 + /// <summary>
  170 + /// A ping ciklus időzítés lejártakor elindított metódus, ami végrehajt egy ping ciklust és eredményét bedolgozza a PingStateQueue sorba
  171 + /// </summary>
  172 + /// <param name="source"></param>
  173 + /// <param name="e"></param>
  174 + private void StartPingCycle(Object source, ElapsedEventArgs e)
  175 + {
  176 + lock (pingcyclelocker)
  177 + {
  178 + this.CycleTimer.Enabled = false; // kikapcsoljuk a timer-t a ciklus végrehajtása alatt
  179 + var newpc = new PingCycle(this.HostNameOrAddress, this.PingCycleNumberOfPackages, this.PingTimeout).Execute();
  180 + this.History.Merge(newpc);
  181 + this.CycleTimer.Interval = this.PingCycleFrequency;// ha esetleg közben újrakonfigurálták....
  182 + this.CycleTimer.Enabled = true; // a ciklus végrehajtása után ismét elindítjuk az időzítőt
  183 + }
  184 + }
  185 + #endregion private metódusok
  186 +
  187 + #region private fields
  188 + /// <summary>
  189 + /// Az egyes ping kérések maximális timeout-ja (millisecundum)
  190 + /// </summary>
  191 + private int PingTimeout;
  192 + /// <summary>
  193 + /// A ping ciklus befejeződése után ennyi idővel indítja a következőt (másodpercben)
  194 + /// </summary>
  195 + private int PingCycleFrequency;
  196 + /// <summary>
  197 + /// A ping vizsgálat teljes időtartama; Ennyi idő eltelte után nem indít több több ping ciklust.
  198 + /// Érték percben. Ha az érték 0, akkor végtelen hosszú időn keresztül indít ping ciklusokat.
  199 + /// </summary>
  200 + private int PingerActiveLength;
  201 + /// <summary>
  202 + /// Egy ciklusban kiadott ping kérések száma.
  203 + /// </summary>
  204 + private int PingCycleNumberOfPackages;
  205 + /// <summary>
  206 + /// A ping ciklusok keveredését megakadályozó locker objektum
  207 + /// </summary>
  208 + private object pingcyclelocker = new object();
  209 + /// <summary>
  210 + /// A Pinger működési periódusának hossza (az autostop timer kikapcsolási ideje) (perc)
  211 + /// </summary>
  212 + private System.Timers.Timer AutoStopTimer;
  213 + /// <summary>
  214 + /// A Ping ciklus timere
  215 + /// </summary>
  216 + private System.Timers.Timer CycleTimer;
  217 + /// <summary>
  218 + /// Ping history
  219 + /// </summary>
  220 + private PingHistory History;
  221 + #endregion private fields
  222 +
  223 + #region PingerConfig class
  224 + /// <summary>
  225 + /// PingerConfig osztály
  226 + /// </summary>
  227 + public class PingerConfig
  228 + {
  229 + /// <summary>
  230 + /// példány létrehozása xml struktúra alapján
  231 + /// </summary>
  232 + /// <param name="pingerconfig"></param>
  233 + public PingerConfig(XElement pingerconfig)
  234 + {
  235 + if (pingerconfig != null)
  236 + {
  237 + var _pingtimeout = GetPositiveIntFromXml(PINGTIMEOUT, pingerconfig, DEFAULT_PINGTIMEOUT);
  238 + var _pingcyclefrequency = GetPositiveIntFromXml(PINGCYCLEFREQUENCY, pingerconfig, 0);
  239 + var _pingcyclenumofpackages = GetPositiveIntFromXml(PINGCYCLENUMOFPACKAGES, pingerconfig, DEFAULT_PINGCYCLENUMOFPACKAGES);
  240 + var _pingeractivlength = GetPositiveIntFromXml(PINGERACTIVELENGTHTOSTOPPERIOD, pingerconfig, DEFAULT_PINGERACTIVELENGTH);
  241 + var _pingerhistorylength = GetPositiveIntFromXml(PINGERHISTORYLENGTH, pingerconfig, DEFAULT_PINGERHISTORYLENGTH);
  242 + SetValues(_pingtimeout, _pingcyclefrequency, _pingcyclenumofpackages, _pingeractivlength, _pingerhistorylength);
  243 + }
  244 + else { SetValues(); }
  245 + }
  246 +
  247 + /// <summary>
  248 + /// Példány létrehozása paraméterek alapján; a konstruktor célja a mértékegységek és a default értékek kezelése
  249 + /// </summary>
  250 + /// <param name="pingtimeout">millisecond</param>
  251 + /// <param name="pingcyclefrequency">seconds</param>
  252 + /// <param name="pingcyclenumofpackages">pcs</param>
  253 + /// <param name="pingeractivelength">minutes</param>
  254 + /// <param name="pingerhistorylength">minutes</param>
  255 + public PingerConfig(int pingtimeout = 0, int pingcyclefrequency = 0, int pingcyclenumofpackages = 0, int pingeractivelength = 0, int pingerhistorylength = 0)
  256 + {
  257 + SetValues(pingtimeout, pingcyclefrequency, pingcyclenumofpackages, pingeractivelength, pingerhistorylength);
  258 + }
  259 +
  260 + /// <summary>
  261 + /// Beállítja a field-ek értékét a megfelelő mértékegységben a paraméterek, illetve az alapértelmezések szerint
  262 + /// </summary>
  263 + /// <param name="pingtimeout">millisecond</param>
  264 + /// <param name="pingcyclefrequency">seconds</param>
  265 + /// <param name="pingcyclenumofpackages">pcs</param>
  266 + /// <param name="pingeractivelength">minutes</param>
  267 + /// <param name="pingerhistorylength">minutes</param>
  268 + private void SetValues(int pingtimeout = 0, int pingcyclefrequency = 0, int pingcyclenumofpackages = 0, int pingeractivelength = 0, int pingerhistorylength = 0)
  269 + {
  270 + this.pingtimeout = pingtimeout <= 0 ? PingerConfig.DEFAULT_PINGTIMEOUT : pingtimeout;
  271 + this.pingcyclefrequency = pingcyclefrequency * 1000;
  272 + if (this.pingcyclefrequency < this.pingtimeout) { this.pingcyclefrequency = this.pingtimeout; }
  273 + this.pingcyclenumofpackages = pingcyclenumofpackages <= 0 ? PingerConfig.DEFAULT_PINGCYCLENUMOFPACKAGES : pingcyclenumofpackages;
  274 + this.pingeractivlength = (pingeractivelength <= 0 ? PingerConfig.DEFAULT_PINGERACTIVELENGTH : pingeractivelength) * 60 * 1000;
  275 + this.pingerhistorylength = (pingerhistorylength <= 0 ? PingerConfig.DEFAULT_PINGERHISTORYLENGTH : pingerhistorylength) * 60 * 1000;
  276 + }
  277 + /// <summary>
  278 + /// Egy int értéket kiemel a megadott xml struktúrából, ha nincs ott a keresett érték,
  279 + /// vagy nem integer, vagy nem pozitív, akkor az alapértelmezést adja vissza
  280 + /// </summary>
  281 + /// <param name="xname">az xml struktúrában a keresett elem neve</param>
  282 + /// <param name="pingerconfig">az xml struktúra</param>
  283 + /// <param name="defaultvalue">az alapértelmezett érték</param>
  284 + /// <returns></returns>
  285 + private static int GetPositiveIntFromXml(string xname, XElement pingerconfig, int defaultvalue)
  286 + {
  287 + string valstr = pingerconfig?.Element(XName.Get(xname))?.Value;
  288 + int val = defaultvalue;
  289 + if (valstr != null && int.TryParse(valstr, out int val1) && val1 > 0) { val = val1; }
  290 + return val;
  291 + }
  292 +
  293 + public int pingtimeout;
  294 + public int pingcyclefrequency;
  295 + public int pingcyclenumofpackages;
  296 + public int pingeractivlength;
  297 + public int pingerhistorylength;
  298 +
  299 + const string PINGTIMEOUT = "PingTimeout";
  300 + const string PINGCYCLEFREQUENCY = "PingCycleFrequency";
  301 + const string PINGCYCLENUMOFPACKAGES = "PingCycleNumOfPackages";
  302 + const string PINGERACTIVELENGTHTOSTOPPERIOD = "PingerActiveLength";
  303 + const string PINGERHISTORYLENGTH = "PingerHistoryLength";
  304 + public const int DEFAULT_PINGTIMEOUT = PingCycle.MAXROUNDTRIPTIMEOUTCATEGORY;
  305 + public const int DEFAULT_PINGCYCLENUMOFPACKAGES = 4;
  306 + public const int DEFAULT_PINGERACTIVELENGTH = 60;
  307 + public const int DEFAULT_PINGERHISTORYLENGTH = 60;
  308 + }
  309 + #endregion PingerConfig class
  310 +
  311 + #region PingHistory class
  312 + /// <summary>
  313 + /// Ping history struktúra
  314 + /// </summary>
  315 + public class PingHistory : IDisposable
  316 + {
  317 + /// <summary>
  318 + /// Ping állapot-sor. Minden eleme egy kezdő-időponttól fogva a következő elem kezdőidopontjáig fennálló állapotot ír le.
  319 + /// Ha az új státusz állapot-ban a RoundtripTimeCategory vagy StatusCategory értéke a sorban levő legutolsó elemben levő értékekhez képes
  320 + /// KÜLÖNBÖZIK, akkor Új elem kerül be a sorba;
  321 + /// AZONOS, akkor a legutolsó elemben a PackagesSent és PackagesLost értékekhez hozzáadódik az új elemben levő megfelelő érték, de új elem nem kerül hozzáadásra
  322 + /// </summary>
  323 + public List<PingCycle> PingStateQueue;
  324 + /// <summary>
  325 + /// A PingStateQueue utolsó eleme
  326 + /// </summary>
  327 + private PingCycle LastPingCycle;
  328 +
  329 + /// <summary>
  330 + /// A tárolt history hossza percben
  331 + /// </summary>
  332 + public int Length;
  333 +
  334 + /// <summary>
  335 + /// A ping ciklusok gyakorisága
  336 + /// </summary>
  337 + public int PingCycleFrequency;
  338 +
  339 + public string HostNameOrAddress;
  340 + public PingerStatus PingerState = PingerStatus.Still;
  341 + public DateTime StartTime;
  342 +
  343 + private object historylocker = new object();
  344 +
  345 + public PingHistory(string hostnameoraddress, int pingerhistorylength,int pingcyclefrequency)
  346 + {
  347 + this.HostNameOrAddress = hostnameoraddress;
  348 + this.PingStateQueue = new List<PingCycle>();
  349 + this.LastPingCycle = null;
  350 + this.Length = pingerhistorylength;
  351 + this.PingCycleFrequency = pingcyclefrequency;
  352 + }
  353 + public PingHistory(PingHistory ph)
  354 + {
  355 + this.HostNameOrAddress = ph.HostNameOrAddress;
  356 + this.PingStateQueue = ph.PingStateQueue.Select(x=>new PingCycle(x)).ToList();
  357 + this.LastPingCycle = ph.LastPingCycle;
  358 + this.Length = ph.Length;
  359 + this.PingCycleFrequency = ph.PingCycleFrequency;
  360 + }
  361 +
  362 + public void Dispose()
  363 + {
  364 + this.PingStateQueue = null;
  365 + this.LastPingCycle = null;
  366 + }
  367 + /// <summary>
  368 + /// A PrintCycle beillesztése a sor végére, vagy "hozzáadása" az utolsó elemhez
  369 + /// </summary>
  370 + /// <param name="newpc"></param>
  371 + public void Merge(PingCycle newpc)
  372 + {
  373 + lock (historylocker)
  374 + {
  375 + if (this.LastPingCycle?.isDifferent(newpc) ?? true) { this.PingStateQueue.Add(newpc); this.LastPingCycle = newpc; }
  376 + else
  377 + {
  378 + var totalrtt = ((this.LastPingCycle.RoundtripTimeAverage * this.LastPingCycle.NumberOfCycles) + newpc.RoundtripTimeAverage);
  379 + this.LastPingCycle.NumberOfCycles++;
  380 + this.LastPingCycle.RoundtripTimeAverage = totalrtt / this.LastPingCycle.NumberOfCycles;
  381 + this.LastPingCycle.PackagesSent += newpc.PackagesSent;
  382 + this.LastPingCycle.PackagesLost += newpc.PackagesLost;
  383 + this.LastPingCycle.LastTimestamp = newpc.StartTimestamp;
  384 + if (newpc.RoundtripTimeMax != -1 && this.LastPingCycle.RoundtripTimeMax != -1)
  385 + {
  386 + if (this.LastPingCycle.RoundtripTimeMax < newpc.RoundtripTimeMax) { this.LastPingCycle.RoundtripTimeMax = newpc.RoundtripTimeMax; }
  387 + if (this.LastPingCycle.RoundtripTimeMin > newpc.RoundtripTimeMin) { this.LastPingCycle.RoundtripTimeMin = newpc.RoundtripTimeMin; }
  388 + }
  389 + }
  390 +
  391 + var borderts = newpc.StartTimestamp.AddMilliseconds(-1 * this.Length);
  392 + this.PingStateQueue.RemoveAll(x => x.LastTimestamp <= borderts);
  393 + var firstelement = this.PingStateQueue.FirstOrDefault();
  394 + if (firstelement != null && firstelement.StartTimestamp < borderts)
  395 + {
  396 + var partiallength = (firstelement.LastTimestamp - borderts).TotalMilliseconds;
  397 + var fulllength = (firstelement.LastTimestamp - firstelement.StartTimestamp).TotalMilliseconds;
  398 + firstelement.PackagesSent = (int)((double)firstelement.PackagesSent * partiallength / fulllength);
  399 + firstelement.PackagesLost = (int)((double)firstelement.PackagesLost * partiallength / fulllength);
  400 + firstelement.NumberOfCycles = (int)((double)firstelement.NumberOfCycles * partiallength / fulllength);
  401 + if (firstelement.NumberOfCycles == 0 || firstelement.PackagesSent == 0) { this.PingStateQueue.Remove(firstelement); firstelement = this.PingStateQueue.FirstOrDefault(); }
  402 +
  403 + var packagelostratio = firstelement.PackagesLost / firstelement.PackagesSent;
  404 + firstelement.PackagesSentCategory =
  405 + packagelostratio == 0 ? PingCycle.LostPackageCategory.Excellent
  406 + : packagelostratio < 0.1 ? PingCycle.LostPackageCategory.Good
  407 + : packagelostratio < 0.3 ? PingCycle.LostPackageCategory.Acceptable
  408 + : PingCycle.LostPackageCategory.Bad;
  409 + firstelement.StartTimestamp = borderts;
  410 + }
  411 + StartTime = firstelement?.StartTimestamp ?? DateTime.MaxValue;
  412 + }
  413 + }
  414 +
  415 + /// <summary>
  416 + /// Beállítja a history hosszát
  417 + /// </summary>
  418 + /// <param name="pingerhistoryperiod"></param>
  419 + public void SetHistoryLength(int pingerhistoryperiod)
  420 + {
  421 + lock (historylocker) { this.Length = pingerhistoryperiod; }
  422 + }
  423 + }
  424 + #endregion PingHistory class
  425 +
  426 + #region PingCycle class
  427 + /// <summary>
  428 + /// A ping history-ban szereplő elemek típusa
  429 + /// </summary>
  430 + public class PingCycle
  431 + {
  432 + /// <summary>
  433 + /// Ping ciklus objektum létrehozása
  434 + /// </summary>
  435 + /// <param name="hostnameoraddress">a pingek célja</param>
  436 + /// <param name="numofpings">ennyi ping-et kell végrehajtani</param>
  437 + /// <param name="pingtimeout">ennyi az egyes ping-ek timeout-ja</param>
  438 + public PingCycle(string hostnameoraddress, int numofpings, int pingtimeout)
  439 + {
  440 + this.HostNameOrAddress = hostnameoraddress;
  441 + this.PackagesSent = numofpings;
  442 + this.PingTimeout = pingtimeout;
  443 + this.PackagesLost = 0;
  444 + this.PackagesSentCategory = LostPackageCategory.Excellent;
  445 + this.NumberOfCycles = 1;
  446 + this.StatusCategory = IPStatusCategory.NotExecuted;
  447 + this.RoundtripTimeAverage = 0;
  448 + this.RoundtripTimeMin = -1;
  449 + this.RoundtripTimeMax = -1;
  450 + }
  451 + public PingCycle(PingCycle pc)
  452 + {
  453 + this.HostNameOrAddress = pc.HostNameOrAddress;
  454 + this.PackagesSent = pc.PackagesSent;
  455 + this.PingTimeout = pc.PingTimeout;
  456 + this.PackagesLost = pc.PackagesLost;
  457 + this.PackagesSentCategory = pc.PackagesSentCategory;
  458 + this.NumberOfCycles = pc.NumberOfCycles;
  459 + this.StatusCategory = pc.StatusCategory;
  460 + this.RoundtripTimeAverage = pc.RoundtripTimeAverage;
  461 + this.RoundtripTimeMax = pc.RoundtripTimeMax;
  462 + this.RoundtripTimeMin= pc.RoundtripTimeMin;
  463 + }
  464 +
  465 + #region public fields,enums
  466 + /// <summary>
  467 + /// A maximális ping timeout kategória értéke
  468 + /// </summary>
  469 + public const int MAXROUNDTRIPTIMEOUTCATEGORY = 5000;
  470 + /// <summary>
  471 + /// A Ping állapot ezen időpillanattól kezdődően állt/áll fenn
  472 + /// </summary>
  473 + public DateTime StartTimestamp;
  474 + /// <summary>
  475 + /// A Ping állapot ezen időpillanattól kezdődően állt/áll fenn
  476 + /// </summary>
  477 + public DateTime LastTimestamp;
  478 + /// <summary>
  479 + /// Az állapot ilyen válaszidőt jelent (valójában ez is egy kategória, de egy konkrét idő érték jellemzi)
  480 + /// </summary>
  481 + public int RoundtripTime;
  482 +
  483 + /// <summary>
  484 + /// A ping ciklusban végrehajtott ping-ek maximális válaszideje
  485 + /// </summary>
  486 + public int RoundtripTimeMax=-1;
  487 + /// <summary>
  488 + /// A ping ciklusban végrehajtott ping-ek minimális válaszideje
  489 + /// </summary>
  490 + public int RoundtripTimeMin=-1;
  491 +
  492 + /// <summary>
  493 + /// A pontos válaszidők átlaga
  494 + /// </summary>
  495 + public int RoundtripTimeAverage;
  496 +
  497 + /// <summary>
  498 + /// Az állapot ilyen válaszidő kategóriát jelent
  499 + /// </summary>
  500 + public RoundTripTimeCategory RoundtripTimeCategory;
  501 + /// <summary>
  502 + /// Az állapot ilyen IPStatus kategóriát jelent
  503 + /// </summary>
  504 + public IPStatusCategory StatusCategory;
  505 + /// <summary>
  506 + /// Ebben a státus állapotban ennyi csomag került kiküldésre
  507 + /// </summary>
  508 + public int PackagesSent;
  509 + /// <summary>
  510 + /// Ebben a státus állapotban ennyi csomag veszett el nem Success
  511 + /// </summary>
  512 + public int PackagesLost;
  513 + /// <summary>
  514 + /// Ebben a státus állapotban az elveszett csomagokra utaló státusz
  515 + /// </summary>
  516 + public LostPackageCategory PackagesSentCategory;
  517 + /// <summary>
  518 + /// A ping ciklusok száma ebben az egységben
  519 + /// </summary>
  520 + public int NumberOfCycles;
  521 +
  522 + /// <summary>
  523 + /// Válasz státusz kategóriák; minél nagyobb az érték, annál kevesebbet tudunk a hiba okáról.
  524 + /// </summary>
  525 + public enum IPStatusCategory { Success = 0, TimedOut = 10, PortUnreachable = 20, HostUnreachable = 30, NetworkUnreachable = 40, Failed = 50, NotExecuted = 100, }
  526 + public enum LostPackageCategory { Excellent = 0, Good = 1, Acceptable = 2, Bad = 3, }
  527 + public enum RoundTripTimeCategory { Q1 = 0, Q2 = 1, Q3 = 2, Q4 = 3, }
  528 + #endregion public fields,enums
  529 +
  530 + #region isDifferent
  531 + /// <summary>
  532 + /// Eldönti, hogy a két objektum azonosnak tekinthető-e, vagy sem
  533 + /// </summary>
  534 + /// <param name="pc"></param>
  535 + /// <returns></returns>
  536 + public bool isDifferent(PingCycle pc)
  537 + {
  538 + return this.StatusCategory != pc.StatusCategory || this.RoundtripTime != pc.RoundtripTime;
  539 + }
  540 + #endregion isDifferent
  541 +
  542 + #region Execute
  543 + /// <summary>
  544 + /// Egy ping ciklus végrehajtása;
  545 + /// </summary>
  546 + /// <returns>
  547 + /// StatusCategory: értéke NotExecuted értéktől különbözni fog.
  548 + /// StartTimestamp: értéke a hívás időpillanata lesz.
  549 + /// PackagesLost: a nem Success-sel visszatérő ping-ek száma
  550 + /// RoundtripTimeCategory: a válaszidő kategória értéke
  551 + /// </returns>
  552 + public PingCycle Execute()
  553 + {
  554 + this.StartTimestamp = DateTime.Now;
  555 + this.LastTimestamp = this.StartTimestamp;
  556 + for (var i = 0; i < this.PackagesSent; i++)
  557 + {
  558 + var pr = PingTool.Ping(this.HostNameOrAddress, (int)PingTimeout);//egy ping kérés feladása
  559 + AddResponse(pr, i + 1);//a ping válasz "bedolgozása" a pingciklus-állapotba
  560 + }
  561 +
  562 + this.RoundtripTime = RoundTripTimeLimits.LastOrDefault(x => x <= this.RoundtripTimeAverage);
  563 + var RoundTripTimeyCategoryValue = RoundTripTimeyCategoryLimits.LastOrDefault(x => x < this.RoundtripTimeAverage);
  564 + var RoundTripTimeyCategoryIndex = RoundTripTimeyCategoryLimits.FindIndex(x => x == RoundTripTimeyCategoryValue);
  565 + this.RoundtripTimeCategory = RoundTripTimeyCategoryIndex == 0 ? RoundTripTimeCategory.Q1
  566 + : RoundTripTimeyCategoryIndex == 1 ? RoundTripTimeCategory.Q2
  567 + : RoundTripTimeyCategoryIndex == 2 ? RoundTripTimeCategory.Q3
  568 + : RoundTripTimeCategory.Q4;
  569 + return this;
  570 + }
  571 + #endregion Execute
  572 +
  573 + #region private elemek
  574 + /// <summary>
  575 + /// a ping célcíme
  576 + /// </summary>
  577 + private string HostNameOrAddress;
  578 + /// <summary>
  579 + /// A FINOM válaszidő kategóriákat tartalmazza; egy válasz az i. válaszidő kategóriába tartozik, ha a pontos válaszidő nagyobb, vagy egyenlő,
  580 + /// mint az i. elem értéke, de kisebb, mint az i+1. elem értéke.A sor első elemének értéke mindig 0!!!!
  581 + /// Ha a sor értékei: 0:0,1:100,2:200,3:300,4:400,5:500, a pontos válaszidő 350, akkor ez a 2. kategóriába tartozik, ha pontos válaszidő.
  582 + /// </summary>
  583 + private static List<int> RoundTripTimeLimits = new List<int> { 0, 25, 50, 75, 100, 150, 200, 250, 300, 400, 500, 750, 1000, 1250, 1500, 1750, 2000, 2500, 3000, 3500, 4000, 4500, PingCycle.MAXROUNDTRIPTIMEOUTCATEGORY, };
  584 +
  585 + /// <summary>
  586 + /// A DURVA válaszidő kategóriákat tartalmazza; egy válasz az i. válaszidő kategóriába tartozik, ha a pontos válaszidő nagyobb, vagy egyenlő,
  587 + /// mint az i. elem értéke, de kisebb, mint az i+1. elem értéke.A sor első elemének értéke mindig 0!!!!
  588 + /// Ha a sor értékei: 0:0,1:100,2:200,3:300,4:400,5:500, a pontos válaszidő 350, akkor ez a 2. kategóriába tartozik, ha pontos válaszidő.
  589 + /// </summary>
  590 + private static List<int> RoundTripTimeyCategoryLimits = new List<int> { 0, 50, 250, 750, };
  591 + /// <summary>
  592 + /// Az egyes ping kérések timeout-ja
  593 + /// </summary>
  594 + private int PingTimeout;
  595 + /// <summary>
  596 + /// A ping válasz bedolgozása a pingciklus státuszba
  597 + /// </summary>
  598 + /// <param name="pr">a ping válasza</param>
  599 + /// <param name="i">a ping kérés indexe; 1,2,....</param>
  600 + private void AddResponse(PingReply pr, int i)
  601 + {
  602 + var ipsCat = GetIPStatusCategory(pr);
  603 + if (ipsCat==IPStatusCategory.Success)
  604 + {
  605 + int prroundtriptime = pr.RoundtripTime >= int.MaxValue ? int.MaxValue : (int)pr.RoundtripTime;
  606 + this.RoundtripTimeMin = prroundtriptime < this.RoundtripTimeMin || this.RoundtripTimeMin ==-1 ? prroundtriptime : this.RoundtripTimeMin;
  607 + this.RoundtripTimeMax = prroundtriptime > this.RoundtripTimeMax || this.RoundtripTimeMax == -1 ? prroundtriptime : this.RoundtripTimeMax;
  608 + this.RoundtripTimeAverage = ((i - 1) * this.RoundtripTimeAverage + prroundtriptime) / i;// az átlagos válaszidőt tárolja
  609 + }
  610 + else { this.PackagesLost++; }
  611 + if (ipsCat < this.StatusCategory) { this.StatusCategory = ipsCat; }// a legjobb státuszt tárolja
  612 + }
  613 + /// <summary>
  614 + /// Visszaadja, hogy a PingReply adat alapján a válasz melyik státusz kategóriába tartozik
  615 + /// </summary>
  616 + /// <param name="pr"></param>
  617 + /// <returns></returns>
  618 + private IPStatusCategory GetIPStatusCategory(PingReply pr)
  619 + {
  620 + switch (pr.Status)
  621 + {
  622 + case IPStatus.Success: return IPStatusCategory.Success;//Success = 0,The ICMP echo request succeeded; an ICMP echo reply was received. When you get this status code, the other System.Net.NetworkInformation.PingReply properties contain valid data.
  623 +
  624 + case IPStatus.DestinationNetworkUnreachable: //DestinationNetworkUnreachable = 11002,The ICMP echo request failed because the network that contains the destination computer is not reachable.
  625 + return IPStatusCategory.NetworkUnreachable;
  626 +
  627 + case IPStatus.BadRoute: //BadRoute = 11012,The ICMP echo request failed because there is no valid route between the source and destination computers.
  628 + case IPStatus.DestinationUnreachable: //DestinationUnreachable = 11040,The ICMP echo request failed because the destination computer that is specified in an ICMP echo message is not reachable; the exact cause of problem is unknown.
  629 + case IPStatus.DestinationHostUnreachable: //DestinationHostUnreachable = 11003,The ICMP echo request failed because the destination computer is not reachable.
  630 + return IPStatusCategory.HostUnreachable;
  631 +
  632 + case IPStatus.TtlReassemblyTimeExceeded://TtlReassemblyTimeExceeded = 11014,The ICMP echo request failed because the packet was divided into fragments for transmission and all of the fragments were not received within the time allottedfor reassembly. RFC 2460 (available at www.ietf.org) specifies 60 seconds as the time limit within which all packet fragments must be received.
  633 + case IPStatus.TtlExpired://TtlExpired = 11013,The ICMP echo request failed because its Time to Live (TTL) value reached zero, causing the forwarding node (router or gateway) to discard the packet.
  634 + case IPStatus.TimeExceeded: //TimeExceeded = 11041,The ICMP echo request failed because its Time to Live (TTL) value reached zero, causing the forwarding node (router or gateway) to discard the packet.
  635 + case IPStatus.TimedOut: //TimedOut = 11010,The ICMP echo Reply was not received within the allotted time. The default time allowed for replies is 5 seconds. You can change this value using theOverload:System.Net.NetworkInformation.Ping.Send or Overload:System.Net.NetworkInformation.Ping.SendAsync methods that take a timeout parameter.
  636 + return IPStatusCategory.TimedOut;
  637 +
  638 + //DestinationProhibited = 11004,The ICMPv6 echo request failed because contact with the destination computer is administratively prohibited. This value applies only to IPv6.
  639 + case IPStatus.DestinationProtocolUnreachable://DestinationProtocolUnreachable = 11004, The ICMP echo request failed because the destination computer that is specified in an ICMP echo message is not reachable, because it does not support the packet'sprotocol. This value applies only to IPv4. This value is described in IETF RFC 1812 as Communication Administratively Prohibited.
  640 + case IPStatus.DestinationPortUnreachable: //DestinationPortUnreachable = 11005,Summary: The ICMP echo request failed because the port on the destination computer is not available.
  641 + case IPStatus.Unknown://Unknown = -1,The ICMP echo request failed for an unknown reason.
  642 + case IPStatus.NoResources: //NoResources = 11006,Summary: The ICMP echo request failed because of insufficient network resources.
  643 + case IPStatus.BadOption: //BadOption = 11007,The ICMP echo request failed because it contains an invalid option.
  644 + case IPStatus.HardwareError: //HardwareError = 11008,The ICMP echo request failed because of a hardware error.
  645 + case IPStatus.PacketTooBig: //PacketTooBig = 11009,The ICMP echo request failed because the packet containing the request is larger than the maximum transmission unit (MTU) of a node (router or gateway) located between the source and destination. The MTU defines the maximum size of a transmittable packet.
  646 + case IPStatus.ParameterProblem: //ParameterProblem = 11015,The ICMP echo request failed because a node (router or gateway) encountered problems while processing the packet header. This is the status if, for example, the header contains invalid field data or an unrecognized option.
  647 + case IPStatus.SourceQuench: //SourceQuench = 11016,The ICMP echo request failed because the packet was discarded. This occurs when the source computer's output queue has insufficient storage space, or when packetsarrive at the destination too quickly to be processed.
  648 + case IPStatus.BadDestination: //BadDestination = 11018,The ICMP echo request failed because the destination IP address cannot receive ICMP echo requests or should never appear in the destination address field ofany IP datagram. For example, calling Overload:System.Net.NetworkInformation.Ping.Send and specifying IP address "000.0.0.0" returns this status.
  649 + case IPStatus.BadHeader: //BadHeader = 11042,The ICMP echo request failed because the header is invalid.
  650 + case IPStatus.UnrecognizedNextHeader://UnrecognizedNextHeader = 11043,The ICMP echo request failed because the Next Header field does not contain a recognized value. The Next Header field indicates the extension header type (ifpresent) or the protocol above the IP layer, for example, TCP or UDP.
  651 + case IPStatus.IcmpError: //IcmpError = 11044,The ICMP echo request failed because of an ICMP protocol error.
  652 + case IPStatus.DestinationScopeMismatch://DestinationScopeMismatch = 11045, The ICMP echo request failed because the source address and destination address that are specified in an ICMP echo message are not in the same scope. This istypically caused by a router forwarding a packet using an interface that is outside the scope of the source address. Address scopes (link-local, site-local, andglobal scope) determine where on the network an address is valid. }
  653 + return IPStatusCategory.Failed;
  654 + }
  655 + return IPStatusCategory.Failed;
  656 + }
  657 + #endregion private elemek
  658 + }
  659 + #endregion PingCycle class
  660 +
  661 + public enum PingerStatus { NotExisting = 2, Still = 1, Operating = 0, }
  662 + }
  663 +
  664 + /// <summary>
  665 + /// TCP Ping eszközök
  666 + /// </summary>
  667 + public static class PingTool
  668 + {
  669 + /// <summary>
  670 + /// Elérhető e az állomás?
  671 + /// </summary>
  672 + /// <param name="hostNameOrAddress">Ip, vagy host név</param>
  673 + /// <param name="timeOut">timeout</param>
  674 + /// <returns></returns>
  675 + public static bool IsHostAccessible(string hostNameOrAddress, int timeOut = 1000)
  676 + {
  677 + PingReply reply = Ping(hostNameOrAddress, timeOut);
  678 + return reply.Status == IPStatus.Success;
  679 + }
  680 +
  681 + /// <summary>
  682 + /// Visszadja az állomás ping idejét (millisecundum)
  683 + /// </summary>
  684 + /// <param name="hostNameOrAddress">Ip, vagy host név</param>
  685 + /// <param name="timeOut">timeout</param>
  686 + /// <returns></returns>
  687 + public static long GetPingTime(string hostNameOrAddress, int timeOut = 1000)
  688 + {
  689 + PingReply reply = Ping(hostNameOrAddress, timeOut);
  690 + return reply.RoundtripTime;
  691 + }
  692 +
  693 + /// <summary>
  694 + /// Visszadja az állomás ping szerinti státuszát (IPStatus)
  695 + /// </summary>
  696 + /// <param name="hostNameOrAddress">Ip, vagy host név</param>
  697 + /// <param name="timeOut">timeout</param>
  698 + ///
  699 +
  700 + /// <returns></returns>
  701 + public static IPStatus GetPingStatus(string hostNameOrAddress, int timeOut = 1000)
  702 + {
  703 + PingReply reply = Ping(hostNameOrAddress, timeOut);
  704 + return reply.Status;
  705 + }
  706 +
  707 + /// <summary>
  708 + /// Ping
  709 + /// </summary>
  710 + /// <param name="hostNameOrAddress">Ip, vagy host név</param>
  711 + /// <param name="timeOut">timeout</param>
  712 + /// <returns>PingReply</returns>
  713 + public static PingReply Ping(string hostNameOrAddress, int timeOut = 1000)
  714 + {
  715 + Ping ping = new Ping();
  716 + return ping.Send(hostNameOrAddress, timeOut);
  717 + }
  718 +
  719 + /// <summary>
  720 + /// Ping specifikált adatokkal
  721 + /// </summary>
  722 + /// <param name="hostNameOrAddress">Ip, vagy host név</param>
  723 + /// <param name="timeOut">timeout</param>
  724 + /// <param name="buffer">ping adatok</param>
  725 + /// <returns>PingReply</returns>
  726 + public static PingReply Ping(string hostNameOrAddress, int timeOut, byte[] buffer)
  727 + {
  728 + if (buffer.Length > 65500)
  729 + {
  730 + throw new ArgumentException("Ping data too big! Maximum 65500 bytes allowed!");
  731 + }
  732 + Ping ping = new Ping();
  733 + return ping.Send(hostNameOrAddress, timeOut, buffer);
  734 + }
  735 +
  736 + /// <summary>
  737 + /// Ping specifikált adatokkal és beállításokkal (PingOptions)
  738 + /// </summary>
  739 + /// <param name="hostNameOrAddress">Ip, vagy host név</param>
  740 + /// <param name="timeOut">timeout</param>
  741 + /// <param name="buffer">ping adatok</param>
  742 + /// <param name="options">ping beállítások</param>
  743 + /// <returns>PingReply</returns>
  744 + public static PingReply Ping(string hostNameOrAddress, int timeOut, byte[] buffer, PingOptions options)
  745 + {
  746 + if (buffer.Length > 65500)
  747 + {
  748 + throw new ArgumentException("Ping data too big! Maximum 65500 bytes allowed!");
  749 + }
  750 + Ping ping = new Ping();
  751 + return ping.Send(hostNameOrAddress, timeOut, buffer, options);
  752 + }
  753 + }
  754 + /// <summary>
  755 + /// IPv4 kezeléssel kapcsolatos eszközök
  756 + /// </summary>
  757 + public static class IPv4Tool
  758 + {
  759 + /// <summary>
  760 + /// Visszad egy IPAddress osztályt, amit a megadott byte értékekből épít fel
  761 + /// </summary>
  762 + /// <param name="ip"></param>
  763 + /// <returns></returns>
  764 + public static IPAddress BuildIPAddress(params byte[] ip)
  765 + {
  766 + if (ip.Length != 4)
  767 + {
  768 + throw new ArgumentException("Invalid IP! Specific 4 byte value for valid IP address!");
  769 + }
  770 + return new IPAddress(ip);
  771 + }
  772 +
  773 + public static IPAddress BuildIPAddress(string ipOrHostname)
  774 + {
  775 + IPAddress ip;
  776 +
  777 + if (IPAddress.TryParse(ipOrHostname, out ip))
  778 + {
  779 + // IP
  780 + return ip;
  781 + }
  782 + else
  783 + {
  784 + // Hostname
  785 + try
  786 + {
  787 + IPHostEntry hostEntry;
  788 + hostEntry = Dns.GetHostEntry(ipOrHostname);
  789 + foreach (var address in hostEntry.AddressList)
  790 + {
  791 + if (address.AddressFamily == AddressFamily.InterNetwork)
  792 + {
  793 + return address;
  794 + }
  795 + }
  796 + throw new ArgumentException(String.Format("Bad input argument! (Argument is not Ip or DNS resorvable host name.)", ipOrHostname));
  797 + }
  798 + catch (Exception ex)
  799 + {
  800 + throw new ArgumentException(String.Format("Bad input argument! (Argument is not Ip or DNS resorvable host name.)", ipOrHostname), ex);
  801 + }
  802 + }
  803 + }
  804 + }
  805 +
  806 + /// <summary>
  807 + /// MAC address lekérdezése IP alapján
  808 + /// </summary>
  809 + public static class MACTool
  810 + {
  811 + /// <summary>
  812 + /// Gets the MAC address (<see cref="PhysicalAddress"/>) associated with the specified IP.
  813 + /// </summary>
  814 + /// <param name="ipAddress">The remote IP address.</param>
  815 + /// <returns>The remote machine's MAC address.</returns>
  816 + public static PhysicalAddress GetMacAddress(IPAddress ipAddress)
  817 + {
  818 + const int MacAddressLength = 6;
  819 + int length = MacAddressLength;
  820 + var macBytes = new byte[MacAddressLength];
  821 + SendARP(BitConverter.ToInt32(ipAddress.GetAddressBytes(), 0), 0, macBytes, ref length);
  822 + return new PhysicalAddress(macBytes);
  823 + }
  824 +
  825 + public static PhysicalAddress GetMacAddress(string ipAddressOrHostName)
  826 + {
  827 + return MACTool.GetMacAddress(IPv4Tool.BuildIPAddress(ipAddressOrHostName));
  828 + }
  829 +
  830 + public static PhysicalAddress GetMacAddress(params byte[] ipAddress)
  831 + {
  832 + return MACTool.GetMacAddress(IPv4Tool.BuildIPAddress(ipAddress));
  833 + }
  834 +
  835 + public static string ToString(this PhysicalAddress mac, char byteSeparator)
  836 + {
  837 + return HexString.ByteArrayToHexViaLookup32(mac.GetAddressBytes(), byteSeparator);
  838 + }
  839 +
  840 + /// <summary>
  841 + /// Netbiostól kér a mac címet, csak netbiost megvalósító eszközökre működik!!!
  842 + /// Elég lassú...
  843 + /// </summary>
  844 + /// <param name="ipAddress"></param>
  845 + /// <returns></returns>
  846 + public static string GetMacAddressFromNetBios(string ipAddress)
  847 + {
  848 + string macAddress = string.Empty;
  849 +
  850 + if (!PingTool.IsHostAccessible(ipAddress))
  851 + {
  852 + return String.Empty;
  853 + }
  854 + try
  855 + {
  856 + ProcessStartInfo processStartInfo = new ProcessStartInfo();
  857 + Process process = new Process();
  858 + if (Environment.Is64BitOperatingSystem)
  859 + {
  860 + string filePath = Environment.GetFolderPath(Environment.SpecialFolder.Windows) + "\\sysnative";
  861 + processStartInfo.FileName = Path.Combine(filePath, "nbtstat");
  862 + }
  863 + else
  864 + {
  865 + processStartInfo.FileName = "nbtstat";
  866 + }
  867 + processStartInfo.RedirectStandardInput = false;
  868 + processStartInfo.RedirectStandardOutput = true;
  869 + processStartInfo.Arguments = "-a " + ipAddress;
  870 + processStartInfo.UseShellExecute = false;
  871 + process = Process.Start(processStartInfo);
  872 + string line = null;
  873 + do
  874 + {
  875 + line = macAddress = process.StandardOutput.ReadLine();
  876 + if (line != null)
  877 + {
  878 + if (macAddress.Trim().ToLower().IndexOf("mac address", 0) > -1)
  879 + {
  880 + macAddress = line;
  881 + break;
  882 + }
  883 + }
  884 + } while (line != null);
  885 + process.WaitForExit();
  886 + if (macAddress.IndexOf('=') > -1)
  887 + {
  888 + macAddress = macAddress.Substring(macAddress.IndexOf('=') + 1);
  889 + }
  890 + macAddress = macAddress.Trim();
  891 + }
  892 + catch (Exception e)
  893 + {
  894 + // Something unexpected happened? Inform the user
  895 + // The possibilities are:
  896 + // 1.That the machine is not on the network currently
  897 + // 2. The IP address/hostname supplied are not on the same network
  898 + // 3. The host was not found on the same subnet or could not be
  899 + // resolved
  900 + }
  901 + return macAddress;
  902 + }
  903 +
  904 + /// <summary>
  905 + /// ARP query kiküldése
  906 + /// </summary>
  907 + /// <param name="DestIP"></param>
  908 + /// <param name="SrcIP"></param>
  909 + /// <param name="pMacAddr"></param>
  910 + /// <param name="PhyAddrLen"></param>
  911 + /// <returns></returns>
  912 + // http://www.codeproject.com/KB/IP/host_info_within_network.aspx
  913 + [System.Runtime.InteropServices.DllImport("iphlpapi.dll", ExactSpelling = true)]
  914 + static extern int SendARP(int DestIP, int SrcIP, byte[] pMacAddr, ref int PhyAddrLen);
  915 + }
  916 +
  917 +
  918 + public static class HexString
  919 + {
  920 + private static readonly uint[] _lookup32 = CreateLookup32();
  921 +
  922 + private static uint[] CreateLookup32()
  923 + {
  924 + var result = new uint[256];
  925 + for (int i = 0; i < 256; i++)
  926 + {
  927 + string s = i.ToString("X2");
  928 + result[i] = ((uint)s[0]) + ((uint)s[1] << 16);
  929 + }
  930 + return result;
  931 + }
  932 +
  933 + public static string ByteArrayToHexViaLookup32(byte[] bytes, char separator)
  934 + {
  935 + var lookup32 = _lookup32;
  936 + byte multipier = 2;
  937 + int length = bytes.Length * 2;
  938 + if (separator != '\0')
  939 + {
  940 + length = bytes.Length * 3 - 1;
  941 + multipier = 3;
  942 + }
  943 + var result = new char[length];
  944 + for (int i = 0; i < bytes.Length; i++)
  945 + {
  946 + var val = lookup32[bytes[i]];
  947 + result[multipier * i] = (char)val;
  948 + result[multipier * i + 1] = (char)(val >> 16);
  949 + if (separator != '\0' && i != bytes.Length - 1)
  950 + {
  951 + result[multipier * i + 2] = separator;
  952 + }
  953 + }
  954 + return new string(result);
  955 + }
  956 + }
  957 +}
0 958 \ No newline at end of file
... ...
Vrh.Log4Pro.MaintenanceConsole/Manager - BackupPackageManager.cs
... ... @@ -81,8 +81,8 @@ namespace Vrh.Log4Pro.MaintenanceConsole.BackupPackageManagerNS
81 81 if (!Directory.Exists(destinationfolder)) { Directory.CreateDirectory(destinationfolder); }
82 82  
83 83 var destinationfilename = Path.GetFileNameWithoutExtension(bp.Xml_PackageFilePath);
84   - var temprootfolder=string.IsNullOrEmpty(bp.Xml_TempDirectoryPath) ? destinationfolder : bp.Xml_TempDirectoryPath;
85   - string tempfolderpath = Path.Combine(temprootfolder, destinationfilename);
  84 + var temprootfolder =string.IsNullOrEmpty(bp.Xml_TempDirectoryPath) ? destinationfolder : bp.Xml_TempDirectoryPath;
  85 + string tempfolderpath = Path.Combine(temprootfolder, Path.GetRandomFileName()); // destinationfilename
86 86  
87 87 if (Directory.Exists(tempfolderpath)) { Directory.Delete(tempfolderpath, true); }
88 88 if (!Directory.Exists(tempfolderpath)) { Directory.CreateDirectory(tempfolderpath); }
... ...
Vrh.Log4Pro.MaintenanceConsole/Manager - MaintenanceToolManager.cs
... ... @@ -21,6 +21,7 @@ using Vrh.Log4Pro.MaintenanceConsole.ToolsNS;
21 21 using Vrh.XmlProcessing;
22 22 using System.Xml.Linq;
23 23 using System.Text.RegularExpressions;
  24 +using System.Net;
24 25  
25 26 namespace Vrh.Log4Pro.MaintenanceConsole.MaintenanceToolManagerNS
26 27 {
... ... @@ -39,6 +40,7 @@ namespace Vrh.Log4Pro.MaintenanceConsole.MaintenanceToolManagerNS
39 40  
40 41 var menufunctions = new Menu("Maintenance Tools", "Select function!")
41 42 .AddMenuItem(new Menu.Item(CLP.Module.MaintenanceToolManager.Functions.RegexTester.KEY, "Regex tester", RegexTester,new Menu.ExecutorParameter(cfg:config)))
  43 + .AddMenuItem(new Menu.Item(CLP.Module.MaintenanceToolManager.Functions.TCPIPTester.KEY, "TcpIp Tester", TcpIpTester, new Menu.ExecutorParameter(cfg: config, null)))
42 44 .AddMenuItem(new Menu.Item(CLP.Module.MaintenanceToolManager.Functions.Tool.KEY, "Tool sample", Tool2, new Menu.ExecutorParameter(cfg: config, null)))
43 45 .SetSelectionMode(Menu.SelectionMode.Single);
44 46 foreach (var x in config.ExternalUtilityConfigList)
... ... @@ -100,10 +102,179 @@ namespace Vrh.Log4Pro.MaintenanceConsole.MaintenanceToolManagerNS
100 102 }
101 103 #endregion RegexTester
102 104  
  105 + #region TcpIpTester
  106 + private static List<Pinger> PingerList = new List<Pinger>();
  107 + private static object TcpIpTester(object parameter, object o)
  108 + {
  109 + var config = (parameter as Menu.ExecutorParameter).GetConfig<MaintenanceToolsXmlProcessor>();
  110 + var pingerconfigxml = config.PingerConfigXml;
  111 + var iplist = config.PingerIpAddressList;
  112 + DisplayPingerParameters(pingerconfigxml);
  113 + var firstrun = true;
  114 + var suppresserroneousipaddresses = false;
  115 + while (true)
  116 + {
  117 + DisplayAllPingerStatus(pingerconfigxml,iplist, suppresserroneousipaddresses);
  118 + suppresserroneousipaddresses = true;
  119 + if (!firstrun) { DisplayPingerCommands(); }
  120 +
  121 + var command = firstrun ? "STARTANDSHOW *" : ColorConsole.ReadLine();
  122 + if (command == "EX") { break; }
  123 + if (command == "") { continue; }
  124 + var cmdarray = command.Split(' ');
  125 + if (cmdarray.Length!=2) { ColorConsole.WriteLine($"Incorrect answer!", ConsoleColor.Red); continue; }
  126 + var cmd = cmdarray[0];
  127 + var inpip = cmdarray[1];
  128 +
  129 + List<Pinger> selectedpingers;
  130 + if (inpip=="ALL" || inpip =="*") { selectedpingers = PingerList; }
  131 + else
  132 + {
  133 + var pinger = PingerList.FirstOrDefault(x => x.HostNameOrAddress == inpip);
  134 + if (pinger == null) { ColorConsole.WriteLine($"IP does not exist.", ConsoleColor.Red); continue; }
  135 + selectedpingers = new List<Pinger>() { pinger };
  136 + }
  137 +
  138 + if (cmd.ToLower() == "start") { StartPingers(selectedpingers, pingerconfigxml); }
  139 + if (cmd.ToLower() == "startandshow") { StartPingers(selectedpingers, pingerconfigxml); DisplayPingerHistory(selectedpingers); }
  140 + else if (cmd.ToLower() == "stop") { StopPingers(selectedpingers); }
  141 + else if (cmd.ToLower() == "drop") { DisposePingers(selectedpingers); }
  142 + else if (cmd.ToLower() == "show") { DisplayPingerHistory(selectedpingers); }
  143 + else { ColorConsole.WriteLine($"Incorrect answer!", ConsoleColor.Red); continue; }
  144 + firstrun = false;
  145 + }
  146 +
  147 + ColorConsole.ReadLine($"{nameof(TcpIpTester)} completed...", ConsoleColor.Yellow);
  148 + return o;
  149 + }
  150 + private static void StartPingers(List<Pinger> selectedpingers, XElement pingerconfigxml)
  151 + {
  152 + foreach (var pinger in selectedpingers) { pinger.Start(pingerconfigxml); }
  153 + }
  154 + private static void StopPingers(List<Pinger> selectedpingers)
  155 + {
  156 + foreach (var pinger in selectedpingers) { pinger.Stop(); }
  157 + }
  158 + private static void DisposePingers(List<Pinger> selectedpingers)
  159 + {
  160 + foreach (var pinger in selectedpingers) { pinger.Dispose(); }
  161 + }
  162 +
  163 + private static void DisplayPingerCommands()
  164 + {
  165 + ColorConsole.WriteLine($"Enter 'START [ip address]' to start/restart pinging.", ConsoleColor.Yellow);
  166 + ColorConsole.WriteLine($"Enter 'STOP [ip address]' to stop pinging.", ConsoleColor.Yellow);
  167 + ColorConsole.WriteLine($"Enter 'DROP [ip address]' to drop collected data.", ConsoleColor.Yellow);
  168 + ColorConsole.WriteLine($"Enter 'SHOW [ip address]' to start monitoring .", ConsoleColor.Yellow);
  169 + ColorConsole.WriteLine($"Set [ip address] to 'ALL' or '*' for action to all ip addresses.", ConsoleColor.Yellow);
  170 + }
  171 +
  172 + private static void DisplayPingerParameters(XElement pingerconfigxml)
  173 + {
  174 + ColorConsole.WriteLine($"Pinger configuration parameters:", ConsoleColor.Yellow);
  175 + var pconfig = new Pinger("127.0.0.1", pingerconfigxml).Configuration;
  176 + ColorConsole.WriteLine(pconfig.pingtimeout.ToString(), ConsoleColor.Yellow, prefix: " pingtimeout :", suffix: "ms");
  177 + ColorConsole.WriteLine(pconfig.pingcyclenumofpackages.ToString(), ConsoleColor.Yellow, prefix: " pings per cycle:", suffix: "pings");
  178 + ColorConsole.WriteLine((pconfig.pingcyclefrequency / 1000).ToString(), ConsoleColor.Yellow, prefix: " cycle frequency:", suffix: "sec");
  179 + ColorConsole.WriteLine((pconfig.pingeractivlength / 60000).ToString(), ConsoleColor.Yellow, prefix: " pinger liftime :", suffix: "min");
  180 + ColorConsole.WriteLine((pconfig.pingerhistorylength / 60000).ToString(), ConsoleColor.Yellow, prefix: " history length :", suffix: "min");
  181 + ColorConsole.WriteLine($"Pinger status for the following addresses:", ConsoleColor.Yellow);
  182 + }
  183 + private static void DisplayPingerHistory(List<Pinger> selectedpingers)
  184 + {
  185 + ColorConsole.WriteLine();
  186 + var cursorleftposition = ColorConsole.CursorLeft;
  187 + var cursortopposition = ColorConsole.CursorTop;
  188 + while (true)
  189 + {
  190 + var pingcyclefrequency = 0;
  191 + ColorConsole.SetCursorPosition(cursorleftposition, cursortopposition);
  192 + var monitorcyclestart = DateTime.Now;
  193 + foreach (var pinger in selectedpingers) { pingcyclefrequency=DisplayOnePingerHistory(pinger); if (ColorConsole.KeyAvailable) { goto finishdisplayhistory; };}
  194 + var elapsed = DateTime.Now.Subtract(monitorcyclestart).TotalMilliseconds;
  195 + if (elapsed < pingcyclefrequency) { Thread.Sleep((int)((double)pingcyclefrequency - elapsed)); }
  196 + ColorConsole.WriteLine();
  197 + ColorConsole.WriteLine($"Press any key to exit from monitoring...", ConsoleColor.Yellow);
  198 + }
  199 + finishdisplayhistory:;
  200 + }
  201 + private static int DisplayOnePingerHistory(Pinger pinger)
  202 + {
  203 + var _h = pinger.GetHistory();
  204 + if (_h == null) { return 0; }
  205 + var h = new Pinger.PingHistory(pinger.GetHistory());//klónozzuk, mert egyébként ha az adatgyűjtés miatt megváltozik akkor exception-re fut a foreach
  206 + ColorConsole.Write(pinger.HostNameOrAddress, ConsoleColor.Yellow, suffix: ": ");
  207 + var pingerstatus = pinger.Operating ? "pinging" : "still";
  208 + var pingerstatuscolor = pinger.Operating ? ConsoleColor.Green : ConsoleColor.Yellow;
  209 + ColorConsole.Write(pingerstatus.PadRight(10), pingerstatuscolor);
  210 +
  211 + var lc = h.PingStateQueue.LastOrDefault();
  212 + if (lc != null)
  213 + {
  214 + var responseinfo = lc.StatusCategory != Pinger.PingCycle.IPStatusCategory.Success ? $"{lc.StatusCategory}" : $"RT:{lc.RoundtripTime} [{lc.RoundtripTimeMin}<{lc.RoundtripTimeAverage}<{lc.RoundtripTimeMax}]ms, LP:{lc.PackagesSentCategory}";
  215 + responseinfo=responseinfo.PadRight(35)+" ";
  216 + ColorConsole.Write(responseinfo, GetPingCycleDisplayColor(lc));
  217 +
  218 + foreach (var he in h.PingStateQueue)
  219 + {
  220 + var pcd = GetPingCycleDisplayCharacter(he);
  221 + var pcdc=GetPingCycleDisplayColor(he);
  222 + for (var i = 0; i < he.NumberOfCycles; i++) { ColorConsole.Write(pcd, pcdc); }
  223 + if (ColorConsole.KeyAvailable) { goto finishdisplayhistory; };
  224 + }
  225 + }
  226 + finishdisplayhistory:
  227 + ColorConsole.WriteLine();
  228 + return h.PingCycleFrequency;
  229 + }
  230 + private static string GetPingCycleDisplayCharacter(Pinger.PingCycle he) { return he.StatusCategory != Pinger.PingCycle.IPStatusCategory.Success ? "9" : he.RoundtripTimeCategory.ToString().Substring(1); }
  231 + private static ConsoleColor GetPingCycleDisplayColor(Pinger.PingCycle pingcycle)
  232 + {
  233 + return pingcycle.StatusCategory != Pinger.PingCycle.IPStatusCategory.Success ? ConsoleColor.Red
  234 + : pingcycle.PackagesSentCategory == Pinger.PingCycle.LostPackageCategory.Excellent ? ConsoleColor.DarkGreen
  235 + : pingcycle.PackagesSentCategory == Pinger.PingCycle.LostPackageCategory.Good ? ConsoleColor.Green
  236 + : pingcycle.PackagesSentCategory == Pinger.PingCycle.LostPackageCategory.Acceptable ? ConsoleColor.DarkYellow
  237 + : pingcycle.PackagesSentCategory == Pinger.PingCycle.LostPackageCategory.Bad ? ConsoleColor.Yellow
  238 + : ConsoleColor.Red;
  239 + }
  240 + private static void DisplayAllPingerStatus(XElement pingerconfigxml,List<MaintenanceToolsXmlProcessor.IPAddressParsingResult> iplist, bool suppresserroneousipaddresses=false)
  241 + {
  242 + foreach (var ip in iplist) { DisplayOnePingerStatus(ip, pingerconfigxml, suppresserroneousipaddresses); }
  243 + }
  244 + private static void DisplayOnePingerStatus(MaintenanceToolsXmlProcessor.IPAddressParsingResult ip, XElement pingerconfigxml,bool suppresserroneousipaddresses = false)
  245 + {
  246 + if (ip.GoodIP)
  247 + {
  248 + ColorConsole.Write($"{ip.IP}", ConsoleColor.Green);
  249 +
  250 + var pingerlist = PingerList.Select(x => x.HostNameOrAddress).ToList();
  251 + Pinger pinger;
  252 + if (pingerlist.Contains(ip.IP.ToString()))
  253 + {
  254 + pinger = PingerList.FirstOrDefault(x => x.HostNameOrAddress == ip.IP.ToString());
  255 + ColorConsole.Write($" Pinger exist.", ConsoleColor.Green);
  256 + }
  257 + else
  258 + {
  259 + pinger = new Pinger(ip.IP.ToString(), pingerconfigxml);
  260 + PingerList.Add(pinger);
  261 + ColorConsole.Write($" Pinger created.", ConsoleColor.Green);
  262 + }
  263 +
  264 + if (pinger.Operating) { ColorConsole.Write($" Pinger running.", ConsoleColor.Green); }
  265 + else { ColorConsole.Write($" Pinger still.", ConsoleColor.Green); }
  266 + ColorConsole.WriteLine();
  267 + }
  268 + else { if (!suppresserroneousipaddresses) { ColorConsole.WriteLine(ip.ErrorMessage, ConsoleColor.Red); } }
  269 + }
  270 + #endregion TcpIpTester
  271 +
  272 +
103 273 #region Tool templates
104 274 private static object Tool2(object parameter, object o)
105 275 {
106   - var config = parameter as MaintenanceToolsXmlProcessor;
  276 + var config = (parameter as Menu.ExecutorParameter).GetConfig<MaintenanceToolsXmlProcessor>();
  277 + var regexptesterconfig = config.RegexpTesterConfig;
107 278 ColorConsole.ReadLine($"{nameof(Tool2)} is not ready yet...", ConsoleColor.Yellow);
108 279 return o;
109 280 }
... ... @@ -134,12 +305,42 @@ namespace Vrh.Log4Pro.MaintenanceConsole.MaintenanceToolManagerNS
134 305 public bool Valid;
135 306 }
136 307 public XElement RegexpTesterConfig;
  308 + public XElement PingerConfigXml;
  309 + public class IPAddressParsingResult
  310 + {
  311 + public IPAddress IP;
  312 + public bool GoodIP;
  313 + public string ErrorMessage;
  314 + public IPAddressParsingResult(IPAddress ip,bool result, string errormessage=null) { IP = ip;GoodIP = result; ErrorMessage = errormessage; }
  315 + }
  316 + public List<IPAddressParsingResult> PingerIpAddressList;
137 317 public List<ExternalUtilityConfig> ExternalUtilityConfigList = new List<ExternalUtilityConfig>();
138 318  
139 319 #region constructor
140 320 public MaintenanceToolsXmlProcessor(string xmlcs, string basefolder, string lcid) : base(xmlcs, basefolder, lcid, null)
141 321 {
142 322 RegexpTesterConfig = GetXElement(nameof(XmlStructure.RegexpTester));
  323 +
  324 + var TcpIpTesterXml = GetXElement(nameof(XmlStructure.TcpIpTester));
  325 + if (TcpIpTesterXml != null)
  326 + {
  327 + PingerConfigXml = GetXElement(TcpIpTesterXml, nameof(XmlStructure.TcpIpTester.Pinger));
  328 + var iplistinstring = GetXElement(TcpIpTesterXml, nameof(XmlStructure.TcpIpTester.IPAddresses))?.Value;
  329 + List<string> iplist;
  330 + PingerIpAddressList = new List<IPAddressParsingResult>();
  331 + if (!string.IsNullOrWhiteSpace(iplistinstring))
  332 + {
  333 + iplist = iplistinstring.Split(new char[] { ' ','/', ',', ';', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries).ToList();
  334 + foreach (var ipcandidate in iplist)
  335 + {
  336 + var ipc=ipcandidate.Trim();
  337 + if (!IPAddress.TryParse(ipc, out IPAddress IP)) { PingerIpAddressList.Add(new IPAddressParsingResult(IP,false, $"{ipc} Format incorrect!")); }
  338 + else if (PingerIpAddressList.Select(x=>x.IP).ToList().Contains(IP)) { PingerIpAddressList.Add(new IPAddressParsingResult(IP, false, $"{ipc} Duplicated IP address!")); }
  339 + else { PingerIpAddressList.Add(new IPAddressParsingResult(IP,true)); };
  340 + }
  341 + }
  342 + }
  343 +
143 344 var euElementList = GetAllXElements(nameof(XmlStructure.ExternalUtility));
144 345 foreach (var euElement in euElementList) { var euc = new ExternalUtilityConfig(euElement); if (euc.Valid && !ExternalUtilityConfigList.Exists(x=>x.Key==euc.Key)) { ExternalUtilityConfigList.Add(euc); } }
145 346 }
... ... @@ -159,6 +360,18 @@ namespace Vrh.Log4Pro.MaintenanceConsole.MaintenanceToolManagerNS
159 360 public static class WaitForExit { public static class Values { public static bool DEFAULT = true; } }
160 361 }
161 362 }
  363 + public static class TcpIpTester
  364 + {
  365 + public static class Pinger
  366 + {
  367 + public static class PingTimeout { }
  368 + public static class PingCycleFrequency { }
  369 + public static class PingCycleNumOfPackages { }
  370 + public static class PingerActiveLength { }
  371 + public static class PingerHistoryLength { }
  372 + }
  373 + public static class IPAddresses{ }
  374 + }
162 375 }
163 376 #endregion XmlStructure
164 377 }
... ...
Vrh.Log4Pro.MaintenanceConsole/Manager - SQLDataBaseManager.cs
... ... @@ -833,7 +833,7 @@ namespace Vrh.Log4Pro.MaintenanceConsole.SQLDataBaseManagerNS
833 833 vars[nameof(DBSubstitutionName.DBDATAGROUP)] = "";
834 834 string zipfilename = VRH.Common.StringConstructor.ResolveConstructorR(vars, backupfilenamemask, "{}@@") + ".zip";
835 835 zipfilefullpath = Path.Combine(backupdirectorypath, zipfilename);
836   - tempbackupdirectorypath = Path.Combine(backupdirectorypath, "TEMP_" + Path.GetFileNameWithoutExtension(zipfilename));
  836 + tempbackupdirectorypath = Path.Combine(backupdirectorypath, Path.GetRandomFileName());
837 837 if (Directory.Exists(tempbackupdirectorypath)) { Directory.Delete(tempbackupdirectorypath, recursive: true); }
838 838 if (!Directory.Exists(tempbackupdirectorypath)) { Directory.CreateDirectory(tempbackupdirectorypath); }
839 839 }
... ... @@ -950,7 +950,7 @@ namespace Vrh.Log4Pro.MaintenanceConsole.SQLDataBaseManagerNS
950 950 vars[nameof(DBSubstitutionName.DBONAME)] = "";
951 951 vars[nameof(DBSubstitutionName.DBDATAGROUP)] = "";
952 952 string zipfilename = VRH.Common.StringConstructor.ResolveConstructorR(vars, backupfilenamemask, "{}@@") + ".zip";
953   - tempbackupdirectorypath = Path.Combine(backupdirectorypath, "TEMP_"+Path.GetFileNameWithoutExtension(zipfilename));
  953 + tempbackupdirectorypath = Path.Combine(backupdirectorypath, Path.GetRandomFileName());
954 954 zipfilefullpath = Path.Combine(backupdirectorypath, zipfilename);
955 955 if (Directory.Exists(tempbackupdirectorypath)) { Directory.Delete(tempbackupdirectorypath, recursive: true); }
956 956 if (!Directory.Exists(tempbackupdirectorypath)) { Directory.CreateDirectory(tempbackupdirectorypath); }
... ...
Vrh.Log4Pro.MaintenanceConsole/Properties/AssemblyInfo.cs
... ... @@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
32 32 // You can specify all the values or you can default the Build and Revision Numbers
33 33 // by using the '*' as shown below:
34 34 // [assembly: AssemblyVersion("1.0.*")]
35   -[assembly: AssemblyVersion("1.7.3.0")]
36   -[assembly: AssemblyFileVersion("1.7.3.0")]
  35 +[assembly: AssemblyVersion("1.8.0.0")]
  36 +[assembly: AssemblyFileVersion("1.8.0.0")]
... ...
Vrh.Log4Pro.MaintenanceConsole/Vrh.Log4Pro.MaintenanceConsole.csproj
... ... @@ -348,6 +348,7 @@
348 348 </Reference>
349 349 </ItemGroup>
350 350 <ItemGroup>
  351 + <Compile Include="ConsoleFunction - Tools - TcpIp.cs" />
351 352 <Compile Include="ConsoleFunction - CommandLineParser.cs" />
352 353 <Compile Include="ConsoleFunction - ColorConsole.cs" />
353 354 <Compile Include="ConsoleFunction - Menu.cs" />
... ...