Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
SoftwareUpdateClient.cs
1using System;
3using System.IO;
4using System.Text;
5using System.Threading.Tasks;
6using System.Xml;
7using Waher.Content;
10using Waher.Events;
13
15{
23 {
27 public const string Wildcard = "*";
28
29 private readonly string componentAddress;
30 private readonly string packageFolder;
31 private readonly Random rnd = new Random();
32
36 public const string NamespaceSoftwareUpdatesIeeeV1 = "urn:ieee:iot:swu:1.0";
37
41 public const string NamespaceSoftwareUpdatesNeuroFoundationV1 = "urn:nf:iot:swu:1.0";
42
47
51 public static readonly string[] NamespacesSoftwareUpdates = new string[]
52 {
55 };
56
67 : base(Client)
68 {
69 this.componentAddress = ComponentAddress;
70 this.packageFolder = PackageFolder;
71
72 if (!Directory.Exists(PackageFolder))
73 Directory.CreateDirectory(PackageFolder);
74
75 #region Neuro-Foundation V1
76
77 this.Client.RegisterMessageHandler("packageInfo", NamespaceSoftwareUpdatesNeuroFoundationV1, this.PackageNotificationHandler, true);
78 this.Client.RegisterMessageHandler("packageDeleted", NamespaceSoftwareUpdatesNeuroFoundationV1, this.PackageDeletedNotificationHandler, false);
79
80 #endregion
81
82 #region IEEE V1
83
84 this.Client.RegisterMessageHandler("packageInfo", NamespaceSoftwareUpdatesIeeeV1, this.PackageNotificationHandler, true);
85 this.Client.RegisterMessageHandler("packageDeleted", NamespaceSoftwareUpdatesIeeeV1, this.PackageDeletedNotificationHandler, false);
86
87 #endregion
88 }
89
91 public override void Dispose()
92 {
93 #region Neuro-Foundation V1
94
95 this.Client.UnregisterMessageHandler("packageInfo", NamespaceSoftwareUpdatesNeuroFoundationV1, this.PackageNotificationHandler, true);
96 this.Client.UnregisterMessageHandler("packageDeleted", NamespaceSoftwareUpdatesNeuroFoundationV1, this.PackageDeletedNotificationHandler, false);
97
98 #endregion
99
100 #region IEEE V1
101
102 this.Client.UnregisterMessageHandler("packageInfo", NamespaceSoftwareUpdatesIeeeV1, this.PackageNotificationHandler, true);
103 this.Client.UnregisterMessageHandler("packageDeleted", NamespaceSoftwareUpdatesIeeeV1, this.PackageDeletedNotificationHandler, false);
104
105 #endregion
106
107 base.Dispose();
108 }
109
113 public override string[] Extensions => new string[] { };
114
118 public string ComponentAddress => this.componentAddress;
119
123 public string PackageFolder => this.packageFolder;
124
131 public Task GetPackageInformation(string FileName, EventHandlerAsync<PackageEventArgs> Callback, object State)
132 {
133 StringBuilder Xml = new StringBuilder();
134
135 Xml.Append("<getPackageInfo xmlns='");
137 Xml.Append("' fileName='");
138 Xml.Append(XML.Encode(FileName));
139 Xml.Append("'/>");
140
141 return this.client.SendIqGet(this.componentAddress, Xml.ToString(), async (Sender, e) =>
142 {
143 XmlElement E = e.FirstElement;
144 Package PackageInfo = null;
145
146 if (e.Ok && !(E is null) && E.LocalName == "packageInfo")
147 PackageInfo = Package.Parse(E);
148 else
149 e.Ok = false;
150
151 PackageEventArgs e2 = new PackageEventArgs(PackageInfo, e);
152 await Callback.Raise(this, e2);
153
154 }, State);
155 }
156
163 public async Task<Package> GetPackageInformationAsync(string FileName)
164 {
165 TaskCompletionSource<Package> Result = new TaskCompletionSource<Package>();
166
167 await this.GetPackageInformation(FileName, (Sender, e) =>
168 {
169 if (e.Ok)
170 Result.TrySetResult(e.Package);
171 else
172 Result.TrySetException(e.StanzaError ?? new Exception("Unable to get package information."));
173
174 return Task.CompletedTask;
175
176 }, null);
177
178 return await Result.Task;
179 }
180
186 public Task GetPackagesInformation(EventHandlerAsync<PackagesEventArgs> Callback, object State)
187 {
188 StringBuilder Xml = new StringBuilder();
189
190 Xml.Append("<getPackages xmlns='");
192 Xml.Append("'/>");
193
194 return this.client.SendIqGet(this.componentAddress, Xml.ToString(), async (Sender, e) =>
195 {
196 XmlElement E = e.FirstElement;
197 Package[] PackagesInfo = null;
198
199 if (e.Ok && !(E is null) && E.LocalName == "packages")
200 {
201 List<Package> Packages = new List<Package>();
202
203 foreach (XmlNode N in E.ChildNodes)
204 {
205 if (N is XmlElement E2 && E2.LocalName == "packageInfo")
206 Packages.Add(Package.Parse(E2));
207 }
208
209 PackagesInfo = Packages.ToArray();
210 }
211 else
212 e.Ok = false;
213
214 PackagesEventArgs e2 = new PackagesEventArgs(PackagesInfo, e);
215 await Callback.Raise(this, e2);
216
217 }, State);
218 }
219
225 public async Task<Package[]> GetPackagesAsync()
226 {
227 TaskCompletionSource<Package[]> Result = new TaskCompletionSource<Package[]>();
228
229 await this.GetPackagesInformation((Sender, e) =>
230 {
231 if (e.Ok)
232 Result.TrySetResult(e.Packages);
233 else
234 Result.TrySetException(e.StanzaError ?? new Exception("Unable to get packages."));
235
236 return Task.CompletedTask;
237
238 }, null);
239
240 return await Result.Task;
241 }
242
250 public Task Subscribe(string FileName, EventHandlerAsync<IqResultEventArgs> Callback, object State)
251 {
252 StringBuilder Xml = new StringBuilder();
253
254 Xml.Append("<subscribe xmlns='");
255 Xml.Append(NamespaceSoftwareUpdatesCurrent);
256 Xml.Append("' fileName='");
257 Xml.Append(XML.Encode(FileName));
258 Xml.Append("'/>");
259
260 return this.client.SendIqSet(this.componentAddress, Xml.ToString(), Callback, State);
261 }
262
269 public async Task SubscribeAsync(string FileName)
270 {
271 TaskCompletionSource<bool> Result = new TaskCompletionSource<bool>();
272
273 await this.Subscribe(FileName, (Sender, e) =>
274 {
275 if (e.Ok)
276 Result.TrySetResult(true);
277 else
278 Result.TrySetException(e.StanzaError ?? new Exception("Unable to subscribe to software updates for " + FileName + "."));
279
280 return Task.CompletedTask;
281
282 }, null);
283
284 await Result.Task;
285 }
286
294 public Task Unsubscribe(string FileName, EventHandlerAsync<IqResultEventArgs> Callback, object State)
295 {
296 StringBuilder Xml = new StringBuilder();
297
298 Xml.Append("<unsubscribe xmlns='");
299 Xml.Append(NamespaceSoftwareUpdatesCurrent);
300 Xml.Append("' fileName='");
301 Xml.Append(XML.Encode(FileName));
302 Xml.Append("'/>");
303
304 return this.client.SendIqSet(this.componentAddress, Xml.ToString(), Callback, State);
305 }
306
313 public async Task UnsubscribeAsync(string FileName)
314 {
315 TaskCompletionSource<bool> Result = new TaskCompletionSource<bool>();
316
317 await this.Unsubscribe(FileName, (Sender, e) =>
318 {
319 if (e.Ok)
320 Result.TrySetResult(true);
321 else
322 {
323 Result.TrySetException(e.StanzaError ?? new Exception("Unable to unsubscribe from software updates for " + FileName + "."));
324 }
325
326 return Task.CompletedTask;
327
328 }, null);
329
330 await Result.Task;
331 }
332
338 public Task GetSubscriptions(EventHandlerAsync<SubscriptionsEventArgs> Callback, object State)
339 {
340 StringBuilder Xml = new StringBuilder();
341
342 Xml.Append("<getSubscriptions xmlns='");
343 Xml.Append(NamespaceSoftwareUpdatesCurrent);
344 Xml.Append("'/>");
345
346 return this.client.SendIqGet(this.componentAddress, Xml.ToString(), async (Sender, e) =>
347 {
348 XmlElement E = e.FirstElement;
349 string[] FileNames = null;
350
351 if (e.Ok && !(E is null) && E.LocalName == "subscriptions")
352 {
353 List<string> Subscriptions = new List<string>();
354
355 foreach (XmlNode N in E.ChildNodes)
356 {
357 if (N is XmlElement E2 && E2.LocalName == "subscription")
358 Subscriptions.Add(E2.InnerText);
359 }
360
361 FileNames = Subscriptions.ToArray();
362 }
363 else
364 e.Ok = false;
365
366 SubscriptionsEventArgs e2 = new SubscriptionsEventArgs(FileNames, e);
367 await Callback.Raise(this, e2);
368 }, State);
369 }
370
376 public async Task<string[]> GetSubscriptionsAsync()
377 {
378 TaskCompletionSource<string[]> Result = new TaskCompletionSource<string[]>();
379
380 await this.GetSubscriptions((Sender, e) =>
381 {
382 if (e.Ok)
383 Result.TrySetResult(e.FileNames);
384 else
385 Result.TrySetException(e.StanzaError ?? new Exception("Unable to get list of current subscriptions."));
386
387 return Task.CompletedTask;
388
389 }, null);
390
391 return await Result.Task;
392 }
393
394 private async Task PackageNotificationHandler(object Sender, MessageEventArgs e)
395 {
396 if (string.Compare(e.From, this.componentAddress, true) != 0)
397 {
398 this.client.Warning("Discarding package notification. Expected source: " + this.componentAddress + ". Actual source: " + e.From);
399 return;
400 }
401
402 Package PackageInfo = Package.Parse(e.Content);
403 PackageUpdatedEventArgs e2 = new PackageUpdatedEventArgs(PackageInfo, e);
404
405 await this.OnSoftwareUpdated.Raise(this, e2, false);
406
407 if (e2.Download)
408 {
409 Task _ = Task.Run(() => this.Download(PackageInfo, e));
410 }
411 }
412
413 private async Task Download(Package PackageInfo, MessageEventArgs e)
414 {
415 try
416 {
417 string FileName = await this.DownloadPackageAsync(PackageInfo);
418 PackageFileEventArgs e3 = new PackageFileEventArgs(PackageInfo, FileName, e);
419
420 if (!await this.OnSoftwareValidation.Raise(this, e3, false))
421 {
422 File.Delete(FileName);
423 Log.Warning("Package with invalid signature downloaded and deleted.", FileName);
424 return;
425 }
426
427 await this.OnSoftwareDownloaded.Raise(this, e3);
428 }
429 catch (Exception ex)
430 {
431 Log.Exception(ex, PackageInfo.FileName);
432 }
433 }
434
441 public Task<string> DownloadPackageAsync(Package PackageInfo)
442 {
443 return this.DownloadPackageAsync(PackageInfo, null);
444 }
445
453 public Task<string> DownloadPackageAsync(Package PackageInfo, TemporaryStream Destination)
454 {
455 return this.DownloadPackageAsync(PackageInfo, Destination, GetDefaultRemoteCertificateValidator(PackageInfo));
456 }
457
458 private static EventHandler<RemoteCertificateEventArgs> GetDefaultRemoteCertificateValidator(Package PackageInfo)
459 {
460 if (PackageInfo.Url.StartsWith("https://localhost:") ||
461 PackageInfo.Url.StartsWith("https://localhost/"))
462 {
463 return TrustLocalhost;
464 }
465 else
466 return null;
467 }
468
469 private static void TrustLocalhost(object Sender, RemoteCertificateEventArgs e)
470 {
471 e.IsValid = true;
472 }
473
482 public async Task<string> DownloadPackageAsync(Package PackageInfo, TemporaryStream Destination,
483 EventHandler<RemoteCertificateEventArgs> RemoteCertificateValidator)
484 {
485 string FileName = Path.Combine(this.packageFolder, PackageInfo.FileName);
486 int MaxRetryDelayMinutes = 5;
487
488 while (true)
489 {
490 string ErrorMessage;
491 int StatusCode;
492
493 try
494 {
496 new Uri(PackageInfo.Url), null, RemoteCertificateValidator, 120000, Destination);
497
498 if (Content.HasError)
499 {
500 if (Content.Error is WebException ex)
501 StatusCode = (int)ex.StatusCode;
502 else
503 StatusCode = (int)System.Net.HttpStatusCode.InternalServerError;
504
505 ErrorMessage = Content.Error.Message;
506 }
507 else
508 {
509 using Stream Output = File.Create(FileName);
510
511 Content.Encoded.Position = 0;
512 await Content.Encoded.CopyToAsync(Output);
513
514 return FileName;
515 }
516 }
517 catch (WebException ex)
518 {
519 StatusCode = (int)ex.StatusCode;
520 ErrorMessage = ex.Message;
521 }
522 catch (Exception ex)
523 {
524 StatusCode = (int)System.Net.HttpStatusCode.InternalServerError;
525 ErrorMessage = ex.Message;
526 }
527
528 if (StatusCode < 500)
529 {
530 throw new GenericException("Unable to download new package from server.",
531 null, PackageInfo.FileName, null, null, null, null, null,
532 new KeyValuePair<string, object>("Status Code", StatusCode),
533 new KeyValuePair<string, object>("Message", ErrorMessage));
534 }
535
536 int MsDelay;
537
538 lock (this.rnd)
539 {
540 MsDelay = this.rnd.Next(1000, MaxRetryDelayMinutes * 60000);
541
542 MaxRetryDelayMinutes <<= 1;
543 if (MaxRetryDelayMinutes > 1440)
544 MaxRetryDelayMinutes = 1440; // Try at least once a day.
545 }
546
547 await Task.Delay(MsDelay);
548 }
549 }
550
555 public event EventHandlerAsync<PackageUpdatedEventArgs> OnSoftwareUpdated = null;
556
567 public event EventHandlerAsync<PackageFileEventArgs> OnSoftwareValidation = null;
568
572 public event EventHandlerAsync<PackageFileEventArgs> OnSoftwareDownloaded = null;
573
574 private async Task PackageDeletedNotificationHandler(object Sender, MessageEventArgs e)
575 {
576 if (string.Compare(e.From, this.componentAddress, true) != 0)
577 return;
578
579 Package PackageInfo = Package.Parse(e.Content);
580 PackageDeletedEventArgs e2 = new PackageDeletedEventArgs(PackageInfo, e);
581 await this.OnSoftwareDeleted.Raise(this, e2, false);
582
583 if (e2.Delete)
584 {
585 try
586 {
587 string FileName = Path.Combine(this.packageFolder, PackageInfo.FileName);
588
589 if (File.Exists(FileName))
590 {
591 File.Delete(FileName);
592
593 await this.OnDownloadedSoftwareDeleted.Raise(this, new PackageFileEventArgs(PackageInfo, FileName, e));
594 }
595 }
596 catch (Exception ex)
597 {
598 Log.Exception(ex);
599 }
600 }
601 }
602
607 public event EventHandlerAsync<PackageDeletedEventArgs> OnSoftwareDeleted = null;
608
612 public event EventHandlerAsync<PackageFileEventArgs> OnDownloadedSoftwareDeleted = null;
613
614 }
615}
Contains information about a stream response to a content request.
TemporaryStream Encoded
Encoded object.
Exception class for web exceptions.
Definition: WebException.cs:11
HttpStatusCode StatusCode
HTTP Status Code of content.
Definition: WebException.cs:40
Static class managing encoding and decoding of internet content.
static Task< ContentStreamResponse > GetTempStreamAsync(Uri Uri, params KeyValuePair< string, string >[] Headers)
Gets a (possibly big) resource, given its URI.
Remove certificate validation event arguments.
Helps with common XML-related tasks.
Definition: XML.cs:21
static string Encode(string s)
Encodes a string for use in XML.
Definition: XML.cs:29
Generic exception, with meta-data for logging.
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:14
static void Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
Definition: Log.cs:1657
static void Warning(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs a warning event.
Definition: Log.cs:576
Event arguments for message events.
string From
From where the message was received.
XmlElement Content
Content of the message. For messages that are processed by registered message handlers,...
Event arguments for software package deletion events.
bool Delete
If the downloaded package is to be deleted. Default=true.
Event arguments for software package events.
Event arguments for software package file events.
Information about a software package.
Definition: Package.cs:11
static Package Parse(XmlElement Xml)
Parses a package information element.
Definition: Package.cs:97
string FileName
Name of software package file.
Definition: Package.cs:31
string Url
URL to download software package.
Definition: Package.cs:50
Event arguments for Software packages events.
Implements an XMPP interface for remote software updates.
string PackageFolder
Folder of downloaded packages.
const string NamespaceSoftwareUpdatesCurrent
Current namespace for software updates.
const string Wildcard
Subscribing to a wildcard allows you to receive events when any software package is updated.
Task< string > DownloadPackageAsync(Package PackageInfo, TemporaryStream Destination)
Downloads a software package.
Task Subscribe(string FileName, EventHandlerAsync< IqResultEventArgs > Callback, object State)
Subscribes to updates to a given software package.
static readonly string[] NamespacesSoftwareUpdates
Namespaces supported for software updates.
async Task< string[]> GetSubscriptionsAsync()
Gets current software update subscriptions.
async Task< Package[]> GetPackagesAsync()
Gets information about available software packages.
override void Dispose()
Disposes of the extension.
Task GetSubscriptions(EventHandlerAsync< SubscriptionsEventArgs > Callback, object State)
Gets current software update subscriptions.
async Task< Package > GetPackageInformationAsync(string FileName)
Gets information about a software package.
Task GetPackagesInformation(EventHandlerAsync< PackagesEventArgs > Callback, object State)
Gets information about available software packages.
SoftwareUpdateClient(XmppClient Client, string ComponentAddress, string PackageFolder)
Implements an XMPP interface for remote software updates.
const string NamespaceSoftwareUpdatesNeuroFoundationV1
urn:nf:iot:swu:1.0
Task GetPackageInformation(string FileName, EventHandlerAsync< PackageEventArgs > Callback, object State)
Gets information about a software package.
Task Unsubscribe(string FileName, EventHandlerAsync< IqResultEventArgs > Callback, object State)
Unsubscribes to updates to a given software package.
Task< string > DownloadPackageAsync(Package PackageInfo)
Downloads a software package.
async Task SubscribeAsync(string FileName)
Subscribes to updates to a given software package.
async Task< string > DownloadPackageAsync(Package PackageInfo, TemporaryStream Destination, EventHandler< RemoteCertificateEventArgs > RemoteCertificateValidator)
Downloads a software package.
override string[] Extensions
Implemented extensions.
async Task UnsubscribeAsync(string FileName)
Unsubscribes to updates to a given software package.
const string NamespaceSoftwareUpdatesIeeeV1
urn:ieee:iot:swu:1.0
Event arguments for subscription list responses responsess.
Manages an XMPP client connection. Implements XMPP, as defined in https://tools.ietf....
Definition: XmppClient.cs:58
bool UnregisterMessageHandler(string LocalName, string Namespace, EventHandlerAsync< MessageEventArgs > Handler, bool RemoveNamespaceAsClientFeature)
Unregisters a Message handler.
Definition: XmppClient.cs:2884
void RegisterMessageHandler(string LocalName, string Namespace, EventHandlerAsync< MessageEventArgs > Handler, bool PublishNamespaceAsClientFeature)
Registers a Message handler.
Definition: XmppClient.cs:2852
Task< uint > SendIqGet(string To, string Xml, EventHandlerAsync< IqResultEventArgs > Callback, object State)
Sends an IQ Get request.
Definition: XmppClient.cs:3598
Base class for XMPP Extensions.
XmppClient client
XMPP Client used by the extension.
void Exception(Exception Exception)
Called to inform the viewer of an exception state.
XmppClient Client
XMPP Client.
Manages a temporary stream. Contents is kept in-memory, if below a memory threshold,...
override async Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
Asynchronously reads the bytes from the current stream and writes them to another stream,...
Definition: ImplTypes.g.cs:58