Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
XmppActor.cs
1using System;
2using System.Collections.Generic;
3using System.Reflection;
4using System.Threading.Tasks;
5using System.Xml;
16
18{
22 public abstract class XmppActor : Actor
23 {
27 public const string XmppNamespace = "http://lab.tagroot.io/Schema/ComSim/XMPP.xsd";
28
32 public const string XmppSchema = "TAG.Simulator.XMPP.Schema.ComSimXmpp.xsd";
33
34 private XmppClient client;
35 private ISniffer sniffer;
36 private AccountCredentials credentials;
37 private XmppCredentials xmppCredentials;
38 private string domain;
39 private string host;
40 private int? port;
41 private string userName;
42 private string apiKey;
43 private string secret;
44 private bool alwaysConnected;
45 private bool allowCramMD5;
46 private bool allowDigestMD5;
47 private bool allowEncryption;
48 private bool allowPlain;
49 private bool allowScramSHA1;
50 private bool allowScramSHA256;
51 private bool requestRosterOnStartup;
52 private bool trustServer;
53 private bool isOnline = false;
54
61 : base(Parent, Model)
62 {
63 }
64
74 {
75 }
76
80 public override string Namespace => XmppNamespace;
81
85 public override string SchemaResource => XmppSchema;
86
90 public bool IsOnline => this.isOnline;
91
95 public bool TrustServer => this.trustServer;
96
100 public string Domain => this.domain;
101
105 public string Host => this.host;
106
110 public XmppClient Client => this.client;
111
116 public override Task FromXml(XmlElement Definition)
117 {
118 this.domain = XML.Attribute(Definition, "domain");
119 this.userName = XML.Attribute(Definition, "userName");
120 this.apiKey = XML.Attribute(Definition, "apiKey");
121 this.secret = XML.Attribute(Definition, "secret");
122 this.alwaysConnected = XML.Attribute(Definition, "alwaysConnected", false);
123
124 if (Definition.HasAttribute("port"))
125 this.port = XML.Attribute(Definition, "port", 0);
126 else
127 this.port = null;
128
129 this.allowCramMD5 = XML.Attribute(Definition, "allowCramMD5", false);
130 this.allowDigestMD5 = XML.Attribute(Definition, "allowDigestMD5", false);
131 this.allowEncryption = XML.Attribute(Definition, "allowEncryption", true);
132 this.allowPlain = XML.Attribute(Definition, "allowPlain", false);
133 this.allowScramSHA1 = XML.Attribute(Definition, "allowScramSHA1", true);
134 this.allowScramSHA256 = XML.Attribute(Definition, "allowScramSHA256", true);
135 this.requestRosterOnStartup = XML.Attribute(Definition, "requestRosterOnStartup", true);
136 this.trustServer = XML.Attribute(Definition, "trustServer", false);
137
138 return base.FromXml(Definition);
139 }
140
144 public override async Task Initialize()
145 {
146 if (this.port.HasValue)
147 this.host = this.domain;
148 else
149 {
150 try
151 {
152 SRV SRV = await DnsResolver.LookupServiceEndpoint(this.domain, "xmpp-client", "tcp");
153 if (!(SRV is null) && !string.IsNullOrEmpty(SRV.TargetHost) && SRV.Port > 0)
154 {
155 this.host = SRV.TargetHost;
156 this.port = SRV.Port;
157 }
158 }
159 catch (Exception)
160 {
161 this.host = this.domain;
162 this.port = 5222;
163 }
164 }
165
166 await base.Initialize();
167 }
168
177 public override Task<Actor> CreateInstanceAsync(int InstanceIndex, string InstanceId)
178 {
179 XmppActor Result = this.CreateInstanceObject(InstanceIndex, InstanceId);
180
181 Result.domain = this.domain;
182 Result.host = this.host;
183 Result.port = this.port;
184 Result.userName = this.userName + InstanceIndex.ToString();
185 Result.apiKey = this.apiKey;
186 Result.secret = this.secret;
187 Result.alwaysConnected = this.alwaysConnected;
188 Result.allowCramMD5 = this.allowCramMD5;
189 Result.allowDigestMD5 = this.allowDigestMD5;
190 Result.allowEncryption = this.allowEncryption;
191 Result.allowPlain = this.allowPlain;
192 Result.allowScramSHA1 = this.allowScramSHA1;
193 Result.allowScramSHA256 = this.allowScramSHA256;
194 Result.requestRosterOnStartup = this.requestRosterOnStartup;
195 Result.trustServer = this.trustServer;
196
197 return Task.FromResult<Actor>(Result);
198 }
199
209
213 public override async Task InitializeInstance()
214 {
215 this.xmppCredentials = await this.GetInstanceCredentials();
216
217 this.sniffer = this.Model.GetSniffer(this.userName);
218
219 if (this.sniffer is null)
220 this.client = new XmppClient(this.xmppCredentials, "en", typeof(XmppActor).GetTypeInfo().Assembly);
221 else
222 this.client = new XmppClient(this.xmppCredentials, "en", typeof(XmppActor).GetTypeInfo().Assembly, this.sniffer);
223
224 this.client.OnStateChanged += this.Client_OnStateChanged;
225 this.client.OnChatMessage += this.Client_OnChatMessage;
226 this.client.OnConnectionError += this.Client_OnConnectionError;
227 this.client.OnError += this.Client_OnError;
228 this.client.OnErrorMessage += this.Client_OnErrorMessage;
229 this.client.OnGroupChatMessage += this.Client_OnGroupChatMessage;
230 this.client.OnHeadlineMessage += this.Client_OnHeadlineMessage;
231 this.client.OnNormalMessage += this.Client_OnNormalMessage;
232 this.client.OnPresence += this.Client_OnPresence;
233 this.client.OnPresenceSubscribe += this.Client_OnPresenceSubscribe;
234 this.client.OnRosterItemAdded += this.Client_OnRosterItemAdded;
235 this.client.OnRosterItemRemoved += this.Client_OnRosterItemRemoved;
236 this.client.OnRosterItemUpdated += this.Client_OnRosterItemUpdated;
237 this.client.CustomPresenceXml += this.Client_CustomPresenceXml;
238
239 string InstanceIndexSuffix = this.InstanceIndex.ToString();
240 int c = this.N.ToString().Length;
241 int Nr0 = c - InstanceIndexSuffix.Length;
242
243 if (Nr0 > 0)
244 InstanceIndexSuffix = new string('0', Nr0) + InstanceIndexSuffix;
245
246 if (this.Parent is IActor ParentActor)
247 {
248 foreach (ISimulationNode Node in ParentActor.Children)
249 {
250 if (Node is HandlerNode HandlerNode)
251 HandlerNode.RegisterHandlers(this, this.client);
252
253 if (Node is Extensions.IXmppExtension Extension)
254 {
255 object ExtensionObj = await Extension.Add(this, this.Client);
256
257 if (!string.IsNullOrEmpty(Extension.Id))
258 this.Model.Variables[Extension.Id + InstanceIndexSuffix] = ExtensionObj;
259 }
260 }
261 }
262
263 if (this.alwaysConnected)
264 {
265 await this.client.Connect(this.domain);
266
267 if (this.xmppCredentials.AllowRegistration)
268 {
269 switch (await this.client.WaitStateAsync(30000, XmppState.Connected, XmppState.Error, XmppState.Offline))
270 {
271 case 0: // Connected
272 break;
273
274 case 1: // Error
275 case 2: // Offline
276 default:
277 throw new Exception("Unable to create account for " + this.userName + "@" + this.domain);
278 }
279 }
280 }
281 }
282
283 private Task Client_CustomPresenceXml(object Sender, CustomPresenceEventArgs e)
284 {
285 if (this.client.TryGetTag("CutomPresenceXML", out object Obj) &&
286 Obj is string Xml)
287 {
288 e.Stanza.Append(Xml);
289 }
290
291 return Task.CompletedTask;
292 }
293
294 private Task Client_OnRosterItemUpdated(object Sender, RosterItem Item)
295 {
296 this.Model.ExternalEvent(this, "RosterItemUpdated",
297 new KeyValuePair<string, object>("Item", Item),
298 new KeyValuePair<string, object>("Client", this.client));
299
300 return Task.CompletedTask;
301 }
302
303 private Task Client_OnRosterItemRemoved(object Sender, RosterItem Item)
304 {
305 this.Model.ExternalEvent(this, "RosterItemRemoved",
306 new KeyValuePair<string, object>("Item", Item),
307 new KeyValuePair<string, object>("Client", this.client));
308
309 return Task.CompletedTask;
310 }
311
312 private Task Client_OnRosterItemAdded(object Sender, RosterItem Item)
313 {
314 this.Model.ExternalEvent(this, "RosterItemAdded",
315 new KeyValuePair<string, object>("Item", Item),
316 new KeyValuePair<string, object>("Client", this.client));
317
318 return Task.CompletedTask;
319 }
320
321 private Task Client_OnPresenceSubscribe(object Sender, PresenceEventArgs e)
322 {
323 if (!this.Model.ExternalEvent(this, "PresenceSubscribe",
324 new KeyValuePair<string, object>("e", e),
325 new KeyValuePair<string, object>("Client", this.client)))
326 {
327 e.Accept();
328 }
329
330 return Task.CompletedTask;
331 }
332
333 private Task Client_OnPresence(object Sender, PresenceEventArgs e)
334 {
335 this.Model.ExternalEvent(this, "Presence",
336 new KeyValuePair<string, object>("e", e),
337 new KeyValuePair<string, object>("Client", this.client));
338
339 return Task.CompletedTask;
340 }
341
342 private Task Client_OnNormalMessage(object Sender, MessageEventArgs e)
343 {
344 this.Model.ExternalEvent(this, "NormalMessage",
345 new KeyValuePair<string, object>("e", e),
346 new KeyValuePair<string, object>("Client", this.client));
347
348 return Task.CompletedTask;
349 }
350
351 private Task Client_OnHeadlineMessage(object Sender, MessageEventArgs e)
352 {
353 this.Model.ExternalEvent(this, "HeadlineMessage",
354 new KeyValuePair<string, object>("e", e),
355 new KeyValuePair<string, object>("Client", this.client));
356
357 return Task.CompletedTask;
358 }
359
360 private Task Client_OnGroupChatMessage(object Sender, MessageEventArgs e)
361 {
362 this.Model.ExternalEvent(this, "GroupChatMessage",
363 new KeyValuePair<string, object>("e", e),
364 new KeyValuePair<string, object>("Client", this.client));
365
366 return Task.CompletedTask;
367 }
368
369 private Task Client_OnErrorMessage(object Sender, MessageEventArgs e)
370 {
371 this.Model.ExternalEvent(this, "ErrorMessage",
372 new KeyValuePair<string, object>("e", e),
373 new KeyValuePair<string, object>("Client", this.client));
374
375 return Task.CompletedTask;
376 }
377
378 private Task Client_OnError(object Sender, Exception Exception)
379 {
380 this.Model.ExternalEvent(this, "Error",
381 new KeyValuePair<string, object>("Exception", Exception),
382 new KeyValuePair<string, object>("Client", this.client));
383
384 return Task.CompletedTask;
385 }
386
387 private Task Client_OnConnectionError(object Sender, Exception Exception)
388 {
389 this.Model.ExternalEvent(this, "ConnectionError",
390 new KeyValuePair<string, object>("Exception", Exception),
391 new KeyValuePair<string, object>("Client", this.client));
392
393 return Task.CompletedTask;
394 }
395
396 private Task Client_OnChatMessage(object Sender, MessageEventArgs e)
397 {
398 this.Model.ExternalEvent(this, "ChatMessage",
399 new KeyValuePair<string, object>("e", e),
400 new KeyValuePair<string, object>("Client", this.client));
401
402 return Task.CompletedTask;
403 }
404
405 private Task Client_OnStateChanged(object Sender, XmppState NewState)
406 {
407 switch (NewState)
408 {
409 case XmppState.Connected:
410 this.isOnline = true;
411
412 if (this.credentials is null)
413 {
414 this.credentials = new AccountCredentials()
415 {
416 Domain = this.domain,
417 UserName = this.userName,
418 PasswordHash = this.client.PasswordHash,
419 PasswordHashMethod = this.client.PasswordHashMethod
420 };
421
422 Database.Insert(this.credentials);
423 }
424 break;
425
426 case XmppState.Error:
427 case XmppState.Offline:
428 this.isOnline = false;
429 break;
430 }
431
432 this.Model.ExternalEvent(this, "OnStateChanged",
433 new KeyValuePair<string, object>("NewState", NewState),
434 new KeyValuePair<string, object>("Client", this.client));
435
436 return Task.CompletedTask;
437 }
438
442 public override async Task StartInstance()
443 {
444 if (this.alwaysConnected && !this.xmppCredentials.AllowRegistration)
445 {
446 switch (await this.client.WaitStateAsync(30000, XmppState.Connected, XmppState.Error, XmppState.Offline))
447 {
448 case 0: // Connected
449 break;
450
451 case 1: // Error
452 case 2: // Offline
453 default:
454 throw new Exception("Unable to connect " + this.userName + "@" + this.domain);
455 }
456 }
457 }
458
462 public override async Task FinalizeInstance()
463 {
464 if (!(this.client is null))
465 {
466 await this.client.DisposeAsync();
467 this.client = null;
468 }
469
470 if (!(this.sniffer is null))
471 {
472 if (this.sniffer is IDisposable Disposable)
473 Disposable.Dispose();
474
475 this.sniffer = null;
476 }
477 }
478
483 protected async virtual Task<XmppCredentials> GetInstanceCredentials()
484 {
485 this.credentials = await Database.FindFirstIgnoreRest<AccountCredentials>(new FilterAnd(
486 new FilterFieldEqualTo("Domain", this.domain),
487 new FilterFieldEqualTo("UserName", this.userName)));
488
489 if (!(this.credentials is null) && string.IsNullOrEmpty(this.credentials.PasswordHash))
490 {
491 await Database.Delete(this.credentials);
492 this.credentials = null;
493 }
494
495 XmppCredentials Result = new XmppCredentials()
496 {
497 Account = this.userName,
498 AllowCramMD5 = this.allowCramMD5,
499 AllowDigestMD5 = this.allowDigestMD5,
500 AllowEncryption = this.allowEncryption,
501 AllowPlain = this.allowPlain,
502 AllowRegistration = false,
503 AllowScramSHA1 = this.allowScramSHA1,
504 AllowScramSHA256 = this.allowScramSHA256,
505 Host = this.host,
506 Port = this.port.Value,
507 RequestRosterOnStartup = this.requestRosterOnStartup,
508 TrustServer = this.trustServer
509 };
510
511 if (this.credentials is null)
512 {
513 Result.AllowRegistration = true;
514 Result.FormSignatureKey = await this.Model.GetKey(this.apiKey, string.Empty);
515 Result.FormSignatureSecret = await this.Model.GetKey(this.secret, string.Empty);
516 Result.Password = Convert.ToBase64String(this.Model.GetRandomBytes(32));
517 }
518 else
519 {
520 Result.Password = this.credentials.PasswordHash;
521 Result.PasswordType = this.credentials.PasswordHashMethod;
522 }
523
524 return Result;
525 }
526
530 public override object ActivityObject
531 {
532 get
533 {
534 return new XmppActivityObject()
535 {
536 Client = this.client,
537 UserName = this.userName,
538 InstanceId = this.InstanceId,
539 InstanceIndex = this.InstanceIndex
540 };
541 }
542 }
543
544 }
545}
Root node of a simulation model
Definition: Model.cs:49
async Task< string > GetKey(string KeyName, string LookupValue)
Gets a key from the database. If it does not exist, it prompts the user for input.
Definition: Model.cs:817
Variables Variables
Model variables.
Definition: Model.cs:175
byte[] GetRandomBytes(int NrBytes)
Gets an array of random bytes.
Definition: Model.cs:768
ISniffer GetSniffer(string Actor)
Gets a sniffer, if sniffer output is desired.
Definition: Model.cs:863
bool ExternalEvent(IExternalEventsNode Source, string Name, params KeyValuePair< string, object >[] Arguments)
Method called when an external event has been received.
Definition: Model.cs:889
Abstract base class for actors
Definition: Actor.cs:15
string InstanceId
ID of actor instance.
Definition: Actor.cs:57
int InstanceIndex
Actor instance index.
Definition: Actor.cs:67
int N
Number of actors of this type specified.
Definition: Actor.cs:62
string PasswordHashMethod
Password hash method
Object used in simulation activities.
Abstract base class for XMPP actors.
Definition: XmppActor.cs:23
override async Task Initialize()
Initialized the node before simulation.
Definition: XmppActor.cs:144
override Task< Actor > CreateInstanceAsync(int InstanceIndex, string InstanceId)
Creates an instance of the actor.
Definition: XmppActor.cs:177
override object ActivityObject
Returns the object that will be used by the actor for actions during an activity.
Definition: XmppActor.cs:531
XmppActor(ISimulationNode Parent, Model Model, int InstanceIndex, string InstanceId)
Abstract base class for XMPP actors.
Definition: XmppActor.cs:72
abstract XmppActor CreateInstanceObject(int InstanceIndex, string InstanceId)
Creates an instance object of the XMPP actor, and initializes it.
XmppClient Client
XMPP Client
Definition: XmppActor.cs:110
const string XmppSchema
TAG.Simulator.XMPP.Schema.ComSimXmpp.xsd
Definition: XmppActor.cs:32
override async Task InitializeInstance()
Initializes an instance of an actor.
Definition: XmppActor.cs:213
bool IsOnline
If instance is online.
Definition: XmppActor.cs:90
const string XmppNamespace
http://lab.tagroot.io/Schema/ComSim/XMPP.xsd
Definition: XmppActor.cs:27
XmppActor(ISimulationNode Parent, Model Model)
Abstract base class for XMPP actors.
Definition: XmppActor.cs:60
virtual async Task< XmppCredentials > GetInstanceCredentials()
Gets XMPP credentials for the instance.
Definition: XmppActor.cs:483
override async Task StartInstance()
Starts an instance of an actor.
Definition: XmppActor.cs:442
override string SchemaResource
Points to the embedded XML Schema resource defining the semantics of the XML namespace.
Definition: XmppActor.cs:85
bool TrustServer
If server is to be trusted, regardless of state of certificate.
Definition: XmppActor.cs:95
override Task FromXml(XmlElement Definition)
Sets properties and attributes of class in accordance with XML definition.
Definition: XmppActor.cs:116
override async Task FinalizeInstance()
Finalizes an instance of an actor.
Definition: XmppActor.cs:462
override string Namespace
XML Namespace where the element is defined.
Definition: XmppActor.cs:80
Abstract base class for handler nodes
Definition: HandlerNode.cs:19
abstract void RegisterHandlers(IActor Actor, XmppClient Client)
Registers handlers on the XMPP Client.
Helps with common XML-related tasks.
Definition: XML.cs:19
static string Attribute(XmlElement E, string Name)
Gets the value of an XML attribute.
Definition: XML.cs:914
DNS resolver, as defined in:
Definition: DnsResolver.cs:32
static Task< SRV > LookupServiceEndpoint(string DomainName, string ServiceName, string Protocol)
Looks up a service endpoint for a domain. If multiple are available, an appropriate one is selected a...
Definition: DnsResolver.cs:825
Event Argument for custom presence XML events.
Event arguments for message events.
Event arguments for presence events.
async Task Accept()
Accepts a subscription or unsubscription request.
Maintains information about an item in the roster.
Definition: RosterItem.cs:75
Manages an XMPP client connection. Implements XMPP, as defined in https://tools.ietf....
Definition: XmppClient.cs:59
async Task< int > WaitStateAsync(int Timeout, params XmppState[] States)
Waits for one of the states in States to occur.
Definition: XmppClient.cs:1064
async Task DisposeAsync()
Closes the connection and disposes of all resources.
Definition: XmppClient.cs:1164
string PasswordHash
Hash value of password. Depends on method used to authenticate user.
Definition: XmppClient.cs:3436
Task Connect()
Connects the client.
Definition: XmppClient.cs:641
bool TryGetTag(string TagName, out object Tag)
Tries to get a tag from the client. Tags can be used to attached application specific objects to the ...
Definition: XmppClient.cs:7207
Class containing credentials for an XMPP client connection.
bool AllowRegistration
If the client is allowed to register for a new account, if the account was not found.
Static interface for database persistence. In order to work, a database provider has to be assigned t...
Definition: Database.cs:19
static async Task Delete(object Object)
Deletes an object in the database.
Definition: Database.cs:717
static async Task Insert(object Object)
Inserts an object into the default collection of the database.
Definition: Database.cs:95
This filter selects objects that conform to all child-filters provided.
Definition: FilterAnd.cs:10
This filter selects objects that have a named field equal to a given value.
Basic interface for simulator nodes. Implementing this interface allows classes with default contruct...
ISimulationNode Parent
Parent node in the simulation model.
Basic interface for simulator nodes. Implementing this interface allows classes with default contruct...
Definition: IActor.cs:11
Interface for sniffers. Sniffers can be added to ICommunicationLayer classes to eavesdrop on communic...
Definition: ISniffer.cs:11
XmppState
State of XMPP connection.
Definition: XmppState.cs:7