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;
2using System.Collections.Generic;
3using System.Net;
4using System.Threading.Tasks;
5using System.Text;
6using System.Xml;
7using Waher.Events;
10
12{
16 public class XmppServerlessMessaging : CommunicationLayer, IDisposable
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(true, 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 return this.Information("Peer connected from " + Peer.RemoteEndpoint.ToString());
98 }
99
104 public async Task RemovePeerAddresses(string FullJID)
105 {
106 string ThisExternalIp = this.p2pNetwork.ExternalAddress is null ? string.Empty : this.p2pNetwork.ExternalAddress.ToString();
107
108 lock (this.addressesByFullJid)
109 {
110 if (this.addressesByFullJid.TryGetValue(FullJID, out AddressInfo Info))
111 {
112 this.addressesByFullJid.Remove(FullJID);
113
114 if (this.addressesByExternalIPPort.TryGetValue(Info.ExternalIp, out Dictionary<int, AddressInfo> Infos))
115 {
116 if (Infos.Remove(Info.ExternalPort) && Infos.Count == 0)
117 this.addressesByExternalIPPort.Remove(Info.ExternalIp);
118 }
119
120 if (Info.ExternalIp == ThisExternalIp)
121 {
122 if (this.addressesByLocalIPPort.TryGetValue(Info.LocalIp, out Infos))
123 {
124 if (Infos.Remove(Info.LocalPort) && Infos.Count == 0)
125 this.addressesByLocalIPPort.Remove(Info.LocalIp);
126 }
127 }
128 }
129 else
130 return;
131 }
132
133 await this.Information("Removing JID from set of recognized JIDs: " + FullJID);
134
135 await this.PeerAddressRemoved.Raise(this, new PeerAddressEventArgs(FullJID, string.Empty, 0, string.Empty, 0));
136 }
137
141 public event EventHandlerAsync<PeerAddressEventArgs> PeerAddressRemoved = null;
142
151 public async Task ReportPeerAddresses(string FullJID, string ExternalIp, int ExternalPort, string LocalIp, int LocalPort)
152 {
153 Dictionary<int, AddressInfo> Infos;
154 string ThisExternalIp;
155
156 if (this.p2pNetwork.ExternalAddress is null)
157 ThisExternalIp = string.Empty;
158 else
159 ThisExternalIp = this.p2pNetwork.ExternalAddress.ToString();
160
161 lock (this.addressesByFullJid)
162 {
163 if (this.addressesByFullJid.TryGetValue(FullJID, out AddressInfo Info))
164 {
165 if (Info.ExternalIp == ExternalIp && Info.ExternalPort == ExternalPort &&
166 Info.LocalIp == LocalIp && Info.LocalPort == LocalPort)
167 {
168 return;
169 }
170
171 if (Info.ExternalIp != ExternalIp)
172 {
173 if (this.addressesByExternalIPPort.TryGetValue(Info.ExternalIp, out Infos))
174 {
175 if (Infos.Remove(Info.ExternalPort) && Infos.Count == 0)
176 this.addressesByExternalIPPort.Remove(Info.ExternalIp);
177 }
178 }
179
180 if (ExternalIp == ThisExternalIp && Info.LocalIp != LocalIp)
181 {
182 if (this.addressesByLocalIPPort.TryGetValue(Info.LocalIp, out Infos))
183 {
184 if (Infos.Remove(Info.LocalPort) && Infos.Count == 0)
185 this.addressesByLocalIPPort.Remove(Info.LocalIp);
186 }
187 }
188 }
189
190 Info = new AddressInfo(FullJID, ExternalIp, ExternalPort, LocalIp, LocalPort);
191 this.addressesByFullJid[FullJID] = Info;
192
193 if (!this.addressesByExternalIPPort.TryGetValue(ExternalIp, out Infos))
194 {
195 Infos = new Dictionary<int, AddressInfo>();
196 this.addressesByExternalIPPort[ExternalIp] = Infos;
197 }
198
199 Infos[ExternalPort] = Info;
200
201 if (ExternalIp == ThisExternalIp)
202 {
203 if (!this.addressesByLocalIPPort.TryGetValue(LocalIp, out Infos))
204 {
205 Infos = new Dictionary<int, AddressInfo>();
206 this.addressesByLocalIPPort[LocalIp] = Infos;
207 }
208
209 Infos[LocalPort] = Info;
210 }
211 }
212
213 await this.Information("P2P information available for " + FullJID + ". External: " + ExternalIp + ":" + ExternalPort.ToString() +
214 ", Local: " + LocalIp + ":" + LocalPort.ToString());
215
216 await this.PeerAddressReceived.Raise(this, new PeerAddressEventArgs(FullJID, ExternalIp, ExternalPort, LocalIp, LocalPort));
217 }
218
222 public event EventHandlerAsync<PeerAddressEventArgs> PeerAddressReceived = null;
223
224 internal void AuthenticatePeer(PeerConnection Peer, string FullJID)
225 {
226 AddressInfo Info;
227
228 lock (this.addressesByFullJid)
229 {
230 if (!this.addressesByFullJid.TryGetValue(FullJID, out Info))
231 throw new XmppException("Peer JID " + FullJID + " not recognized.");
232 }
233
234 if (Info.ExternalIp == this.p2pNetwork.ExternalAddress.ToString())
235 {
236 if (Peer.RemoteEndpoint.Address.ToString() != Info.LocalIp)
237 {
238 throw new XmppException("Expected connection from " + Info.LocalIp + ", but was from " +
239 Peer.RemoteEndpoint.Address.ToString());
240 }
241 }
242 else
243 {
244 if (Peer.RemoteEndpoint.Address.ToString() != Info.ExternalIp)
245 {
246 throw new XmppException("Expected connection from " + Info.ExternalIp + ", but was from " +
247 Peer.RemoteEndpoint.ToString());
248 }
249 }
250
251 // End-to-end encryption will asure communication is only read by the indended receiver.
252 }
253
254 internal Task PeerAuthenticated(PeerState State)
255 {
256 lock (this.peersByFullJid)
257 {
258 this.peersByFullJid[State.RemoteFullJid] = State;
259 }
260
261 return this.Information("Peer authenticated: " + State.RemoteFullJid);
262 }
263
264 internal async Task NewXmppClient(XmppClient Client, string LocalJid, string RemoteJid)
265 {
266 await this.Information("Serverless XMPP connection established with " + RemoteJid);
267
268 /*foreach (ISniffer Sniffer in this.Sniffers)
269 Client.Add(Sniffer);*/
270
271 await this.OnNewXmppClient.Raise(this, new PeerConnectionEventArgs(Client, null, LocalJid, RemoteJid));
272 }
273
277 public event EventHandlerAsync<PeerConnectionEventArgs> OnNewXmppClient = null;
278
279 internal async Task PeerClosed(PeerState State)
280 {
281 await this.Information("Serverless XMPP connection with " + State.RemoteFullJid + " closed.");
282
283 if (!(this.peersByFullJid is null))
284 {
285 lock (this.peersByFullJid)
286 {
287 if (this.peersByFullJid.TryGetValue(State.RemoteFullJid, out PeerState State2) && State2 == State)
288 this.peersByFullJid.Remove(State.RemoteFullJid);
289 }
290 }
291 }
292
299 public Task GetPeerConnection(string FullJID, EventHandlerAsync<PeerConnectionEventArgs> Callback, object State)
300 {
301 return this.GetPeerConnection(FullJID, Callback, State, this.OnResynch);
302 }
303
309 public async Task<PeerConnectionEventArgs> GetPeerConnectionAsync(string FullJID)
310 {
311 TaskCompletionSource<PeerConnectionEventArgs> Result = new TaskCompletionSource<PeerConnectionEventArgs>();
312
313 await this.GetPeerConnection(FullJID, (Sender, e) =>
314 {
315 Result.TrySetResult(e);
316 return Task.CompletedTask;
317 }, null);
318
319 return await Result.Task;
320 }
321
325 public event EventHandlerAsync<ResynchEventArgs> OnResynch = null;
326
332 public bool CanConnectToPeer(string FullJID)
333 {
334 AddressInfo Info;
335
336 lock (this.addressesByFullJid)
337 {
338 if (!this.addressesByFullJid.TryGetValue(FullJID, out Info))
339 return false;
340 }
341
342 return !string.IsNullOrEmpty(Info.ExternalIp);
343 }
344
351 public bool TryGetAddressInfo(string FullJID, out AddressInfo Address)
352 {
353 lock (this.addressesByFullJid)
354 {
355 return this.addressesByFullJid.TryGetValue(FullJID, out Address);
356 }
357 }
358
359 private async Task GetPeerConnection(string FullJID, EventHandlerAsync<PeerConnectionEventArgs> Callback, object State, EventHandlerAsync<ResynchEventArgs> ResynchMethod)
360 {
361 PeerState Result;
362 PeerState Old = null;
363 AddressInfo Info;
364 string Header = null;
365 bool b;
366
367 if (this.p2pNetwork is null || this.p2pNetwork.State != PeerToPeerNetworkState.Ready)
368 {
369 await Callback.Raise(this, new PeerConnectionEventArgs(null, State, this.fullJid, FullJID));
370 return;
371 }
372
373 lock (this.addressesByFullJid)
374 {
375 b = this.addressesByFullJid.TryGetValue(FullJID, out Info);
376 }
377
378 if (!b)
379 {
380 await Callback.Raise(this, new PeerConnectionEventArgs(null, State, this.fullJid, FullJID));
381 return;
382 }
383
384 lock (this.peersByFullJid)
385 {
386 b = this.peersByFullJid.TryGetValue(FullJID, out Result);
387
388 if (b)
389 {
390 if (Result.AgeSeconds >= 30 && (Result.HasCallbacks || Result.XmppClient is null || !Result.Peer.Tcp.Connected))
391 {
392 this.peersByFullJid.Remove(FullJID);
393 Old = Result;
394 Result = null;
395 b = false;
396 }
397 else if (Result.State != XmppState.Connected)
398 {
399 Result.AddCallback(Callback, State);
400 return;
401 }
402 }
403
404 if (!b)
405 {
406 Header = "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' from='" +
407 this.fullJid + "' to='" + FullJID + "' version='1.0'>";
408
409 Result = new PeerState(null, this, FullJID, Header, "</stream:stream>", string.Empty, 1.0, Callback, State);
410 this.peersByFullJid[FullJID] = Result;
411 }
412 }
413
414 if (b)
415 {
416 await Callback.Raise(this, new PeerConnectionEventArgs(Result.XmppClient, State, this.fullJid, FullJID));
417 return;
418 }
419 else if (!(Old is null))
420 {
421 Old.CallCallbacks();
422 await Old.DisposeAsync();
423 }
424
425 _ = Task.Run(async () =>
426 {
427 PeerConnection Connection;
428
429 try
430 {
431 Connection = await this.ConnectToAsync(FullJID, Info);
432 }
433 catch (Exception ex)
434 {
435 await this.Exception(ex);
436 Connection = null;
437
438 if (!(ResynchMethod is null))
439 {
440 try
441 {
442 ResynchEventArgs e = new ResynchEventArgs(FullJID, async (sender, e2) =>
443 {
444 try
445 {
446 if (e2.Ok)
447 await this.GetPeerConnection(FullJID, Callback, State, null);
448 else
449 {
450 lock (this.peersByFullJid)
451 {
452 this.peersByFullJid.Remove(FullJID);
453 }
454
455 Result.CallCallbacks();
456 }
457 }
458 catch (Exception ex2)
459 {
460 Log.Exception(ex2);
461 }
462 });
463
464 await ResynchMethod(this, e);
465 }
466 catch (Exception ex2)
467 {
468 Log.Exception(ex2);
469 }
470
471 return;
472 }
473 }
474
475 if (Connection is null)
476 {
477 lock (this.peersByFullJid)
478 {
479 this.peersByFullJid.Remove(FullJID);
480 }
481
482 Result.CallCallbacks();
483 }
484 else
485 {
486 Result.Peer = Connection;
487 Connection.Start(async (Sender, e) =>
488 {
489 if (!(ResynchMethod is null))
490 {
491 if (!await ResynchMethod.Raise(this, new ResynchEventArgs(FullJID, async (sender2, e2) =>
492 {
493 try
494 {
495 if (e2.Ok)
496 {
497 Result.Peer = null;
498 Connection = await this.ConnectToAsync(FullJID, Info);
499 Result.Peer = Connection;
500 Connection.Start();
501 Result.HeaderSent = true;
502 await Result.SendAsync(Header);
503 await this.TransmitText(Header);
504 }
505 else
506 Result.CallCallbacks();
507 }
508 catch (Exception ex)
509 {
510 await this.Exception(ex);
511 }
512 }), false))
513 {
514 Result.CallCallbacks();
515 }
516 }
517 else
518 Result.CallCallbacks();
519 });
520
521 Result.HeaderSent = true;
522 await Result.SendAsync(Header);
523 await this.TransmitText(Header);
524 }
525 });
526 }
527
528 private async Task<PeerConnection> ConnectToAsync(string FullJID, AddressInfo Info)
529 {
530 PeerConnection Connection;
531 string Ip;
532 int Port;
533
534 if (Info.ExternalIp == this.p2pNetwork.ExternalAddress.ToString())
535 {
536 Ip = Info.LocalIp;
537 Port = Info.LocalPort;
538 }
539 else
540 {
541 Ip = Info.ExternalIp;
542 Port = Info.ExternalPort;
543 }
544
545 if (IPAddress.TryParse(Ip, out IPAddress Addr))
546 {
547 await this.Information("Connecting to " + Ip + ":" + Port.ToString() + " (" + FullJID + ")");
548 Connection = await this.p2pNetwork.ConnectToPeer(new IPEndPoint(Addr, Port));
549 await this.Information("Connected to to " + Ip + ":" + Port.ToString() + " (" + FullJID + ")");
550 }
551 else
552 Connection = null;
553
554 return Connection;
555 }
556
560 [Obsolete("Use the DisposeAsync() method.")]
561 public async void Dispose()
562 {
563 try
564 {
565 await this.DisposeAsync();
566 }
567 catch (Exception ex)
568 {
569 Log.Exception(ex);
570 }
571 }
572
576 public async Task DisposeAsync()
577 {
578 if (!this.disposed)
579 {
580 if (!(this.p2pNetwork is null))
581 {
582 await this.p2pNetwork.DisposeAsync();
583 this.p2pNetwork = null;
584 }
585
586 if (!(this.peersByFullJid is null))
587 {
588 PeerState[] States;
589
590 lock (this.peersByFullJid)
591 {
592 States = new PeerState[this.peersByFullJid.Count];
593 this.peersByFullJid.Values.CopyTo(States, 0);
594
595 this.peersByFullJid.Clear();
596 }
597
598 this.peersByFullJid = null;
599
600 foreach (PeerState State in States)
601 {
602 State.ClearCallbacks();
603 await State.Close();
604 }
605 }
606
607 this.disposed = true;
608 }
609 }
610
615 public void AppendP2pInfo(StringBuilder Xml)
616 {
617 if (!(this.p2pNetwork is null) &&
618 this.p2pNetwork.State == PeerToPeerNetworkState.Ready &&
619 !(this.p2pNetwork.ExternalEndpoint is null))
620 {
621 Xml.Append("<p2p xmlns='");
623 Xml.Append("' extIp='");
624 Xml.Append(this.p2pNetwork.ExternalAddress.ToString());
625 Xml.Append("' extPort='");
626 Xml.Append(this.p2pNetwork.ExternalEndpoint.Port.ToString());
627 Xml.Append("' locIp='");
628 Xml.Append(this.p2pNetwork.LocalAddress.ToString());
629 Xml.Append("' locPort='");
630 Xml.Append(this.p2pNetwork.LocalEndpoint.Port.ToString());
631 Xml.Append("'/>");
632 }
633 }
634
641 public async Task<bool> AddPeerAddressInfo(string FullJID, XmlElement P2P)
642 {
643 string ExtIp = null;
644 int? ExtPort = null;
645 string LocIp = null;
646 int? LocPort = null;
647
648 if (!(P2P is null))
649 {
650 foreach (XmlAttribute Attr in P2P.Attributes)
651 {
652 switch (Attr.Name)
653 {
654 case "extIp":
655 if (IPAddress.TryParse(Attr.Value, out IPAddress Addr) && InternetGatewayRegistrator.IsPublicAddress(Addr))
656 ExtIp = Attr.Value;
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:13
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
Simple base class for classes implementing communication protocols.
Task Exception(Exception Exception)
Called to inform the viewer of an exception state.
ISniffer[] Sniffers
Registered sniffers.
Task Information(string Comment)
Called to inform the viewer of something.
Task TransmitText(string Text)
Called when text has been transmitted.
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:12
string ExternalIp
External IP Address.
Definition: AddressInfo.cs:44
string LocalIp
Local IP Address.
Definition: AddressInfo.cs:54
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:952
string RemoteFullJid
Remote Full JID
Definition: PeerState.cs:786
async Task Close()
CLoses the connection.
Definition: PeerState.cs:857
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.
Base class of XMPP exceptions
Interface for sniffers. Sniffers can be added to ICommunicationLayer classes to eavesdrop on communic...
Definition: ISniffer.cs:11
PeerToPeerNetworkState
State of Peer-to-peer network.
XmppState
State of XMPP connection.
Definition: XmppState.cs:7