Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
XmppServerlessMessaging.cs
1using System;
3using System.Net;
4using System.Threading.Tasks;
5using System.Text;
6using System.Xml;
7using Waher.Events;
10
12{
17 {
18 private Dictionary<string, PeerState> peersByFullJid = new Dictionary<string, PeerState>(StringComparer.CurrentCultureIgnoreCase);
19 private readonly Dictionary<string, AddressInfo> addressesByFullJid = new Dictionary<string, AddressInfo>(StringComparer.CurrentCultureIgnoreCase);
20 private readonly Dictionary<string, Dictionary<int, AddressInfo>> addressesByExternalIPPort = new Dictionary<string, Dictionary<int, AddressInfo>>();
21 private readonly Dictionary<string, Dictionary<int, AddressInfo>> addressesByLocalIPPort = new Dictionary<string, Dictionary<int, AddressInfo>>();
22 private PeerToPeerNetwork p2pNetwork = null;
23 private string fullJid;
24 private bool disposed = false;
25
32 public XmppServerlessMessaging(string ApplicationName, string FullJid, params ISniffer[] Sniffers)
33 : this(ApplicationName, FullJid, PeerToPeerNetwork.DefaultPort, PeerToPeerNetwork.DefaultPort,
34 PeerToPeerNetwork.DefaultBacklog, Sniffers)
35 {
36 }
37
46 public XmppServerlessMessaging(string ApplicationName, string FullJid, ushort LocalPort, ushort ExternalPort, params ISniffer[] Sniffers)
47 : this(ApplicationName, FullJid, LocalPort, ExternalPort, PeerToPeerNetwork.DefaultBacklog, Sniffers)
48 {
49 }
50
60 public XmppServerlessMessaging(string ApplicationName, string FullJid, ushort LocalPort, ushort ExternalPort, int Backlog,
61 params ISniffer[] Sniffers)
62 : base(false, Sniffers)
63 {
64 this.fullJid = FullJid;
65 this.p2pNetwork = new PeerToPeerNetwork(ApplicationName, LocalPort, ExternalPort, Backlog, Sniffers)
66 {
67 EncapsulatePackets = false
68 };
69
70 // TODO: Implement support for NAT-PMP
71
72 this.p2pNetwork.OnPeerConnected += this.P2PNetwork_OnPeerConnected;
73 }
74
78 public PeerToPeerNetwork Network => this.p2pNetwork;
79
83 public string FullJid
84 {
85 get => this.fullJid;
86 set => this.fullJid = value;
87 }
88
92 public bool Disposed => this.disposed;
93
94 private Task P2PNetwork_OnPeerConnected(object Listener, PeerConnection Peer)
95 {
96 PeerState _ = new PeerState(Peer, this);
97 this.Information("Peer connected from " + Peer.RemoteEndpoint.ToString());
98 return Task.CompletedTask;
99 }
100
105 public async Task RemovePeerAddresses(string FullJID)
106 {
107 string ThisExternalIp = this.p2pNetwork.ExternalAddress is null ? string.Empty : this.p2pNetwork.ExternalAddress.ToString();
108
109 lock (this.addressesByFullJid)
110 {
111 if (this.addressesByFullJid.TryGetValue(FullJID, out AddressInfo Info))
112 {
113 this.addressesByFullJid.Remove(FullJID);
114
115 if (this.addressesByExternalIPPort.TryGetValue(Info.ExternalIp, out Dictionary<int, AddressInfo> Infos))
116 {
117 if (Infos.Remove(Info.ExternalPort) && Infos.Count == 0)
118 this.addressesByExternalIPPort.Remove(Info.ExternalIp);
119 }
120
121 if (Info.ExternalIp == ThisExternalIp)
122 {
123 if (this.addressesByLocalIPPort.TryGetValue(Info.LocalIp, out Infos))
124 {
125 if (Infos.Remove(Info.LocalPort) && Infos.Count == 0)
126 this.addressesByLocalIPPort.Remove(Info.LocalIp);
127 }
128 }
129 }
130 else
131 return;
132 }
133
134 this.Information("Removing JID from set of recognized JIDs: " + FullJID);
135
136 await this.PeerAddressRemoved.Raise(this, new PeerAddressEventArgs(FullJID, string.Empty, 0, string.Empty, 0));
137 }
138
142 public event EventHandlerAsync<PeerAddressEventArgs> PeerAddressRemoved = null;
143
152 public async Task ReportPeerAddresses(string FullJID, string ExternalIp, int ExternalPort, string LocalIp, int LocalPort)
153 {
154 Dictionary<int, AddressInfo> Infos;
155 string ThisExternalIp;
156
157 if (this.p2pNetwork.ExternalAddress is null)
158 ThisExternalIp = string.Empty;
159 else
160 ThisExternalIp = this.p2pNetwork.ExternalAddress.ToString();
161
162 lock (this.addressesByFullJid)
163 {
164 if (this.addressesByFullJid.TryGetValue(FullJID, out AddressInfo Info))
165 {
166 if (Info.ExternalIp == ExternalIp && Info.ExternalPort == ExternalPort &&
167 Info.LocalIp == LocalIp && Info.LocalPort == LocalPort)
168 {
169 return;
170 }
171
172 if (Info.ExternalIp != ExternalIp)
173 {
174 if (this.addressesByExternalIPPort.TryGetValue(Info.ExternalIp, out Infos))
175 {
176 if (Infos.Remove(Info.ExternalPort) && Infos.Count == 0)
177 this.addressesByExternalIPPort.Remove(Info.ExternalIp);
178 }
179 }
180
181 if (ExternalIp == ThisExternalIp && Info.LocalIp != LocalIp)
182 {
183 if (this.addressesByLocalIPPort.TryGetValue(Info.LocalIp, out Infos))
184 {
185 if (Infos.Remove(Info.LocalPort) && Infos.Count == 0)
186 this.addressesByLocalIPPort.Remove(Info.LocalIp);
187 }
188 }
189 }
190
191 Info = new AddressInfo(FullJID, ExternalIp, ExternalPort, LocalIp, LocalPort);
192 this.addressesByFullJid[FullJID] = Info;
193
194 if (!this.addressesByExternalIPPort.TryGetValue(ExternalIp, out Infos))
195 {
196 Infos = new Dictionary<int, AddressInfo>();
197 this.addressesByExternalIPPort[ExternalIp] = Infos;
198 }
199
200 Infos[ExternalPort] = Info;
201
202 if (ExternalIp == ThisExternalIp)
203 {
204 if (!this.addressesByLocalIPPort.TryGetValue(LocalIp, out Infos))
205 {
206 Infos = new Dictionary<int, AddressInfo>();
207 this.addressesByLocalIPPort[LocalIp] = Infos;
208 }
209
210 Infos[LocalPort] = Info;
211 }
212 }
213
214 this.Information("P2P information available for " + FullJID + ". External: " + ExternalIp + ":" + ExternalPort.ToString() +
215 ", Local: " + LocalIp + ":" + LocalPort.ToString());
216
217 await this.PeerAddressReceived.Raise(this, new PeerAddressEventArgs(FullJID, ExternalIp, ExternalPort, LocalIp, LocalPort));
218 }
219
223 public event EventHandlerAsync<PeerAddressEventArgs> PeerAddressReceived = null;
224
225 internal string AuthenticatePeer(PeerConnection Peer, string FullJID)
226 {
227 AddressInfo Info;
228
229 lock (this.addressesByFullJid)
230 {
231 if (!this.addressesByFullJid.TryGetValue(FullJID, out Info))
232 return "Peer JID " + FullJID + " not recognized.";
233 }
234
235 if (Info.ExternalIp == this.p2pNetwork.ExternalAddress.ToString())
236 {
237 if (Peer.RemoteEndpoint.Address.ToString() != Info.LocalIp)
238 {
239 return "Expected connection from " + Info.LocalIp + ", but was from " +
240 Peer.RemoteEndpoint.Address.ToString();
241 }
242 }
243 else
244 {
245 if (Peer.RemoteEndpoint.Address.ToString() != Info.ExternalIp)
246 {
247 return "Expected connection from " + Info.ExternalIp + ", but was from " +
248 Peer.RemoteEndpoint.ToString();
249 }
250 }
251
252 // End-to-end encryption will ensure communication is only read by the indended receiver.
253
254 return null;
255 }
256
257 internal void PeerAuthenticated(PeerState State)
258 {
259 lock (this.peersByFullJid)
260 {
261 this.peersByFullJid[State.RemoteFullJid] = State;
262 }
263
264 this.Information("Peer authenticated: " + State.RemoteFullJid);
265 }
266
267 internal async Task NewXmppClient(XmppClient Client, string LocalJid, string RemoteJid)
268 {
269 this.Information("Serverless XMPP connection established with " + RemoteJid);
270
271 /*foreach (ISniffer Sniffer in this.Sniffers)
272 Client.Add(Sniffer);*/
273
274 await this.OnNewXmppClient.Raise(this, new PeerConnectionEventArgs(Client, null, LocalJid, RemoteJid));
275 }
276
280 public event EventHandlerAsync<PeerConnectionEventArgs> OnNewXmppClient = null;
281
282 internal void PeerClosed(PeerState State)
283 {
284 this.Information("Serverless XMPP connection with " + State.RemoteFullJid + " closed.");
285
286 if (!(this.peersByFullJid is null))
287 {
288 lock (this.peersByFullJid)
289 {
290 if (this.peersByFullJid.TryGetValue(State.RemoteFullJid, out PeerState State2) && State2 == State)
291 this.peersByFullJid.Remove(State.RemoteFullJid);
292 }
293 }
294 }
295
302 public Task GetPeerConnection(string FullJID, EventHandlerAsync<PeerConnectionEventArgs> Callback, object State)
303 {
304 return this.GetPeerConnection(FullJID, Callback, State, this.OnResynch);
305 }
306
312 public async Task<PeerConnectionEventArgs> GetPeerConnectionAsync(string FullJID)
313 {
314 TaskCompletionSource<PeerConnectionEventArgs> Result = new TaskCompletionSource<PeerConnectionEventArgs>();
315
316 await this.GetPeerConnection(FullJID, (Sender, e) =>
317 {
318 Result.TrySetResult(e);
319 return Task.CompletedTask;
320 }, null);
321
322 return await Result.Task;
323 }
324
328 public event EventHandlerAsync<ResynchEventArgs> OnResynch = null;
329
335 public bool CanConnectToPeer(string FullJID)
336 {
337 AddressInfo Info;
338
339 lock (this.addressesByFullJid)
340 {
341 if (!this.addressesByFullJid.TryGetValue(FullJID, out Info))
342 return false;
343 }
344
345 return !string.IsNullOrEmpty(Info.ExternalIp);
346 }
347
354 public bool TryGetAddressInfo(string FullJID, out AddressInfo Address)
355 {
356 lock (this.addressesByFullJid)
357 {
358 return this.addressesByFullJid.TryGetValue(FullJID, out Address);
359 }
360 }
361
362 private async Task GetPeerConnection(string FullJID, EventHandlerAsync<PeerConnectionEventArgs> Callback, object State, EventHandlerAsync<ResynchEventArgs> ResynchMethod)
363 {
364 PeerState Result;
365 PeerState Old = null;
366 AddressInfo Info;
367 string Header = null;
368 bool b;
369
370 if (this.p2pNetwork is null || this.p2pNetwork.State != PeerToPeerNetworkState.Ready)
371 {
372 await Callback.Raise(this, new PeerConnectionEventArgs(null, State, this.fullJid, FullJID));
373 return;
374 }
375
376 lock (this.addressesByFullJid)
377 {
378 b = this.addressesByFullJid.TryGetValue(FullJID, out Info);
379 }
380
381 if (!b)
382 {
383 await Callback.Raise(this, new PeerConnectionEventArgs(null, State, this.fullJid, FullJID));
384 return;
385 }
386
387 lock (this.peersByFullJid)
388 {
389 b = this.peersByFullJid.TryGetValue(FullJID, out Result);
390
391 if (b)
392 {
393 if (Result.AgeSeconds >= 30 && (Result.HasCallbacks || Result.XmppClient is null || !Result.Peer.Tcp.Connected))
394 {
395 this.peersByFullJid.Remove(FullJID);
396 Old = Result;
397 Result = null;
398 b = false;
399 }
400 else if (Result.State != XmppState.Connected)
401 {
402 Result.AddCallback(Callback, State);
403 return;
404 }
405 }
406
407 if (!b)
408 {
409 Header = "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' from='" +
410 this.fullJid + "' to='" + FullJID + "' version='1.0'>";
411
412 Result = new PeerState(null, this, FullJID, Header, "</stream:stream>", string.Empty, 1.0, Callback, State);
413 this.peersByFullJid[FullJID] = Result;
414 }
415 }
416
417 if (b)
418 {
419 await Callback.Raise(this, new PeerConnectionEventArgs(Result.XmppClient, State, this.fullJid, FullJID));
420 return;
421 }
422 else if (!(Old is null))
423 {
424 await Old.CallCallbacks();
425 await Old.DisposeAsync();
426 }
427
428 _ = Task.Run(async () =>
429 {
430 PeerConnection Connection;
431
432 try
433 {
434 Connection = await this.ConnectToAsync(FullJID, Info);
435 }
436 catch (Exception ex)
437 {
438 this.Exception(ex);
439 Connection = null;
440
441 if (!(ResynchMethod is null))
442 {
443 try
444 {
445 ResynchEventArgs e = new ResynchEventArgs(FullJID, async (sender, e2) =>
446 {
447 try
448 {
449 if (e2.Ok)
450 await this.GetPeerConnection(FullJID, Callback, State, null);
451 else
452 {
453 lock (this.peersByFullJid)
454 {
455 this.peersByFullJid.Remove(FullJID);
456 }
457
458 await Result.CallCallbacks();
459 }
460 }
461 catch (Exception ex2)
462 {
463 Log.Exception(ex2);
464 }
465 });
466
467 await ResynchMethod(this, e);
468 }
469 catch (Exception ex2)
470 {
471 Log.Exception(ex2);
472 }
473
474 return;
475 }
476 }
477
478 if (Connection is null)
479 {
480 lock (this.peersByFullJid)
481 {
482 this.peersByFullJid.Remove(FullJID);
483 }
484
485 await Result.CallCallbacks();
486 }
487 else
488 {
489 Result.Peer = Connection;
490 Connection.Start(async (Sender, e) =>
491 {
492 if (!(ResynchMethod is null))
493 {
494 if (!await ResynchMethod.Raise(this, new ResynchEventArgs(FullJID, async (sender2, e2) =>
495 {
496 try
497 {
498 if (e2.Ok)
499 {
500 Result.Peer = null;
501 Connection = await this.ConnectToAsync(FullJID, Info);
502 Result.Peer = Connection;
503 Connection.Start();
504 Result.HeaderSent = true;
505 await Result.SendAsync(Header);
506 this.TransmitText(Header);
507 }
508 else
509 await Result.CallCallbacks();
510 }
511 catch (Exception ex)
512 {
513 this.Exception(ex);
514 }
515 }), false))
516 {
517 await Result.CallCallbacks();
518 }
519 }
520 else
521 await Result.CallCallbacks();
522 });
523
524 Result.HeaderSent = true;
525 await Result.SendAsync(Header);
526 this.TransmitText(Header);
527 }
528 });
529 }
530
531 private async Task<PeerConnection> ConnectToAsync(string FullJID, AddressInfo Info)
532 {
533 PeerConnection Connection;
534 string Ip;
535 int Port;
536
537 if (Info.ExternalIp == this.p2pNetwork.ExternalAddress.ToString())
538 {
539 Ip = Info.LocalIp;
540 Port = Info.LocalPort;
541 }
542 else
543 {
544 Ip = Info.ExternalIp;
545 Port = Info.ExternalPort;
546 }
547
548 if (IPAddress.TryParse(Ip, out IPAddress Addr))
549 {
550 this.Information("Connecting to " + Ip + ":" + Port.ToString() + " (" + FullJID + ")");
551 Connection = await this.p2pNetwork.ConnectToPeer(new IPEndPoint(Addr, Port));
552 this.Information("Connected to to " + Ip + ":" + Port.ToString() + " (" + FullJID + ")");
553 }
554 else
555 Connection = null;
556
557 return Connection;
558 }
559
563 [Obsolete("Use the DisposeAsync() method.")]
564 public void Dispose()
565 {
566 this.DisposeAsync().Wait();
567 }
568
572 public async Task DisposeAsync()
573 {
574 if (!this.disposed)
575 {
576 if (!(this.p2pNetwork is null))
577 {
578 await this.p2pNetwork.DisposeAsync();
579 this.p2pNetwork = null;
580 }
581
582 if (!(this.peersByFullJid is null))
583 {
584 PeerState[] States;
585
586 lock (this.peersByFullJid)
587 {
588 States = new PeerState[this.peersByFullJid.Count];
589 this.peersByFullJid.Values.CopyTo(States, 0);
590
591 this.peersByFullJid.Clear();
592 }
593
594 this.peersByFullJid = null;
595
596 foreach (PeerState State in States)
597 {
598 State.ClearCallbacks();
599 await State.Close();
600 }
601 }
602
603 this.disposed = true;
604 }
605 }
606
611 public void AppendP2pInfo(StringBuilder Xml)
612 {
613 if (!(this.p2pNetwork is null) &&
614 this.p2pNetwork.State == PeerToPeerNetworkState.Ready &&
615 !(this.p2pNetwork.ExternalEndpoint is null) &&
616 !this.p2pNetwork.OnPublicNetwork())
617 {
618 Xml.Append("<p2p xmlns='");
620 Xml.Append("' extIp='");
621 Xml.Append(this.p2pNetwork.ExternalAddress.ToString());
622 Xml.Append("' extPort='");
623 Xml.Append(this.p2pNetwork.ExternalEndpoint.Port.ToString());
624 Xml.Append("' locIp='");
625 Xml.Append(this.p2pNetwork.LocalAddress.ToString());
626 Xml.Append("' locPort='");
627 Xml.Append(this.p2pNetwork.LocalEndpoint.Port.ToString());
628 Xml.Append("'/>");
629 }
630 }
631
638 public async Task<bool> AddPeerAddressInfo(string FullJID, XmlElement P2P)
639 {
640 string ExtIp = null;
641 int? ExtPort = null;
642 string LocIp = null;
643 int? LocPort = null;
644
645 if (!(P2P is null))
646 {
647 foreach (XmlAttribute Attr in P2P.Attributes)
648 {
649 switch (Attr.Name)
650 {
651 case "extIp":
652 if (IPAddress.TryParse(Attr.Value, out IPAddress Addr) &&
654 {
655 ExtIp = Attr.Value;
656 }
657 break;
658
659 case "extPort":
660 if (int.TryParse(Attr.Value, out int i) && i >= 0 && i < 65536)
661 ExtPort = i;
662 else
663 return false;
664 break;
665
666 case "locIp":
667 LocIp = Attr.Value;
668 break;
669
670 case "locPort":
671 if (int.TryParse(Attr.Value, out i) && i >= 0 && i < 65536)
672 LocPort = i;
673 else
674 return false;
675 break;
676 }
677 }
678 }
679
680 if (!(ExtIp is null) && ExtPort.HasValue && !(LocIp is null) && LocPort.HasValue)
681 {
682 await this.ReportPeerAddresses(FullJID, ExtIp, ExtPort.Value, LocIp, LocPort.Value);
683 return true;
684 }
685 else
686 {
687 await this.RemovePeerAddresses(FullJID);
688 return false;
689 }
690 }
691
692 }
693}
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
Simple base class for classes implementing communication protocols.
void TransmitText(string Text)
Called when text has been transmitted.
void Exception(Exception Exception)
Called to inform the viewer of an exception state.
ISniffer[] Sniffers
Registered sniffers.
void Information(string Comment)
Called to inform the viewer of something.
Manages registration of TCP and UDP ports in an Internet Gateway
static bool IsPublicAddress(IPAddress Address)
Checks if an IPv4 address is public.
IPEndPoint RemoteEndpoint
Remote endpoint.
void Start()
Starts receiving on the connection.
Manages a peer-to-peer network that can receive connections from outside of a NAT-enabled firewall.
Contains information about peer addresses.
Definition: AddressInfo.cs:7
string ExternalIp
External IP Address.
Definition: AddressInfo.cs:39
string LocalIp
Local IP Address.
Definition: AddressInfo.cs:49
Class managing end-to-end encryption.
const string IoTHarmonizationP2PCurrent
Current namespace for peer-to-peer communication
Peer connection state.
Definition: PeerState.cs:17
Task DisposeAsync()
IDisposable.Dispose
Definition: PeerState.cs:941
string RemoteFullJid
Remote Full JID
Definition: PeerState.cs:789
async Task Close()
CLoses the connection.
Definition: PeerState.cs:853
Class managing peer-to-peer serveless XMPP communication.
async Task ReportPeerAddresses(string FullJID, string ExternalIp, int ExternalPort, string LocalIp, int LocalPort)
Reports recognized peer addresses.
bool TryGetAddressInfo(string FullJID, out AddressInfo Address)
Gets peer-to-peer address information
EventHandlerAsync< PeerAddressEventArgs > PeerAddressReceived
Event raised when address information about a peer has been received.
PeerToPeerNetwork Network
Peer-to-peer network.
XmppServerlessMessaging(string ApplicationName, string FullJid, ushort LocalPort, ushort ExternalPort, params ISniffer[] Sniffers)
Class managing peer-to-peer serveless XMPP communication.
Task GetPeerConnection(string FullJID, EventHandlerAsync< PeerConnectionEventArgs > Callback, object State)
Gets a peer XMPP connection.
async Task< bool > AddPeerAddressInfo(string FullJID, XmlElement P2P)
Adds P2P address information about a peer.
EventHandlerAsync< PeerAddressEventArgs > PeerAddressRemoved
Event raised when address information about a peer has been removed.
EventHandlerAsync< ResynchEventArgs > OnResynch
Event raised when the peer-to-peer connection parameters need to be updated for a given remote JID.
async Task RemovePeerAddresses(string FullJID)
Removes a JID from the recognized set of JIDs.
async Task< PeerConnectionEventArgs > GetPeerConnectionAsync(string FullJID)
Gets a peer XMPP connection.
EventHandlerAsync< PeerConnectionEventArgs > OnNewXmppClient
Event raised when a new XMPP client has been created.
void AppendP2pInfo(StringBuilder Xml)
Appends P2P information to XML.
XmppServerlessMessaging(string ApplicationName, string FullJid, ushort LocalPort, ushort ExternalPort, int Backlog, params ISniffer[] Sniffers)
Class managing peer-to-peer serveless XMPP communication.
XmppServerlessMessaging(string ApplicationName, string FullJid, params ISniffer[] Sniffers)
Class managing peer-to-peer serveless XMPP communication.
bool CanConnectToPeer(string FullJID)
If it is possible to connect directly to a given peer, given it's bare JID.
Interface for asynchronously disposable objects.
Interface for sniffers. Sniffers can be added to ICommunicationLayer classes to eavesdrop on communic...
Definition: ISniffer.cs:10
Definition: ImplTypes.g.cs:58
class Header(ISimulationNode Parent, Model Model)
Represents an identity property.
Definition: Header.cs:18
PeerToPeerNetworkState
State of Peer-to-peer network.
XmppState
State of XMPP connection.
Definition: XmppState.cs:7