Anytime.
- solutionsville
using System;
using System.Collections.Concurrent;
using System.Data.SqlClient;
using System.IO;
using System.Threading;
using WinSCP;
namespace CnaFirmware
{
public enum UpdateState
{
Idle, // black
Working, // black
Uploading,
// complete:
CompleteNoError, // green
// errors:
Aborted,
CompleteWithError // red
};
public class UploadTask
{
ConcurrentQueue<string> ConsoleQueue;
public string cna_ip;
string fingerprint;
readonly string ImageFileName;
public string CnaVersion;
readonly string TargetVersion;
public string CurrentFileName;
public double CurrentFileProgress;
private static string _lastFileName;
bool abort = false;
Session session;
Thread TaskThread;
readonly string LogFileName;
public string UpdatedFirmwareVersion;
public bool WarningShown;
// this string is for the user checking status during the update from the UI:
public string status;
// this status determines the color of the item in the list box
public UpdateState state;
// these allow the main form to track progress and repaint the item when done
public bool UpdateSuccess;
public bool JobComplete;
public bool NeedUiUpdate;
public bool Verbose;
// Set WinSCP DebugLogLevel Boolean
public bool debug;
// need states so the checkedlistbox knows what color to paint this.
// this class will push firmware to one CNA.
// inputs: IP address of CNA
// Fingerprint string
// image file (*.run) to upload
// queue for sending messages back to main (UI) thread
// status: idle
// transfering
// load (remote script)
// complete
// logging: only log start, errors, or success to the UI. everything goes to the log file for this cna.
// update 20180618: fingerprint is no longer required: WinSCP will scan for fingerprint, then upload firmware.
public UploadTask(string cnaip,
string fprint,
ConcurrentQueue<string> q,
string filename)
{
// DebugLogLevel=0 by default
debug = false;
cna_ip = cnaip;
WarningShown = false;
UpdateSuccess = false;
fingerprint = fprint;
ConsoleQueue = q;
ImageFileName = filename;
LogFileName = string.Format("{0}\\{1}.log", Globals.LogPath + @"\CNAip\", cna_ip);
// for checking file transfer progress:
CurrentFileName = string.Empty;
CurrentFileProgress = 0;
status = "idle";
state = UpdateState.Idle;
JobComplete = false;
NeedUiUpdate = false;
UpdatedFirmwareVersion = "";
// firmware version of target CNA
CnaVersion = "";
// new firmware version to be uploaded
TargetVersion = ExtractCnaVersion(filename);
}
// This is to extract the version of the Firmware file
string ExtractCnaVersion(string filename)
{
// ...CNA15xxvX_X_X.run 10,16
string v = "0.0.0";
string f = Path.GetFileName(filename);
int posv = f.IndexOf("v");
int posr = f.IndexOf(".run");
posv++;
try
{
v = f.Substring(posv, posr - posv);
}
catch (Exception) { }
return v.Replace("_", ".");
}
void _PostStatus(string s)
{
// set status string to what's going on.
ConsoleQueue.Enqueue(string.Format("!{0}: {1}", cna_ip, s));
}
void NewState(UpdateState s)
{
state = s;
NeedUiUpdate = true;
}
public void SetCnaVersion(byte[] data)
{
// Set Expected CNA version
if (data[0] < 2) return;
string v = string.Format("{0}.{1}.{2}", data[1], data[2], data[3]);
Log("SetCnaVersion:" + v);
CnaVersion = v;
}
public void Start()
{
// Start the Thread
TaskThread = new Thread(() => TaskThreadCode());
TaskThread.Start();
}
void Logui(string s)
{
// send to UI
ConsoleQueue.Enqueue(string.Format("{0}: {1}", cna_ip, s));
// and file it
Log(s);
}
void Log(string s)
{
Directory.CreateDirectory(Globals.LogPath + @"\CNAip");
File.AppendAllText(LogFileName, string.Format("{0} {1}: {2}{3}", DateTime.Now.ToString("yyyyMMdd.HHmmss.fff"), cna_ip, s, Environment.NewLine));
}
// Upload Status and progress
public string GetStatus()
{
string s = "";
if (status == "uploading binary")
{
s = string.Format("{0}: uploading binary ({1:P0})", cna_ip, CurrentFileProgress);
}
else
{
s = status;
s = string.Format("{0}: {1}", cna_ip, status);
}
return s;
}
public void TaskThreadCode()
{
Logui("Starting update");
NewState(UpdateState.Working);
try
{
// first get the CNA SSH fingerprint
SessionOptions sessionOptions = new SessionOptions
{
Protocol = Protocol.Scp,
HostName = cna_ip
};
sessionOptions.AddRawSettings("KEX", "ecdh,dh-gex-sha1,dh-group14-sha1,dh-group1-sha1,rsa,WARN");
//Session session;
using (session = new Session())
{
fingerprint = session.ScanFingerprint(sessionOptions, "MD5");
Log($"Fingerprint: {fingerprint}");
}
// Set up session options for firmware transfer
// The same configuration is used on all devices
sessionOptions = new SessionOptions
{
Protocol = Protocol.Scp,
HostName = cna_ip,
UserName = "root",
Password = "root",
SshHostKeyFingerprint = fingerprint
};
// This is the same for all connections
sessionOptions.AddRawSettings("AuthGSSAPI", "1");
sessionOptions.AddRawSettings("Cipher", "aes,blowfish,3des,chacha20,WARN,arcfour,des");
sessionOptions.AddRawSettings("KEX", "ecdh,dh-gex-sha1,dh-group14-sha1,dh-group1-sha1,rsa,WARN");
status = "uploading binary";
using (session = new Session())
{
try
{
fingerprint = session.ScanFingerprint(sessionOptions, "MD5");
Log($"Fingerprint: {fingerprint}");
// Will continuously report progress of transfer
Directory.CreateDirectory(Globals.LogPath + @"\Debug");
// If MenuItem is checked then DebugLogLevel = 2, else Default of 0.
// also sets the DebugLogPath and SessionLogPath values.
if (debug == true)
{
session.DebugLogLevel = 2;
session.DebugLogPath = Globals.LogPath + @"\Debug\WSCPDebug.log";
session.SessionLogPath = Globals.LogPath + @"\Debug\WSCPSession.log";
}
else
{
session.DebugLogLevel = 0;
session.DebugLogPath = null;
session.SessionLogPath = null;
}
session.FileTransferProgress += SessionFileTransferProgress;
session.OutputDataReceived += SessionOutputDataReceived;
// session.DisableVersionCheck = true;
// Connect
Log("Opening session");
session.Open(sessionOptions);
// Upload files
TransferOptions transferOptions = new TransferOptions()
{
TransferMode = TransferMode.Binary
};
// Transfer Results
TransferOperationResult transferResult;
transferResult = session.PutFiles(ImageFileName, "/var/tmp/*", false, transferOptions);
}
catch (Exception ex)
{
Logui("WinSCP: " + ex.Message);
ConsoleQueue.Enqueue("@" + ex.Message);
status = "WinSCP Error";
NewState(UpdateState.CompleteWithError);
JobComplete = true;
_PostStatus("Failed");
// terminate thread
return;
}
if (abort) session.Close();
// Write to log upload status
Log("File transfer succeeded");
// PutFiles has completed: now run bash script.
status = "waiting for remote load";
// Change to the remote directory.
session.ExecuteCommand("cd /tmp");
// Modify the permissions on the *.run file.
session.ExecuteCommand("chmod 777 *.run");
// Log entry status update.
Log("Running remote command: load");
// This command will display the completion of the Load command results, including version information.
session.ExecuteCommand(string.Format("./{0} --load", Path.GetFileName(ImageFileName)));
// note: you should never get here!
// Eprom load completed, executing reboot.
status = "remote load complete";
// Add entery to log that session is closing.
Log("Closing WinSCP session");
// Close the session.
session.Close();
}
}
catch (Exception e)
{
// either the update was aborted by the user, or an error occurred, or the upload was successful.
// so - this thread will always exit from here.
if (e.Message == "Aborted.")
{
// Abort the Threads.
if (abort)
{
// user abort
status = "User abort";
NewState(UpdateState.Idle);
Logui("Update cancelled by user");
}
else
{
// normal (success)
if (UpdateSuccess)
{
status = "updating database";
Log("Updating database record");
UpdateDbRecord();
UpdatedFirmwareVersion = TargetVersion;
Logui("Update successful");
NewState(UpdateState.CompleteNoError);
status = "CNA update complete";
}
}
}
else
{
// a real error occurred, and this thread will exit with an error status.
status = "Error";
NewState(UpdateState.CompleteWithError);
Logui(string.Format("Error: {0}", e.Message));
}
}
JobComplete = true;
}
void SessionFileTransferProgress(object sender, FileTransferProgressEventArgs e)
{
// This displays the file transfer status percentage.
if (abort) session.Abort();
//New line for every new file
if ((_lastFileName != null) && (_lastFileName != e.FileName))
{
Log(string.Format("Uploading: {0}", e.FileName));
}
// Print transfer progress
_PostStatus(string.Format("Uploading: {0:P0}", e.FileProgress));
// Remember a name of the last file reported
_lastFileName = e.FileName;
CurrentFileProgress = e.FileProgress;
}
void SessionOutputDataReceived(object sender, OutputDataReceivedEventArgs e)
{
if (abort) session.Abort();
if (
e.Data.Contains("Starting")
|| e.Data.Contains("Opening")
|| e.Data.Contains("Searching")
|| e.Data.Contains("Connecting")
|| e.Data.Contains("Updating")
|| e.Data.Contains("Validating")
|| e.Data.Contains("Authenticating...")
|| e.Data == "File transfer succeeded"
|| e.Data == "Valid firmware"
|| e.Data == "Update successful"
)
_PostStatus(e.Data);
Log(e.Data);
if (e.Data.Contains("Updating CNA") && e.Data.Contains("success"))
{
UpdateSuccess = true;
_PostStatus("Firmware update was successful.");
// done: abort the session now
session.Abort();
}
}
public void Cancel()
{
abort = true;
//logui("Upload Cancelled by User!");
}
void UpdateDbRecord()
{
// This routine is for updating the Firmware version number in the nms_cfg database.
SqlConnection NmsRt;
using (NmsRt = new SqlConnection(Globals.ConnectionString))
{
try
{
NmsRt.Open();
}
catch (Exception)
{
Logui("Unable to open NMS database.");
return;
}
string query = string.Format("update cna_firmware set firmware_rev = '{0}' where ip_address = '{1}'", TargetVersion, cna_ip);
try
{
using (SqlCommand sql = new SqlCommand(query, NmsRt)) sql.ExecuteNonQuery();
}
catch (Exception ex)
{
Logui("UpdateDbRecord: " + ex.Message);
}
}
}
}
}