1379 lines
43 KiB
C#
1379 lines
43 KiB
C#
///
|
|
/// Copyright (c) 2017 Sensus Metering Systems
|
|
///
|
|
using System;
|
|
using System.Diagnostics;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
using System.Net.Sockets;
|
|
using log4net;
|
|
|
|
namespace TBF.BenchControl.Network.Telnet
|
|
{
|
|
/// <summary>
|
|
/// This class implements iST Telnet Client functionality.
|
|
/// </summary>
|
|
public class TelnetClient
|
|
{
|
|
static ILog log = LogManager.GetLogger("TelnetClient");
|
|
|
|
///
|
|
/// Common commands
|
|
///
|
|
public const string CtrlCCommand = "\u0003"; /// CTRL-C command to close the connection after a failed login
|
|
public const string RebootCommand = "sudo shutdown -r now"; /// 'reboot' command to reboot and start a new session, CmdAction.REBOOT_RECONNECT
|
|
public const string ExitCommand = "\u0004"; /// CTRL-D command to terminate the session, used by CmdAction.EXIT_RECONNECT,
|
|
|
|
///
|
|
/// Re-connect after re-boot behavior:
|
|
///
|
|
const int DelayAfterRebootMs = 20000; /// [ms] Delay before the first attempt to re-connect: 20 sec
|
|
const int LoginTimeOutMs = 20000; /// [ms] Login time out period, applies when TCP connection was established
|
|
const int LoginRetryPeriodMs = 25000; /// [ms] Delay before every next attempt to re-connect, normally applies when TCP connection was refused
|
|
const int TimeIncrementMs = 1000; /// [ms] Can be much larger now, times out blocking read operations, does not delay normal program flow
|
|
|
|
|
|
const int DefaultTelnetPortNumber = 23; /// Can be ooverriden by a host string like "192.168.0.5:8023"
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// High level states of TelnetClient operation
|
|
/// </summary>
|
|
public enum TelnetState
|
|
{
|
|
Inactive,
|
|
Busy,
|
|
Success,
|
|
Error,
|
|
BusyFinal, /// Is not followed by a summary dialog
|
|
FatalError,
|
|
}
|
|
|
|
/// <summary>
|
|
/// High level state of TelnetClient operation used to update device's icon, etc.
|
|
/// </summary>
|
|
public TelnetState State { get { return state; } } /// readonly
|
|
TelnetState state;
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Telnet (Network Virtual Terminal) control characters (code >= 128).
|
|
/// <seealso cref = http://support.microsoft.com/kb/231866 />
|
|
/// </summary>
|
|
enum NvtCommand
|
|
{
|
|
NA = -1, /// -1 = NVT command not available (possibly a broken connection)
|
|
SE = 240, /// 240 = End of sub-negotiation parameters
|
|
NOP, /// 241 = No operation
|
|
DM, /// 242 = Data mark
|
|
BRK, /// 243 = Break
|
|
IP, /// 244 = Suspend
|
|
AO, /// 245 = Abort output
|
|
AYT, /// 246 = Are you there
|
|
EC, /// 247 = Erase characters
|
|
EL, /// 248 = Erase line
|
|
GA, /// 249 = Go ahead
|
|
SB, /// 250 = Sub-negotiation
|
|
WILL, /// 251 = will
|
|
WONT, /// 252 = wont
|
|
DO, /// 253 = do
|
|
DONT, /// 254 = don't
|
|
IAC, /// 255 = Interpret as command
|
|
}
|
|
|
|
/// <summary>
|
|
/// Telnet (Network Virtual Terminal) options negotiated by the use of command:
|
|
/// IAC + [WILL | WONT | DO | DONT] + NvtOption (sequence of 3 bytes).
|
|
/// <seealso cref = http://support.microsoft.com/kb/231866 />
|
|
/// </summary>
|
|
enum NvtOption
|
|
{
|
|
NA = -1, /// = NVT option not available (possibly a broken connection)
|
|
SGA = 3, /// = Suppress go ahead
|
|
}
|
|
|
|
/// <summary>
|
|
/// Network virtual terminal (state machine) states
|
|
/// </summary>
|
|
enum NvtState
|
|
{
|
|
AwaitingConnect = 0, /// Awaits CONNECT command from the cmdQueue, no session is active, TCP is not polled
|
|
Login, /// Retries a login after REBOOT_RECONNECT or EXIT_RECONNECT commands, queue is not polled
|
|
Session, /// Telnet session: awaits commands from the cmdQueue, receives characters from TCP (full-duplex)
|
|
Measurement, /// Telnet session: awaits CTRL-C command from the cmdQueue, receives characters from TCP and extracts data
|
|
WaitResponse, /// Receives characters from TCP, builds a response str., awaiting the prompt, queue is not polled
|
|
WaitReboot, /// Wait until TCP connection is terminated and reboot starts
|
|
NvtStatesCount
|
|
}
|
|
|
|
/// <summary>
|
|
/// Network virtual terminal (state machine) state used in control at lower level
|
|
/// </summary>
|
|
NvtState nvtState;
|
|
|
|
/// <summary>
|
|
/// Network virtual terminal (state machine) events
|
|
/// </summary>
|
|
enum NvtEvent
|
|
{
|
|
None = 0,
|
|
TcpReceived, /// TCP data received
|
|
CmdReceived, /// Command from the cmdQueue received
|
|
Timeout, /// Timeout occurred
|
|
ConnectionLost, /// TCP connection lost
|
|
Terminate /// Command CmdAction.TERMINATE received
|
|
}
|
|
|
|
/// <summary>
|
|
/// Values returned by communication functions
|
|
/// </summary>
|
|
enum RetVal
|
|
{
|
|
OK = 0,
|
|
LoginRejected, /// Login rejected, invalid username or password
|
|
Timeout,
|
|
NoTcpConnection, /// No TCP connection (connection lost)
|
|
Terminate, /// Returned by CmdQueueClear() if TERINATE is in the queue
|
|
Error,
|
|
}
|
|
|
|
|
|
///
|
|
/// Private fields
|
|
///
|
|
object objRef; /// Reference to object to be included in info sent by event handlers
|
|
IVirtualTerminal vt; /// Virtual terminal
|
|
string hostname; /// Telnet server host name or IP address.
|
|
int portNr = DefaultTelnetPortNumber; /// Telnet server port number.
|
|
TcpClient tcp = null; /// Reference to TcpClient
|
|
AutoResetEvent disconnectDone;
|
|
string prompt; /// Prompt string used for command complete detection
|
|
/// ... initialized to a default iCOM board prompt.
|
|
string labelStr = String.Empty; /// Set by a command with CmdAction.LABEL.
|
|
string completeResponse; /// A complete response used by IF_RECEIVED, IF_NOT_RECEIVED.
|
|
Thread thread; /// Worker thread.
|
|
bool disposed = false;
|
|
|
|
Queue<Command> cmdQueue = new Queue<Command>(); /// Worker thread command queue.
|
|
AutoResetEvent queueEvent; /// Sync. event set when cmdQueue is filled (=Enqueue())
|
|
|
|
/// Get command queue size
|
|
int cmdQueueCount
|
|
{
|
|
get { lock (cmdQueue) { return cmdQueue.Count; } }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Thread safe cmdQueue.Peek()
|
|
/// </summary>
|
|
/// <returns>Command</returns>
|
|
Command CmdQueuePeek()
|
|
{
|
|
lock (cmdQueue)
|
|
{
|
|
if (cmdQueue.Count > 0) return cmdQueue.Peek();
|
|
else return new Command(CmdAction.VOID);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Thread safe cmdQueue.Clear(), Leaves TERMINATE in the queue, if there is any.
|
|
/// </summary>
|
|
/// <returns>RetVal.OK or RetVal.Teminate</returns>
|
|
RetVal CmdQueueClear()
|
|
{
|
|
lock (cmdQueue)
|
|
{
|
|
if (cmdQueue.Count > 0 && cmdQueue.Peek().Action == CmdAction.TERMINATE) return RetVal.Terminate;
|
|
cmdQueue.Clear();
|
|
labelStr = String.Empty;
|
|
return RetVal.OK;
|
|
}
|
|
}
|
|
|
|
|
|
///
|
|
/// Properties set by the constructor
|
|
///
|
|
readonly string telnetUserName;
|
|
readonly string telnetPassword;
|
|
readonly string defaultPrompt;
|
|
readonly bool skipFirstLine; /// Skip the first line when logging in
|
|
|
|
|
|
DateTime cmdStartTime; /// Time of the last Dequeue (= command execution start time).
|
|
TimeSpan cmdTimeOut; /// Time out period of the last dequeued command.
|
|
DateTime connectTimeout; /// Checked when handling NvtEvent.Timeout and in the state NvtState.Login
|
|
|
|
int timeDone = 0; /// Duration of commands in [s] executed so far ...
|
|
/// ... starting from Command(CmdAction.NOTIFICATION, "Begin")
|
|
|
|
Command currentCmd = new Command(CmdAction.VOID); /// The currently executed command, used to calculate timeDone
|
|
|
|
string notificationText;
|
|
string fatalErrorText;
|
|
IList<AuxBrdUpgItemMessage> auxBrdUpgMessages;
|
|
|
|
/// <summary>
|
|
/// Creates a telnet connection, uses default port number, does not log in.
|
|
/// Hostname (IP address) and the port number is passed to worker thread
|
|
/// in a Command with CmdAction.CONNECT and str=hostname.
|
|
/// </summary>
|
|
public TelnetClient(object objRef, string threadId, string userName, string password, string defaultPrompt, bool skipFirstLine, bool useVT)
|
|
{
|
|
this.objRef = objRef;
|
|
this.telnetUserName = userName;
|
|
this.telnetPassword = password;
|
|
this.defaultPrompt = defaultPrompt;
|
|
this.skipFirstLine = skipFirstLine;
|
|
|
|
state = TelnetState.Inactive; /// Device's icon is being updated by HNIP
|
|
///
|
|
if (useVT)
|
|
{
|
|
vt = new VirtualTerminal();
|
|
}
|
|
else
|
|
{
|
|
vt = new DummyVT();
|
|
}
|
|
|
|
/// TODO: pass a VirtualTerminal reference
|
|
this.portNr = DefaultTelnetPortNumber;
|
|
queueEvent = new AutoResetEvent(false);
|
|
disconnectDone = new AutoResetEvent(false);
|
|
thread = new Thread(new ThreadStart(this.WorkerThread));
|
|
thread.Name = "Telnet_" + threadId + "_"; /// Is used also as a log file root name
|
|
thread.Start();
|
|
}
|
|
|
|
~TelnetClient()
|
|
{
|
|
Terminate();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enqueue a command that terminates the worker thread, used on program exit
|
|
/// </summary>
|
|
public void Terminate()
|
|
{
|
|
lock (cmdQueue)
|
|
{
|
|
cmdQueue.Clear();
|
|
cmdQueue.Enqueue(new Command(CmdAction.TERMINATE));
|
|
queueEvent.Set();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Close the log file and finish the worker thread by enqueueing
|
|
/// CmdAction.TERIMNATE into the command queue.
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
/// Check to see if Dispose has already been called. Avoid multiple Terminate()-s, thread.Join()-s.
|
|
lock (this)
|
|
{
|
|
if (disposed) return;
|
|
disposed = true;
|
|
}
|
|
|
|
Terminate();
|
|
|
|
if (!thread.Join(5000)) throw(new Exception("Fatal error"));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Worker thread procedure (an instance method).
|
|
/// </summary>
|
|
void WorkerThread()
|
|
{
|
|
log.Info("-------------------- Worker thread started --------------------");
|
|
/// Turn timeout off (turned on by commands)
|
|
cmdStartTime = DateTime.Now;
|
|
cmdTimeOut = TimeSpan.MaxValue;
|
|
|
|
/// State machine state (NVT state)
|
|
nvtState = NvtState.AwaitingConnect;
|
|
|
|
/// Builds the response from particular 'ds'-es
|
|
StringBuilder responseBuilder = new StringBuilder();
|
|
|
|
Command cmd = new Command(CmdAction.VOID); /// Command from the command queue
|
|
string ds = null; /// String received from TCP
|
|
|
|
while (true)
|
|
{
|
|
bool pollCmd = (nvtState == NvtState.AwaitingConnect || nvtState == NvtState.Session || nvtState == NvtState.Measurement);
|
|
bool pollTcp = (nvtState != NvtState.AwaitingConnect && nvtState != NvtState.Login);
|
|
|
|
///
|
|
/// NVT state machine event read. Contains (blocking) cmdQueue and TCP reads.
|
|
/// Possible events are:
|
|
/// NvtEvent.Terminate ....... cmd.Action==CmdAction.TERMINATE occurred
|
|
/// NvtEvent.CmdReceived ..... command 'cmd' received from the command queue
|
|
/// NvtEvent.TcpReceived ..... string 'ds' received from the TCP
|
|
/// NvtEvent.ConnectionLost .. TCP connection lost (when reading TCP)
|
|
/// NvtEvent.Timeout ......... timeout occurred
|
|
///
|
|
NvtEvent nvtEvent = NvtWaitLoop(pollCmd, pollTcp, ref cmd, ref ds);
|
|
Debug.WriteLine("nvtEvent = " + nvtEvent.ToString() + ", cmd = " + cmd.ToString() +
|
|
((ds!=null && ds.Length>0) ? (", ds = '" + ds + "'") : "") );
|
|
|
|
///
|
|
/// NVT state machine state change, the event processing.
|
|
/// No command queue or TCP read can be done here.
|
|
///
|
|
try
|
|
{
|
|
if (nvtEvent == NvtEvent.Terminate)
|
|
{
|
|
DisconnectTcp();
|
|
return; /// Exit the thread
|
|
}
|
|
/// -------------------------------
|
|
/// Process command available event
|
|
/// -------------------------------
|
|
else if (nvtEvent == NvtEvent.CmdReceived)
|
|
{
|
|
if (cmd.Action == CmdAction.NOTIFICATION)
|
|
{
|
|
OnNotification(cmd.Str);
|
|
continue;
|
|
}
|
|
else if (cmd.Action == CmdAction.LABEL)
|
|
{
|
|
labelStr = cmd.Str;
|
|
log.Info("Starting to process Aux.Board Upgrade Item Nr." + labelStr);
|
|
continue;
|
|
}
|
|
else if (cmd.Action == CmdAction.SET_PROMPT)
|
|
{
|
|
prompt = cmd.Str;
|
|
continue;
|
|
}
|
|
else if (cmd.Action == CmdAction.CLEAR_RESPONSE)
|
|
{
|
|
responseBuilder.Clear();
|
|
continue;
|
|
}
|
|
else if (cmd.Action == CmdAction.CONNECT && nvtState == NvtState.AwaitingConnect)
|
|
{
|
|
ParseHostString(cmd.Str);
|
|
connectTimeout = cmdStartTime + cmdTimeOut;
|
|
cmdTimeOut = TimeSpan.Zero;
|
|
nvtState = NvtState.Login;
|
|
continue;
|
|
}
|
|
else if (nvtState == NvtState.Session || nvtState == NvtState.Measurement)
|
|
{
|
|
switch (cmd.Action)
|
|
{
|
|
case CmdAction.SEND_STRING:
|
|
vt.WriteVt(cmd.Str + System.Environment.NewLine);
|
|
WriteLineTcp(cmd.Str);
|
|
OnVtTextChanged();
|
|
break;
|
|
|
|
case CmdAction.SEND_MSRMNT_CMD:
|
|
vt.WriteVt(cmd.Str + System.Environment.NewLine);
|
|
WriteLineTcp(cmd.Str);
|
|
OnVtTextChanged();
|
|
/// state change
|
|
responseBuilder.Clear();
|
|
nvtState = NvtState.Measurement;
|
|
break;
|
|
|
|
case CmdAction.SEND_COMMAND:
|
|
vt.WriteVt(cmd.Str + System.Environment.NewLine);
|
|
WriteLineTcp(cmd.Str);
|
|
OnVtTextChanged();
|
|
/// state change
|
|
responseBuilder.Clear();
|
|
nvtState = NvtState.WaitResponse;
|
|
break;
|
|
|
|
case CmdAction.DISCONNECT:
|
|
if (RetVal.OK == DisconnectTcp())
|
|
{
|
|
cmdTimeOut = TimeSpan.MaxValue;
|
|
nvtState = NvtState.AwaitingConnect;
|
|
}
|
|
else
|
|
{
|
|
cmdTimeOut = TimeSpan.MaxValue;
|
|
nvtState = NvtState.Session;
|
|
}
|
|
break;
|
|
|
|
case CmdAction.REBOOT_RECONNECT:
|
|
vt.WriteVt(RebootCommand + System.Environment.NewLine);
|
|
WriteLineTcp(RebootCommand);
|
|
OnVtTextChanged();
|
|
connectTimeout = cmdStartTime + cmdTimeOut; /// Both were set when REBOOT_RECONNECT was dequeued
|
|
cmdTimeOut = TimeSpan.FromMilliseconds(DelayAfterRebootMs);
|
|
responseBuilder.Clear();
|
|
nvtState = NvtState.WaitReboot;
|
|
break;
|
|
|
|
case CmdAction.EXIT_RECONNECT:
|
|
if (RetVal.OK != DisconnectTcp())
|
|
{
|
|
cmdTimeOut = TimeSpan.MaxValue;
|
|
nvtState = NvtState.Session;
|
|
break;
|
|
}
|
|
connectTimeout = cmdStartTime + cmdTimeOut;
|
|
cmdTimeOut = TimeSpan.Zero;
|
|
nvtState = NvtState.Login;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
/// --------------------------------
|
|
/// Process TCP data available event
|
|
/// --------------------------------
|
|
else if (nvtEvent == NvtEvent.TcpReceived)
|
|
{
|
|
switch (nvtState)
|
|
{
|
|
case NvtState.Session:
|
|
if (ds.Length > 0)
|
|
{
|
|
vt.WriteVt(ds);
|
|
OnVtTextChanged();
|
|
}
|
|
break;
|
|
|
|
case NvtState.Measurement:
|
|
if (ds.Length > 0)
|
|
{
|
|
vt.WriteVt(ds);
|
|
responseBuilder.Append(ds);
|
|
OnVtTextChanged();
|
|
}
|
|
{
|
|
/// Extract measurements
|
|
int from = responseBuilder.ToString().IndexOf('[');
|
|
if (from >= 0)
|
|
{
|
|
int len = responseBuilder.ToString(from + 1, responseBuilder.Length - from - 1).IndexOf(']');
|
|
|
|
if (len >= 0)
|
|
{
|
|
OnMeasurementDataReceived(this, new MeasuredDataEventArgs(responseBuilder.ToString(from + 1, len)));
|
|
responseBuilder.Remove(0, from + len + 2);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case NvtState.WaitResponse:
|
|
if (ds.Length > 0)
|
|
{
|
|
vt.WriteVt(ds);
|
|
responseBuilder.Append(ds);
|
|
OnVtTextChanged();
|
|
}
|
|
|
|
/// Raise OnPromptReceived(response) and clear responseBuilder when prompt detected.
|
|
if (responseBuilder.ToString().EndsWith(prompt))
|
|
{
|
|
completeResponse = responseBuilder.ToString(0, responseBuilder.Length - prompt.Length);
|
|
responseBuilder.Clear();
|
|
|
|
cmdStartTime = DateTime.Now; /// Reset timeout
|
|
cmdTimeOut = TimeSpan.MaxValue;
|
|
nvtState = NvtState.Session; /// Normal program flow
|
|
|
|
OnPromptReceived(completeResponse);
|
|
|
|
if (cmd.FatalRegex != null)
|
|
{
|
|
/// Check for a fatal error
|
|
Match match = cmd.FatalRegex.Match(completeResponse);
|
|
if (match.Success)
|
|
{
|
|
cmdTimeOut = TimeSpan.MaxValue;
|
|
DisconnectTcp(); /// Disconnect first
|
|
nvtState = NvtState.AwaitingConnect;
|
|
OnFatalError(GetRegexSubstring(match));
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (cmd.ErrorRegex != null)
|
|
{
|
|
/// Check for an error
|
|
Match match = cmd.ErrorRegex.Match(completeResponse);
|
|
if (match.Success) OnAddMessage(Category.Error, GetRegexSubstring(match));
|
|
}
|
|
|
|
if (cmd.WarningRegex != null)
|
|
{
|
|
/// Check for a warning
|
|
Match match = cmd.WarningRegex.Match(completeResponse);
|
|
if (match.Success) OnAddMessage(Category.Warning, GetRegexSubstring(match));
|
|
}
|
|
|
|
if (cmd.SuccessRegex != null)
|
|
{
|
|
/// Check for a success
|
|
Match match = cmd.SuccessRegex.Match(completeResponse);
|
|
if (match.Success) OnAddMessage(Category.Success, GetRegexSubstring(match));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case NvtState.WaitReboot:
|
|
if (ds.Length > 0)
|
|
{
|
|
vt.WriteVt(ds);
|
|
responseBuilder.Append(ds);
|
|
OnVtTextChanged();
|
|
}
|
|
|
|
/// Check the expected response after 'reboot' command.
|
|
if (responseBuilder.ToString().EndsWith("Sending SIGTERM to all processes.\r\n"))
|
|
{
|
|
responseBuilder.Clear();
|
|
if (RetVal.OK == DisconnectTcp())
|
|
{
|
|
cmdTimeOut = TimeSpan.FromMilliseconds(DelayAfterRebootMs);
|
|
nvtState = NvtState.Login;
|
|
}
|
|
else
|
|
{
|
|
cmdTimeOut = TimeSpan.MaxValue;
|
|
nvtState = NvtState.Session;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
/// ---------------------------------
|
|
/// Process TCP connection lost event
|
|
/// ---------------------------------
|
|
else if (nvtEvent == NvtEvent.ConnectionLost)
|
|
{
|
|
DisconnectTcp();
|
|
OnFatalError("Lost connection to telnet server " + hostname + ":" + portNr);
|
|
cmdTimeOut = TimeSpan.MaxValue;
|
|
nvtState = NvtState.AwaitingConnect;
|
|
continue;
|
|
}
|
|
/// ---------------------
|
|
/// Process timeout event
|
|
/// ---------------------
|
|
else if (nvtEvent == NvtEvent.Timeout)
|
|
{
|
|
cmdStartTime = DateTime.Now; /// Reset time after any timeout
|
|
|
|
if (nvtState == NvtState.Login)
|
|
{
|
|
if (cmdStartTime > connectTimeout)
|
|
{
|
|
DisconnectTcp();
|
|
OnFatalError("(Re)Connect timeout elapsed");
|
|
cmdTimeOut = TimeSpan.MaxValue;
|
|
nvtState = NvtState.AwaitingConnect;
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
tcp = new TcpClient(hostname, portNr);
|
|
|
|
log.Info("TCP client connected to " + hostname);
|
|
OnConnected();
|
|
}
|
|
catch (SocketException)
|
|
{
|
|
/// Cannot connect if iCOM is busy rebooting.
|
|
/// No state change, retry connection after LoginRetryPeriodMs
|
|
cmdTimeOut = TimeSpan.FromMilliseconds(LoginRetryPeriodMs);
|
|
continue;
|
|
}
|
|
|
|
prompt = defaultPrompt; /// Reset prompt to default
|
|
|
|
if (RetVal.OK == Login(telnetUserName, telnetPassword))
|
|
{
|
|
cmdTimeOut = TimeSpan.MaxValue;
|
|
nvtState = NvtState.Session;
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
log.Info("Telnet login refused " + hostname);
|
|
DisconnectTcp();
|
|
|
|
// No state change, retry connection after LoginRetryPeriodMs
|
|
cmdTimeOut = TimeSpan.FromMilliseconds(LoginRetryPeriodMs);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
else if (nvtState == NvtState.WaitResponse)
|
|
{
|
|
DisconnectTcp(); /// Disconnect first
|
|
OnFatalError("Timeout occurred at Telnet command " + cmd.ToString());
|
|
cmdTimeOut = TimeSpan.MaxValue;
|
|
nvtState = NvtState.AwaitingConnect;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
string msg = e.Message;
|
|
DisconnectTcp();
|
|
OnDisconnected();
|
|
OnFatalError("Lost connection to telnet server " + hostname + ":" + portNr + " Details: " + msg);
|
|
cmdTimeOut = TimeSpan.MaxValue;
|
|
nvtState = NvtState.AwaitingConnect;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disconnect the TCP client.
|
|
/// </summary>
|
|
/// <returns>RetVal.OK or RetVal.Error</returns>
|
|
RetVal DisconnectTcp()
|
|
{
|
|
if (tcp == null || !tcp.Connected)
|
|
{
|
|
return RetVal.OK; /// Already disconnected
|
|
}
|
|
|
|
tcp.Client.Shutdown(SocketShutdown.Both);
|
|
tcp.Client.BeginDisconnect(false, new AsyncCallback(DisconnectCallback), tcp.Client); /// (reuseSocket, callback, ...)
|
|
|
|
/// Wait for the disconnect to complete.
|
|
disconnectDone.WaitOne();
|
|
if (tcp.Client.Connected)
|
|
{
|
|
OnFatalError("Cannot disconnect from telnet server " + hostname + ":" + portNr);
|
|
return RetVal.Error;
|
|
}
|
|
|
|
OnDisconnected();
|
|
tcp = null;
|
|
return RetVal.OK;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Callback method used by DisconnectTcp()
|
|
/// </summary>
|
|
/// <param name="ar">IAsyncResult</param>
|
|
void DisconnectCallback(IAsyncResult ar)
|
|
{
|
|
Socket client = (Socket)ar.AsyncState;
|
|
client.EndDisconnect(ar); /// Complete the disconnect request.
|
|
disconnectDone.Set(); /// Signal that the disconnect is complete.
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// NVT state machine event read loop.
|
|
/// Possible events are:
|
|
/// NvtEvent.Terminate ....... cmd.Action==CmdAction.TERMINATE occurred
|
|
/// NvtEvent.CmdReceived ..... command 'cmd' received from the command queue
|
|
/// NvtEvent.TcpReceived ..... string 'ds' received from the TCP
|
|
/// NvtEvent.ConnectionLost .. TCP connection lost (when reading TCP)
|
|
/// NvtEvent.Timeout ......... timeout occurred
|
|
/// Notes:
|
|
/// 'pollCmd == false' means WaitResponse or Rebooting+Login is in progress, cmdQueue is not polled
|
|
/// </summary>
|
|
/// <param name="pollCmd">true = poll the command queue, false = don't (e.g. when nvtState=WaitPrompt)</param>
|
|
/// <param name="pollTcp">true = poll TCP, false = don't (e.g. when nvtState=AwaitingConnect)</param>
|
|
/// <param name="cmd">A command from the command queue</param>
|
|
/// <param name="ds">Received string from the TCP</param>
|
|
/// <returns>NVT event</returns>
|
|
NvtEvent NvtWaitLoop(bool pollCmd, bool pollTcp, ref Command cmd, ref string ds)
|
|
{
|
|
while (true)
|
|
{
|
|
DateTime dateTimeNow = DateTime.Now; /// Current time is read at this single place in the loop
|
|
|
|
if (CmdQueuePeek().Action == CmdAction.TERMINATE)
|
|
{
|
|
/// On program exit
|
|
return NvtEvent.Terminate;
|
|
}
|
|
else if ((cmdTimeOut != TimeSpan.MaxValue) && (dateTimeNow >= (cmdStartTime + cmdTimeOut)))
|
|
{
|
|
return NvtEvent.Timeout;
|
|
}
|
|
else if (pollCmd && (cmdQueueCount > 0))
|
|
{
|
|
/// In NvtState.AwaitingCmg when queue is not empty
|
|
cmd = Dequeue();
|
|
return (cmd.Action == CmdAction.TERMINATE) ? NvtEvent.Terminate : NvtEvent.CmdReceived;
|
|
}
|
|
else if (pollTcp)
|
|
{
|
|
/// In NvtState.Session, NvtState.WaitResponse and NvtState.WaitReboot
|
|
try
|
|
{
|
|
if (tcp.Client.Poll(1000 * TimeIncrementMs, SelectMode.SelectRead)) /// Blocking, time is in microseconds
|
|
{
|
|
ds = ReadTcp(tcp);
|
|
return NvtEvent.TcpReceived;
|
|
}
|
|
}
|
|
catch (Exception)
|
|
{
|
|
return NvtEvent.ConnectionLost;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!pollCmd)
|
|
{
|
|
/// The remaining time out period is always positive, conversion done from ticks (100ns) to ms
|
|
int remainingTimeoutMs = (int)(((cmdStartTime + cmdTimeOut).Ticks - dateTimeNow.Ticks) / 10000);
|
|
|
|
if ((cmdTimeOut != TimeSpan.MaxValue) && (0 < remainingTimeoutMs) && (remainingTimeoutMs < TimeIncrementMs))
|
|
{
|
|
Thread.Sleep(remainingTimeoutMs);
|
|
}
|
|
else
|
|
{
|
|
Thread.Sleep(TimeIncrementMs);
|
|
}
|
|
}
|
|
else if (queueEvent.WaitOne(TimeIncrementMs, false) && (cmdQueueCount > 0))
|
|
{
|
|
/// In NvtState.AwaitingConnect when queue was empty and one element was just enqueued
|
|
cmd = Dequeue();
|
|
return cmd.Action == CmdAction.TERMINATE ? NvtEvent.Terminate : NvtEvent.CmdReceived;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Parse the host + port number string, update hostname and portNr variables.
|
|
/// </summary>
|
|
/// <param name="hostStr">Example: "192.168.254.50" or "192.168.254.108:23"</param>
|
|
void ParseHostString(string hostStr)
|
|
{
|
|
/// Extract a hostname (or IP address) and a port number from cmd.Str
|
|
int colonPos = hostStr.IndexOf(':');
|
|
if (colonPos == -1)
|
|
{
|
|
hostname = hostStr;
|
|
portNr = DefaultTelnetPortNumber;
|
|
}
|
|
else
|
|
{
|
|
hostname = hostStr.Substring(0, colonPos);
|
|
portNr = Convert.ToUInt16(hostStr.Substring(colonPos + 1));
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Login to the telnet server.
|
|
/// Returns a response (a prompt) received after a login.
|
|
/// </summary>
|
|
/// <param name="username">User name</param>
|
|
/// <param name="password">Password</param>
|
|
/// <returns>RetVal.OK, RetVal.Timeout, RetVal.NoTcpConnection</returns>
|
|
RetVal Login(string username, string password)
|
|
{
|
|
if (username == null || password == null) return RetVal.LoginRejected;
|
|
|
|
///
|
|
/// Username
|
|
///
|
|
RetVal retval = WaitForLoginPrompt(": ", skipFirstLine);
|
|
if (retval != RetVal.OK) return retval;
|
|
vt.WriteVt(username + System.Environment.NewLine);
|
|
WriteLineTcp(username);
|
|
|
|
///
|
|
/// Password (hidden on the VT)
|
|
///
|
|
retval = WaitForLoginPrompt(": ", false);
|
|
if (retval != RetVal.OK) return retval;
|
|
WriteLineTcp(password);
|
|
|
|
///
|
|
/// Reset 'prompt' after (re)login and wait for the prompt
|
|
///
|
|
retval = WaitForLoginPrompt(prompt, false);
|
|
if (retval != RetVal.OK) return retval;
|
|
|
|
log.Info("Telnet login successfully completed " + hostname);
|
|
|
|
return RetVal.OK;
|
|
}
|
|
|
|
/// <summary>
|
|
/// One part of login which is reused.
|
|
/// </summary>
|
|
/// <param name="endsWithPattern">Quit condition, login prompt should end with this pattern</param>
|
|
/// <returns></returns>
|
|
RetVal WaitForLoginPrompt(string endsWithPattern, bool skipFirstLine)
|
|
{
|
|
StringBuilder s = new StringBuilder();
|
|
|
|
do {
|
|
Command cmd = new Command(CmdAction.VOID); /// Command from the command queue
|
|
string ds = null; /// String received from TCP
|
|
|
|
cmdStartTime = DateTime.Now;
|
|
cmdTimeOut = TimeSpan.FromMilliseconds(LoginTimeOutMs);
|
|
|
|
NvtEvent retval = NvtWaitLoop(false, true, ref cmd, ref ds); /// No cmd poll, do only TCP poll
|
|
Debug.WriteLine("retval = " + retval.ToString() + ", cmd = " + cmd.ToString() +
|
|
((ds != null && ds.Length > 0) ? (", ds = '" + ds + "'") : ""));
|
|
|
|
if (retval == NvtEvent.Timeout)
|
|
{
|
|
return RetVal.Timeout;
|
|
}
|
|
else if (retval != NvtEvent.TcpReceived)
|
|
{
|
|
return RetVal.NoTcpConnection;
|
|
}
|
|
|
|
if (ds != null && ds.Length > 0)
|
|
{
|
|
if (skipFirstLine)
|
|
{
|
|
if (ds.Contains(Environment.NewLine))
|
|
{
|
|
skipFirstLine = false;
|
|
string afterNL = ds.Substring(ds.IndexOf(Environment.NewLine) + Environment.NewLine.Length);
|
|
if (afterNL.Length > 0)
|
|
{
|
|
vt.WriteVt(afterNL);
|
|
s.Append(afterNL);
|
|
OnVtTextChanged();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
vt.WriteVt(ds);
|
|
s.Append(ds);
|
|
OnVtTextChanged();
|
|
}
|
|
}
|
|
}
|
|
while (!s.ToString().EndsWith(endsWithPattern));
|
|
|
|
return RetVal.OK;
|
|
}
|
|
|
|
|
|
string GetRegexSubstring(Match match)
|
|
{
|
|
Group group = match.Groups[0];
|
|
if (group.Success) return group.Value;
|
|
|
|
// No group defined but expression matched anyway,
|
|
// return the matched substring instead
|
|
return match.Value;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Sends a string to the telnet server.
|
|
/// </summary>
|
|
/// <param name="str">String to be sent</param>
|
|
void WriteTcp(string str)
|
|
{
|
|
Debug.WriteLine("WriteTcp(" + str.Replace("\n", "<LF>") + ")");
|
|
if (!tcp.Connected) return;
|
|
|
|
byte[] buf = System.Text.ASCIIEncoding.ASCII.GetBytes(str.Replace("\0xFF", "\0xFF\0xFF"));
|
|
tcp.GetStream().Write(buf, 0, buf.Length);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sends a line to the telnet server - terminating '\n' is appended to the string 'str'.
|
|
/// </summary>
|
|
/// <param name="str">Line to be sent without terminaitng '\n' character(s).</param>
|
|
void WriteLineTcp(string str)
|
|
{
|
|
WriteTcp (str + "\n");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read a string from telnet until no characters available.
|
|
/// </summary>
|
|
string ReadTcp(TcpClient tcp)
|
|
{
|
|
if (!tcp.Connected) return null;
|
|
|
|
StringBuilder strBldr = new StringBuilder();
|
|
ParseTelnet(strBldr);
|
|
|
|
return strBldr.ToString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse telnet stream of characters.
|
|
/// </summary>
|
|
void ParseTelnet(StringBuilder strBldr)
|
|
{
|
|
while (tcp.Available > 0)
|
|
{
|
|
NvtCommand input = (NvtCommand)tcp.GetStream().ReadByte();
|
|
switch (input)
|
|
{
|
|
case NvtCommand.NA:
|
|
break;
|
|
case NvtCommand.IAC:
|
|
/// Interpret as command
|
|
NvtCommand inputCmd = (NvtCommand)tcp.GetStream().ReadByte();
|
|
if (inputCmd == NvtCommand.NA) break;
|
|
|
|
switch (inputCmd)
|
|
{
|
|
case NvtCommand.IAC:
|
|
/// Literal IAC = 255 escaped, so append char 255 to the string
|
|
strBldr.Append(inputCmd);
|
|
break;
|
|
|
|
case NvtCommand.DO:
|
|
case NvtCommand.DONT:
|
|
case NvtCommand.WILL:
|
|
case NvtCommand.WONT:
|
|
/// Reply to all commands with "WONT", unless it is SGA (suppres go ahead)
|
|
NvtOption inputOpt = (NvtOption)tcp.GetStream().ReadByte();
|
|
if (inputOpt == NvtOption.NA) break;
|
|
|
|
tcp.GetStream().WriteByte((byte)NvtCommand.IAC);
|
|
if (inputOpt == NvtOption.SGA)
|
|
{
|
|
if (inputCmd == NvtCommand.DO)
|
|
tcp.GetStream().WriteByte((byte)NvtCommand.WILL);
|
|
else
|
|
tcp.GetStream().WriteByte((byte)NvtCommand.DO);
|
|
}
|
|
else
|
|
{
|
|
if (inputCmd == NvtCommand.DO)
|
|
tcp.GetStream().WriteByte((byte)NvtCommand.WONT);
|
|
else
|
|
tcp.GetStream().WriteByte((byte)NvtCommand.DONT);
|
|
}
|
|
tcp.GetStream().WriteByte((byte)inputOpt);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
strBldr.Append( (char)input );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
///
|
|
/// Public properties (read only)
|
|
///
|
|
public IVirtualTerminal Vt { get { return vt; } }
|
|
public string Hostname { get { return hostname; } }
|
|
public string NotificationText { get { return notificationText; } }
|
|
public string ErrorText { get { return fatalErrorText; } }
|
|
public IList<AuxBrdUpgItemMessage> AuxBrdUpgMessages { get { return auxBrdUpgMessages; } }
|
|
|
|
/// <summary>
|
|
/// true when there are command-s in cmdQueue
|
|
/// </summary>
|
|
public bool InProgress
|
|
{
|
|
get { return (cmdQueueCount > 0); }
|
|
}
|
|
|
|
///
|
|
/// Public methods (self explanatory)
|
|
///
|
|
public void ResetNotificationText() { notificationText = null; }
|
|
public void ResetErrorText() { fatalErrorText = null; }
|
|
public void ResetCollectedMessages()
|
|
{
|
|
auxBrdUpgMessages = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resets the progress to 0%. To be used before enqueing commands
|
|
/// when starting upgrade (the 2nd and each next time).
|
|
/// </summary>
|
|
public void ResetProgress()
|
|
{
|
|
lock (cmdQueue)
|
|
{
|
|
timeDone = 0;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clear VT screen
|
|
/// </summary>
|
|
public void ClearScreen()
|
|
{
|
|
vt.ClearScreen();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Insert a telnet client command to the FIFO queue 'cmdQueue'
|
|
/// to be processed then by the worker thread.
|
|
/// </summary>
|
|
/// <param name="cmd">Command : a pair CmdAction, string</param>
|
|
public void Enqueue(Command cmd)
|
|
{
|
|
lock (cmdQueue)
|
|
{
|
|
cmdQueue.Enqueue(cmd);
|
|
queueEvent.Set();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get a command from the FIFO queue 'cmdQueue'.
|
|
/// Called by the worker thread when commands are really processed.
|
|
/// Does also auxiliary actions, resets cmdStartTime time, etc.
|
|
/// </summary>
|
|
/// <param name="cmd">Command : a pair CmdAction, string</param>
|
|
Command Dequeue()
|
|
{
|
|
Command nextCmd;
|
|
bool queueJustEmptied = false;
|
|
|
|
try
|
|
{
|
|
lock (cmdQueue)
|
|
{
|
|
queueJustEmptied = (cmdQueue.Count == 1);
|
|
nextCmd = cmdQueue.Dequeue();
|
|
}
|
|
|
|
log.Info("Command: " + nextCmd.ToString());
|
|
}
|
|
catch
|
|
{
|
|
log.Debug("Debug: Attempt to read a command from an empty queue.");
|
|
nextCmd = new Command(CmdAction.VOID);
|
|
}
|
|
|
|
cmdStartTime = DateTime.Now;
|
|
if (nextCmd.Timeout > 0)
|
|
{
|
|
cmdTimeOut = new TimeSpan(0, 0, nextCmd.Timeout); /// in seconds
|
|
}
|
|
else
|
|
{
|
|
cmdTimeOut = TimeSpan.MaxValue; /// no timeout (timeout = weeks)
|
|
}
|
|
|
|
lock (cmdQueue)
|
|
{
|
|
if (currentCmd.Duration >= 0)
|
|
{
|
|
timeDone += currentCmd.Duration;
|
|
}
|
|
}
|
|
|
|
if (queueJustEmptied && nextCmd.Action != CmdAction.TERMINATE)
|
|
{
|
|
IssueSummary();
|
|
}
|
|
|
|
currentCmd = nextCmd;
|
|
return currentCmd;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Progress info to be polled by UI routines.
|
|
/// Info is based on the commands in the queue and 'timeDone'
|
|
/// Is thread safe, merely reads some fields and does safe calculations.
|
|
/// </summary>
|
|
/// <param name="timeDone">[sec]</param>
|
|
/// <param name="timeTodo">[sec]</param>
|
|
public void ProgressInfo(ref int timeDone, ref int timeTodo)
|
|
{
|
|
Command[] cmds;
|
|
lock (cmdQueue)
|
|
{
|
|
cmds = cmdQueue.ToArray();
|
|
timeDone = this.timeDone;
|
|
}
|
|
timeTodo = currentCmd.Duration;
|
|
for (int i = 0; i < cmds.GetLength(0); i++)
|
|
{
|
|
timeTodo += cmds[i].Duration;
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
public event EventHandler connectedHandler;
|
|
|
|
/// <summary>
|
|
/// This method is called from within this class when a telnet connection
|
|
/// was established and user was successfully logged in.
|
|
/// </summary>
|
|
protected virtual void OnConnected()
|
|
{
|
|
Debug.WriteLine("TelnetClient:OnConnected");
|
|
log.Warn("Connected");
|
|
|
|
if (null != connectedHandler)
|
|
{
|
|
try
|
|
{
|
|
connectedHandler(objRef, System.EventArgs.Empty);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
log.Info("Internal error: ConnectedHandler exception: " + e.Message);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public event EventHandler disconnectedHandler;
|
|
|
|
/// <summary>
|
|
/// This method is called from within this class when the telnet connection was broken.
|
|
/// </summary>
|
|
protected virtual void OnDisconnected()
|
|
{
|
|
Debug.WriteLine("TelnetClient:OnDisconnected");
|
|
log.Warn("Disconnected");
|
|
|
|
if (null != disconnectedHandler)
|
|
{
|
|
try
|
|
{
|
|
disconnectedHandler(objRef, System.EventArgs.Empty);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
log.Info("Internal error: DisconnectedHandler exception: " + e.Message);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
///
|
|
/// VtTextChangedEventHandler delegate and handler
|
|
///
|
|
public delegate void VtTextChangedEventHandler(object sender, VtTextChangedEventArgs e);
|
|
public event VtTextChangedEventHandler vtTextChangedHandler;
|
|
|
|
/// <summary>
|
|
/// This method is called from within this class when the contents of virtual terminal has changed.
|
|
/// </summary>
|
|
protected virtual void OnVtTextChanged()
|
|
{
|
|
if (null != vtTextChangedHandler)
|
|
{
|
|
try
|
|
{
|
|
vtTextChangedHandler(objRef, new VtTextChangedEventArgs(vt.Text));
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
log.Info("Internal error: VtTextChangedHandler exception: " + e.Message);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
///
|
|
/// MeasurementDataReceivedHandler handler
|
|
///
|
|
public event EventHandler<MeasuredDataEventArgs> MeasurementDataReceivedHandler;
|
|
|
|
/// <summary>
|
|
/// This method is called from within this class when measurement data were extracted.
|
|
/// </summary>
|
|
public void OnMeasurementDataReceived(object sender, MeasuredDataEventArgs msrmtData)
|
|
{
|
|
if (MeasurementDataReceivedHandler == null) return;
|
|
try { MeasurementDataReceivedHandler(sender, msrmtData); }
|
|
catch (Exception e) { log.Error("MeasurementDataReceivedHandler(...) failed", e); }
|
|
}
|
|
|
|
|
|
///
|
|
/// PromptReceviedEventHandler delegate and handler
|
|
///
|
|
public delegate void PromptReceivedEventHandler(object sender, PromptReceivedEventArgs e);
|
|
public event PromptReceivedEventHandler promptReceivedHandler;
|
|
|
|
/// <summary>
|
|
/// This method is called from within this class when
|
|
/// a prompt string was received from the telnet server.
|
|
/// </summary>
|
|
protected virtual void OnPromptReceived(string response)
|
|
{
|
|
Debug.WriteLine("TelnetClient:OnPromptReceived");
|
|
log.Info("Prompt received, response:" + Environment.NewLine + completeResponse);
|
|
|
|
if (null != promptReceivedHandler)
|
|
{
|
|
try
|
|
{
|
|
promptReceivedHandler(objRef, new PromptReceivedEventArgs(response));
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
log.Info("Internal error: PromptReceivedHandler exception: " + e.Message);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
///
|
|
/// NotificationEventHandler delegate and handler
|
|
///
|
|
public delegate void NotificationEventHandler(object sender, NotificationEventArgs e);
|
|
public event NotificationEventHandler notificationHandler;
|
|
|
|
/// <summary>
|
|
/// This method is called from within this class when the command processing loop
|
|
/// receives a Command with Action==NOTIFICATION. This can be used for synchronization.
|
|
/// </summary>
|
|
protected virtual void OnNotification(string notificationText)
|
|
{
|
|
Debug.WriteLine("TelnetClient:OnNotification");
|
|
log.Info("Notification: " + notificationText);
|
|
|
|
this.notificationText = notificationText;
|
|
if (null != notificationHandler)
|
|
{
|
|
try
|
|
{
|
|
notificationHandler(objRef, new NotificationEventArgs(notificationText));
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
log.Info("Internal error: NotificationHandler exception: " + e.Message);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
///
|
|
/// FatalEventHandler delegate and handler
|
|
///
|
|
public delegate void FatalEventHandler(object sender, FatalEventArgs e);
|
|
public event FatalEventHandler fatalErrorHandler;
|
|
|
|
/// <summary>
|
|
/// This method is called from within this class when a fatal error occurs.
|
|
/// </summary>
|
|
protected virtual void OnFatalError(string fatalErrorText)
|
|
{
|
|
Debug.WriteLine("TelnetClient:OnFatalError(" + fatalErrorText + ")");
|
|
log.FatalFormat("Fatal: {0}", fatalErrorText);
|
|
|
|
/// Skip the remaining command queue actions (except of TERMINATE)
|
|
CmdQueueClear();
|
|
|
|
this.fatalErrorText = fatalErrorText;
|
|
|
|
state = TelnetState.FatalError;
|
|
|
|
if (null != fatalErrorHandler)
|
|
{
|
|
try
|
|
{
|
|
fatalErrorHandler(objRef, new FatalEventArgs(fatalErrorText));
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
log.Info("Internal fatal: FatalHandler exception: " + e.Message);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
///
|
|
/// SummaryEventHandler delegate and handler
|
|
///
|
|
public delegate void SummaryEventHandler(object sender, SummaryEventArgs e);
|
|
public event SummaryEventHandler showSummaryHandler;
|
|
|
|
/// <summary>
|
|
/// This method is called from within this class when a error occurs.
|
|
/// Error does not interrupt the processing. It merely buffers the error.
|
|
/// The error is issued to UI when the queue is empty (the upgrade is complete)
|
|
/// </summary>
|
|
protected virtual void OnAddMessage(Category category, string messageText)
|
|
{
|
|
Debug.WriteLine("TelnetClient:OnAddMessage");
|
|
log.WarnFormat("Aux.Board Upgrade Item Nr.{0} {1}({2})", labelStr, category, messageText);
|
|
|
|
if (category == Category.Error) state = TelnetState.Error;
|
|
|
|
if (auxBrdUpgMessages == null)
|
|
{
|
|
auxBrdUpgMessages = new List<AuxBrdUpgItemMessage>();
|
|
}
|
|
auxBrdUpgMessages.Add(new AuxBrdUpgItemMessage(category, labelStr, messageText));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Issued the collected errors to the UI when the queue is empty (the upgrade is complete).
|
|
/// </summary>
|
|
protected virtual void IssueSummary()
|
|
{
|
|
if (state == TelnetState.BusyFinal || state == TelnetState.Inactive)
|
|
{
|
|
state = TelnetState.Inactive;
|
|
return;
|
|
}
|
|
|
|
if (state == TelnetState.Busy) state = TelnetState.Success; /// Success in case there wasn't an error so far
|
|
|
|
if (null != showSummaryHandler)
|
|
{
|
|
try
|
|
{
|
|
showSummaryHandler(objRef, new SummaryEventArgs(auxBrdUpgMessages));
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
log.Info("Internal error: SummaryEventHandler exception: " + e.Message);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|