Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
GatewayService.cs
1using System;
2using System.Collections.Generic;
3using System.IO;
4using System.Runtime.InteropServices;
5using System.ServiceProcess;
6using System.Text;
7using System.Threading;
8using System.Threading.Tasks;
9using Waher.Content;
11using Waher.Events;
16
17#pragma warning disable CA1416 // Validate platform compatibility
18
20{
24 public class GatewayService : ServiceBase
25 {
26 private bool autoPaused = false;
27 private bool starting = false;
28
34 public GatewayService(string ServiceName, string InstanceName)
35 : base()
36 {
37 this.ServiceName = ServiceName;
38 if (!string.IsNullOrEmpty(InstanceName))
39 this.ServiceName += " " + InstanceName;
40
41 this.AutoLog = true;
42 this.CanHandlePowerEvent = true;
43 this.CanHandleSessionChangeEvent = true;
44 this.CanPauseAndContinue = true;
45 this.CanShutdown = true;
46 this.CanStop = true;
47 }
48
49 protected override void OnStart(string[] args)
50 {
51 try
52 {
53 bool Started;
54
55 if (this.starting)
56 Started = false;
57 else
58 {
59 using PendingTimer Timer = new(this);
60
61 Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory);
62
63 Gateway.GetDatabaseProvider += Program.GetDatabase;
64 Gateway.RegistrationSuccessful += Program.RegistrationSuccessful;
65 Gateway.OnTerminate += this.TerminateService;
66
67 this.starting = true;
68 try
69 {
70 Started = Gateway.Start(false, true, Program.InstanceName).Result;
71 }
72 finally
73 {
74 this.starting = false;
75 }
76 }
77
78 if (!Started)
79 {
80 Log.Alert("Gateway being started in another process.");
81 ThreadPool.QueueUserWorkItem(_ => this.Stop());
82 return;
83 }
84 }
85 catch (Exception ex)
86 {
87 this.ExitCode = 1;
88 Log.Alert(ex);
89 }
90 }
91
92 private Task TerminateService(object Sender, EventArgs e)
93 {
94 this.ExitCode = 1;
95 ThreadPool.QueueUserWorkItem(_ => this.Stop());
96 return Task.CompletedTask;
97 }
98
99 private class PendingTimer : IDisposable
100 {
101 private readonly GatewayService service;
102 private Timer timer;
103 private bool disposed = false;
104
105 public PendingTimer(GatewayService Service)
106 {
107 this.service = Service;
108 this.timer = new Timer(this.MoreTime, null, 0, 1000);
109 }
110
111 public void Dispose()
112 {
113 this.disposed = true;
114 this.timer?.Dispose();
115 this.timer = null;
116 }
117
118 private void MoreTime(object State)
119 {
120 if (!this.disposed)
121 {
122 try
123 {
124 this.service.RequestAdditionalTime(2000);
125 }
126 catch (InvalidOperationException)
127 {
128 this.timer?.Dispose();
129 this.timer = null;
130 }
131 catch (Exception)
132 {
133 // Ignore
134 }
135 }
136 }
137 }
138
139 protected override void OnPause()
140 {
141 this.OnStop();
142 }
143
144 protected override void OnContinue()
145 {
146 try
147 {
148 bool Started;
149
150 if (this.starting)
151 Started = false;
152 else
153 {
154 using PendingTimer Timer = new(this);
155
156 this.starting = true;
157 try
158 {
159 Started = Gateway.Start(false, true, Program.InstanceName).Result;
160 }
161 finally
162 {
163 this.starting = false;
164 }
165 }
166
167 if (!Started)
168 {
169 Log.Alert("Gateway being started in another process.");
170 ThreadPool.QueueUserWorkItem(_ => this.Stop());
171 return;
172 }
173 }
174 catch (Exception ex)
175 {
176 this.ExitCode = 1;
177 Log.Alert(ex);
178 }
179 }
180
181 protected override bool OnPowerEvent(PowerBroadcastStatus powerStatus)
182 {
183 switch (powerStatus)
184 {
185 case PowerBroadcastStatus.BatteryLow:
186 case PowerBroadcastStatus.OemEvent:
187 case PowerBroadcastStatus.PowerStatusChange:
188 case PowerBroadcastStatus.QuerySuspend:
189 Flush();
190 return true;
191
192 case PowerBroadcastStatus.ResumeAutomatic:
193 case PowerBroadcastStatus.ResumeCritical:
194 case PowerBroadcastStatus.ResumeSuspend:
195 if (this.autoPaused)
196 {
197 this.autoPaused = false;
198
199 if (this.starting)
200 Log.Warning("Gateway is in the process of starting, called from another source.");
201 else
202 {
203 Log.Notice("Resuming service.");
204 this.OnContinue();
205 }
206 }
207 return true;
208
209 case PowerBroadcastStatus.Suspend:
210 this.autoPaused = true;
211 Log.Notice("Suspending service.");
212 this.OnStop();
213 return true;
214
215 case PowerBroadcastStatus.QuerySuspendFailed:
216 default:
217 return true;
218 }
219 }
220
221 protected override void OnSessionChange(SessionChangeDescription ChangeDescription)
222 {
223 int SessionId = ChangeDescription.SessionId;
224 List<KeyValuePair<string, object>> Tags = new()
225 {
226 new("SessionId", SessionId),
227 new("Domain", Gateway.Domain?.Value ?? "N/A")
228 };
229
230 AddWtsUserName(Tags, SessionId);
231 AddWtsName(Tags, "Initial Program", SessionId, WtsInfoClass.WTSInitialProgram);
232 AddWtsName(Tags, "Application Name", SessionId, WtsInfoClass.WTSApplicationName);
233 AddWtsName(Tags, "Working Directory", SessionId, WtsInfoClass.WTSWorkingDirectory);
234 AddWtsName(Tags, "OEM ID", SessionId, WtsInfoClass.WTSOEMId);
235 AddWtsName(Tags, "Station Name", SessionId, WtsInfoClass.WTSWinStationName);
236 AddWtsName(Tags, "Connect State", SessionId, WtsInfoClass.WTSConnectState);
237 AddWtsName(Tags, "Client Build Number", SessionId, WtsInfoClass.WTSClientBuildNumber);
238 AddWtsName(Tags, "Client Name", SessionId, WtsInfoClass.WTSClientName);
239 AddWtsName(Tags, "Client Directory", SessionId, WtsInfoClass.WTSClientDirectory);
240 AddWtsName(Tags, "Client Product ID", SessionId, WtsInfoClass.WTSClientProductId);
241 AddWtsName(Tags, "Client Hardware ID", SessionId, WtsInfoClass.WTSClientHardwareId);
242 AddWtsName(Tags, "Client Address", SessionId, WtsInfoClass.WTSClientAddress);
243 AddWtsName(Tags, "Client Display", SessionId, WtsInfoClass.WTSClientDisplay);
244 AddWtsName(Tags, "Client Protocol Type", SessionId, WtsInfoClass.WTSClientProtocolType);
245 AddWtsName(Tags, "Idle Time", SessionId, WtsInfoClass.WTSIdleTime);
246 AddWtsName(Tags, "Logon Time", SessionId, WtsInfoClass.WTSLogonTime);
247 AddWtsName(Tags, "Incoming Bytes", SessionId, WtsInfoClass.WTSIncomingBytes);
248 AddWtsName(Tags, "Outgoing Bytes", SessionId, WtsInfoClass.WTSOutgoingBytes);
249 AddWtsName(Tags, "Incoming Frames", SessionId, WtsInfoClass.WTSIncomingFrames);
250 AddWtsName(Tags, "Outgoing Frames", SessionId, WtsInfoClass.WTSOutgoingFrames);
251 AddWtsName(Tags, "Client Info", SessionId, WtsInfoClass.WTSClientInfo);
252 AddWtsName(Tags, "Session Info", SessionId, WtsInfoClass.WTSSessionInfo);
253
254 string Message;
255
256 switch (ChangeDescription.Reason)
257 {
258 case SessionChangeReason.ConsoleConnect:
259 Message = "User connected to machine via console interface.";
260 break;
261
262 case SessionChangeReason.ConsoleDisconnect:
263 Message = "User disconnected console interface.";
264 break;
265
266 case SessionChangeReason.RemoteConnect:
267 Message = "User connected remotely to machine.";
268 break;
269
270 case SessionChangeReason.RemoteDisconnect:
271 Message = "User disconnected remote interface.";
272 break;
273
274 case SessionChangeReason.SessionLock:
275 Message = "User session locked.";
276 break;
277
278 case SessionChangeReason.SessionLogoff:
279 Message = "User logged off.";
280 break;
281
282 case SessionChangeReason.SessionLogon:
283 Message = "User logged on.";
284 break;
285
286 case SessionChangeReason.SessionRemoteControl:
287 Message = "User remote control status of session has changed.";
288 break;
289
290 case SessionChangeReason.SessionUnlock:
291 Message = "User session unlocked.";
292 break;
293
294 default:
295 Tags.Add(new KeyValuePair<string, object>("Reason", ChangeDescription.Reason.ToString()));
296 Message = "Session changed.";
297 break;
298 }
299
301 Log.Notice(Message, Tags.ToArray());
302 else
303 {
304 if ((Setup.NotificationConfiguration.Instance.Addresses?.Length ?? 0) == 0)
305 Log.Alert(Message, Tags.ToArray());
306 else
307 {
308 Log.Notice(Message, Tags.ToArray());
309
310 StringBuilder Markdown = new();
311
312 Markdown.AppendLine(MarkdownDocument.Encode(Message));
313 Markdown.AppendLine();
314 Markdown.AppendLine("| Details ||");
315 Markdown.AppendLine("|:----|:---|");
316
317 foreach (KeyValuePair<string, object> Tag in Tags)
318 {
319 Markdown.Append("| ");
320 Markdown.Append(MarkdownDocument.Encode(Tag.Key));
321 Markdown.Append(" | ");
322 Markdown.Append(MarkdownDocument.Encode(Tag.Value?.ToString() ?? string.Empty));
323 Markdown.AppendLine(" |");
324 }
325
326 Gateway.SendNotification(Markdown.ToString());
327 }
328 }
329 }
330
331 private static void AddWtsUserName(List<KeyValuePair<string, object>> Tags, int SessionId)
332 {
333 string Value = GetUserName(SessionId);
334 if (!string.IsNullOrEmpty(Value))
335 Tags.Add(new KeyValuePair<string, object>("User Name", Value));
336 }
337
338 private static void AddWtsName(List<KeyValuePair<string, object>> Tags, string Key, int SessionId, WtsInfoClass InfoClass)
339 {
340 string Value = GetWtsName(SessionId, InfoClass);
341 if (!string.IsNullOrEmpty(Value))
342 Tags.Add(new KeyValuePair<string, object>(Key, Value));
343 }
344
345 private static string GetUserName(int SessionId)
346 {
347 try
348 {
349 string UserName = GetWtsName(SessionId, WtsInfoClass.WTSUserName);
350 if (string.IsNullOrEmpty(UserName))
351 return null;
352
353 string Domain = GetWtsName(SessionId, WtsInfoClass.WTSDomainName);
354 if (!string.IsNullOrEmpty(Domain))
355 UserName = Domain + "\\" + UserName;
356
357 return UserName;
358 }
359 catch (Exception ex)
360 {
361 Log.Exception(ex);
362 return null;
363 }
364 }
365
366 private static string GetWtsName(int SessionId, WtsInfoClass InfoClass)
367 {
368 string Result;
369
370 try
371 {
372 if (Win32.WTSQuerySessionInformation(IntPtr.Zero, SessionId, InfoClass, out IntPtr Buffer, out int StrLen) && StrLen > 1)
373 {
374 try
375 {
376 Result = Marshal.PtrToStringAnsi(Buffer);
377 Win32.WTSFreeMemory(Buffer);
378 Buffer = IntPtr.Zero;
379 }
380 finally
381 {
382 if (Buffer != IntPtr.Zero)
383 Win32.WTSFreeMemory(Buffer);
384 }
385 }
386 else
387 return null;
388 }
389 catch (Exception ex)
390 {
391 Log.Exception(ex);
392 return null;
393 }
394
395 if (!string.IsNullOrEmpty(Result))
396 Result = CommonTypes.Escape(Result, specialCharactersToEscape, specialCharacterEscapes);
397
398 return Result;
399 }
400
401 private static readonly char[] specialCharactersToEscape = new char[]
402 {
403 '\x00',
404 '\x01',
405 '\x02',
406 '\x03',
407 '\x04',
408 '\x05',
409 '\x06',
410 '\a', // 7 - 0x07
411 '\b', // 8 - 0x08
412 '\n', // 10 - 0x0a
413 '\v', // 11 - 0x0b
414 '\f', // 12 - 0x0c
415 '\r', // 13 - 0x0d
416 '\x0e',
417 '\x0f',
418 '\x10',
419 '\x11',
420 '\x12',
421 '\x13',
422 '\x14',
423 '\x15',
424 '\x16',
425 '\x17',
426 '\x18',
427 '\x19',
428 '\x1a',
429 '\x1b',
430 '\x1c',
431 '\x1d',
432 '\x1e',
433 '\x1f'
434 };
435 private static readonly string[] specialCharacterEscapes = new string[]
436 {
437 "<NUL>", // '\x00',
438 "<SOH>", // '\x01',
439 "<STX>", // '\x02',
440 "<ETX>", // '\x03',
441 "<EOT>", // '\x04',
442 "<ENQ>", // '\x05',
443 "<ACK>", // '\x06',
444 "<BEL>", // '\a', // 7 - 0x07
445 "<BS>", // '\b', // 8 - 0x08
446 "<LF>", // '\n', // 10 - 0x0a
447 "<VT>", // '\v', // 11 - 0x0b
448 "<FF>", // '\f', // 12 - 0x0c
449 "<CR>", // '\r', // 13 - 0x0d
450 "<SO>", // '\x0e',
451 "<SI>", // '\x0f',
452 "<DLE>", // '\x10',
453 "<DC1>", // '\x11',
454 "<DC2>", // '\x12',
455 "<DC3>", // '\x13',
456 "<DC4>", // '\x14',
457 "<NAK>", // '\x15',
458 "<SYN>", // '\x16',
459 "<ETB>", // '\x17',
460 "<CAN>", // '\x18',
461 "<EM>", // '\x19',
462 "<SUB>", // '\x1a',
463 "<ESC>", // '\x1b',
464 "<FS>", // '\x1c',
465 "<GS>", // '\x1d',
466 "<RS>", // '\x1e',
467 "<US>" // '\x1f'
468 };
469
470
471 protected override void OnShutdown()
472 {
473 Log.Notice("System is shutting down.");
474 this.Stop();
475 }
476
477 protected override void OnStop()
478 {
479 Log.Notice("Service is being stopped.");
480 try
481 {
482 using PendingTimer Timer = new(this);
483
484 Flush();
485 Gateway.Stop().Wait();
486 Log.Terminate();
487 }
488 catch (Exception ex)
489 {
490 Log.Alert(ex);
491 }
492 }
493
494 private static void Flush()
495 {
497 Database.Provider.Flush().Wait();
498
500 Ledger.Provider.Flush().Wait();
501 }
502
503 protected override void OnCustomCommand(int command)
504 {
506 }
507 }
508}
509
510#pragma warning restore CA1416 // Validate platform compatibility
Helps with parsing of commong data types.
Definition: CommonTypes.cs:13
static string Escape(string s, char[] CharactersToEscape, string EscapeSequence)
Escapes a set of characters in a string.
Definition: CommonTypes.cs:828
Contains a markdown document. This markdown document class supports original markdown,...
static string Encode(string s)
Encodes all special characters in a string so that it can be included in a markdown document without ...
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:13
static void Terminate()
Must be called when the application is terminated. Stops all event sinks that have been registered.
Definition: Log.cs:90
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:1647
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:566
static void Notice(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs a notice event.
Definition: Log.cs:450
static void Alert(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs an alert event.
Definition: Log.cs:1227
Static class managing the runtime environment of the IoT Gateway.
Definition: Gateway.cs:126
static CaseInsensitiveString Domain
Domain name.
Definition: Gateway.cs:2354
static Task< bool > Start(bool ConsoleOutput)
Starts the gateway.
Definition: Gateway.cs:218
static async Task Stop()
Stops the gateway.
Definition: Gateway.cs:2187
static Task SendNotification(Graph Graph)
Sends a graph as a notification message to configured notification recipients.
Definition: Gateway.cs:3826
static async Task< bool > ExecuteServiceCommand(int CommandNr)
Executes a service command.
Definition: Gateway.cs:3349
GatewayService(string ServiceName, string InstanceName)
Gateway Service
IoT Gateway Windows Service Application.
Definition: Program.cs:48
Handles interaction with Windows Service API.
Definition: Win32.cs:14
Represents a case-insensitive string.
string Value
String-representation of the case-insensitive string. (Representation is case sensitive....
static bool IsNullOrEmpty(CaseInsensitiveString value)
Indicates whether the specified string is null or an CaseInsensitiveString.Empty string.
Static interface for database persistence. In order to work, a database provider has to be assigned t...
Definition: Database.cs:19
static bool HasProvider
If a database provider is registered.
Definition: Database.cs:79
static IDatabaseProvider Provider
Registered database provider.
Definition: Database.cs:57
Static interface for ledger persistence. In order to work, a ledger provider has to be assigned to it...
Definition: Ledger.cs:14
static bool HasProvider
If a ledger provider is registered.
Definition: Ledger.cs:105
static ILedgerProvider Provider
Registered ledger provider.
Definition: Ledger.cs:83
Task Flush()
Persists any pending changes.
Task Flush()
Persists any pending changes.
WtsInfoClass
Windows Terminal Services Infomration class.
Definition: WtsInfoClass.cs:7