Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
WebSocketSession.cs
1using System;
3using System.Text;
4using System.Threading.Tasks;
5using System.Xml;
7using Waher.Events;
12
14{
19 {
20 internal readonly WebSocketClientResource webSocketResource;
21 internal WebSocket webSocket;
22 private CaseInsensitiveString from;
23 private CaseInsensitiveString to;
24 private string language;
25 private double version;
26
27 private readonly string remoteEndPoint = string.Empty;
28 internal string streamId = null;
29 internal bool streamOpen = false;
30
41 : base(Server, XmppConnectionState.StreamNegotiation, Sniffers)
42 {
43 this.webSocketResource = WebSocketResource;
44 this.webSocket = WebSocket;
45 this.remoteEndPoint = RemoteEndPoint;
46 }
47
51 public override string Binding => "Web-Socket";
52
56 public double Version
57 {
58 get => this.version;
59 internal set => this.version = value;
60 }
61
66 {
67 get => this.from;
68 internal set => this.from = value;
69 }
70
75 {
76 get => this.to;
77 internal set => this.to = value;
78 }
79
83 public string Language
84 {
85 get => this.language;
86 internal set => this.language = value;
87 }
88
92 public bool IsBound
93 {
94 get => this.isBound;
95 internal set => this.isBound = value;
96 }
97
101 public override string RemoteEndPoint => this.remoteEndPoint;
102
106 public override string Protocol => "XMPP (WS)";
107
111 public override async Task DisposeAsync()
112 {
113 if (!this.disposed)
114 {
115 await this.SetState(XmppConnectionState.Offline);
116
117 if (this.isBound && !(this.Server is null))
118 {
119 this.isBound = false;
120 await this.ProcessFragment("<presence type=\"unavailable\" xmlns=\"jabber:client\"/>");
121 this.Server?.ConnectionClosed(this);
122 }
123
124 this.webSocket?.Close();
125 this.webSocket = null;
126
127 await base.DisposeAsync();
128 }
129 }
130
137 public override async Task<bool> BeginWrite(string Xml, EventHandlerAsync<DeliveryEventArgs> Callback, object State)
138 {
139 if (!this.disposed)
140 {
141 if (string.IsNullOrEmpty(Xml))
142 await Callback.Raise(this, new DeliveryEventArgs(State, true));
143 else if (!(this.webSocket is null))
144 {
145 await this.webSocket.Send(Xml, false, Callback, State);
146 this.TransmitText(Xml);
147 }
148 }
149
150 return true;
151 }
152
159 public override Task<bool> StreamError(string ErrorXml, string Reason)
160 {
161 return this.ToError("<error xmlns=\"http://etherx.jabber.org/streams\">" + ErrorXml + "</error>", Reason);
162 }
163
164 private async Task<bool> ToError(string ErrorXml, string Reason)
165 {
166 Log.Error("Terminating session. " + Reason, this.FullJid);
167
168 if (string.IsNullOrEmpty(ErrorXml))
169 {
170 await this.SetState(XmppConnectionState.Error);
171 await this.DisposeAsync();
172
173 return false;
174 }
175 else
176 {
177 return await this.BeginWrite(ErrorXml, async (Sender, e) =>
178 {
179 await this.SetState(XmppConnectionState.Error);
180 await this.DisposeAsync();
181 }, null);
182 }
183 }
184
185 internal void TouchConnection()
186 {
187 try
188 {
189 if (!string.IsNullOrEmpty(this.fullJid))
190 this.server.TouchClientConnection(this.fullJid);
191 }
192 catch (Exception ex)
193 {
194 Log.Exception(ex);
195 }
196 }
197
198 internal async Task<bool> ProcessFragment(string Xml)
199 {
200 XmlDocument Doc;
201
202 try
203 {
204 if (!string.IsNullOrEmpty(this.fullJid))
205 this.server?.TouchClientConnection(this.fullJid);
206
207 this.ReceiveText(Xml);
208
209 if (Xml.StartsWith("</"))
210 {
211 this.Information("Terminating session on client request.");
212
213 return await this.BeginWrite("</stream:stream>", async (Sender, e) =>
214 {
215 await this.SetState(XmppConnectionState.Offline);
216 await this.DisposeAsync();
217 }, null);
218 }
219 else
220 {
221 Doc = XML.ParseXml("<stream:stream xmlns:stream=\"http://etherx.jabber.org/streams\" xmlns=\"jabber:client\">" +
222 Xml + "</stream:stream>", true);
223
224 XmlElement Root = Doc.DocumentElement;
225 XmlElement StanzaElement = null;
226
227 foreach (XmlNode N in Root.ChildNodes)
228 {
229 if (N is XmlElement E)
230 {
231 StanzaElement = E;
232 break;
233 }
234 }
235
236 if (!(StanzaElement is null))
237 {
238 Stanza Stanza = new Stanza(Root, StanzaElement, StanzaElement.InnerXml);
239 return await this.ProcessStanza(Stanza);
240 }
241 }
242 }
243 catch (Exception ex)
244 {
245 this.Exception(ex);
246 return await this.StreamError("<bad-format xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>", ex.Message);
247 }
248
249 return true;
250 }
251
256 protected override SslStream GetSslStream()
257 {
258 return this.webSocket.ClientConnection.Stream as SslStream;
259 }
260
267 protected override async Task<bool> ProcessBindingSpecificStanza(Stanza Stanza, XmlElement StanzaElement)
268 {
269 switch (StanzaElement.LocalName)
270 {
271 case "open":
272 if (StanzaElement.NamespaceURI != WebSocketClientResource.FramingNamespace)
273 return await this.StreamErrorInvalidNamespace();
274
275 await this.InitStream();
276 break;
277
278 case "close":
279 if (StanzaElement.NamespaceURI != WebSocketClientResource.FramingNamespace)
280 return await this.StreamErrorInvalidNamespace();
281
282 string Tx = "<close xmlns='" + WebSocketClientResource.FramingNamespace + "'/>";
283
284 await (this.webSocket?.Send(Tx, false, async (Sender, e) =>
285 {
286 await this.DisposeAsync();
287 }, null) ?? Task.FromResult(false));
288
289 this.TransmitText(Tx);
290 break;
291
292 default:
293 if (!await this.StreamError("<unsupported-stanza-type xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>", "Unsupported stanza: " + StanzaElement.LocalName))
294 return false;
295 break;
296 }
297
298 return true;
299 }
300
301 internal async Task InitStream()
302 {
303 StringBuilder sb = new StringBuilder();
304
305 sb.Append("<features xmlns=\"http://etherx.jabber.org/streams\">");
306
307 if (!this.isAuthenticated)
308 {
309 await this.SetState(XmppConnectionState.Authenticating);
310
311 sb.Append("<mechanisms xmlns='" + XmppServer.SaslNamespace + "'>");
312
313 SslStream SslStream = this.webSocket.ClientConnection.Stream as SslStream;
314 foreach (IAuthenticationMechanism Mechanism in XmppServer.mechanisms)
315 {
316 if (Mechanism.Allowed(SslStream))
317 {
318 sb.Append("<mechanism>");
319 sb.Append(Mechanism.Name);
320 sb.Append("</mechanism>");
321 }
322 }
323
324 sb.Append("</mechanisms>");
325
326 if (await this.server.CanRegister(this))
327 sb.Append("<register xmlns='http://jabber.org/features/iq-register'/>");
328 }
329 else if (!this.isBound)
330 {
331 await this.SetState(XmppConnectionState.Binding);
332
333 sb.Append("<bind xmlns='" + XmppClientConnection.BindNamespace + "'/>");
334 sb.Append("<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>");
335 }
336
337 sb.Append("</features>");
338
339 string Tx = sb.ToString();
340 await this.webSocket.Send(Tx);
341 this.TransmitText(Tx);
342 }
343
348 public override bool CheckLive()
349 {
350 return !this.disposed && this.State != XmppConnectionState.Error && this.State != XmppConnectionState.Offline && this.webSocket.CheckLive();
351 }
352
353 }
354}
Helps with common XML-related tasks.
Definition: XML.cs:21
static XmlDocument ParseXml(string Xml)
Parses an XML Document from its string representation.
Definition: XML.cs:713
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 Error(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs an error event.
Definition: Log.cs:692
void TransmitText(string Text)
Called when text has been transmitted.
void Exception(Exception Exception)
Called to inform the viewer of an exception state.
void ReceiveText(string Text)
Called when text has been received.
ISniffer[] Sniffers
Registered sniffers.
void Information(string Comment)
Called to inform the viewer of something.
Event arguments for delivery events.
Class handling a web-socket.
Definition: WebSocket.cs:17
Task Close()
Closes the connection.
Definition: WebSocket.cs:906
async Task< bool > Send(string Payload, int MaxFrameLength)
Sends a text payload, possibly in multiple frames.
Definition: WebSocket.cs:607
bool CheckLive()
Checks if the connection is live.
Definition: WebSocket.cs:1094
Abstract base class for XMPP client connections
bool isAuthenticated
If user is authenticated
XmppConnectionState State
Current state of connection.
async Task< bool > ProcessStanza(Stanza Stanza)
Processes an XMPP Stanza.
XmppServer Server
XMPP Server serving the client.
Task< bool > StreamErrorInvalidNamespace()
Sends Stream Error that namespace is invalid.
Contains information about a stanza.
Definition: Stanza.cs:9
const string FramingNamespace
urn:ietf:params:xml:ns:xmpp-framing
WebSocketSession(WebSocketClientResource WebSocketResource, WebSocket WebSocket, string RemoteEndPoint, XmppServer Server, params ISniffer[] Sniffers)
Web-socket Session
CaseInsensitiveString To
To address (domain)
override string Protocol
String representing protocol being used.
override SslStream GetSslStream()
Returns the underlying encrypted stream.
override async Task DisposeAsync()
IDisposable.Dispose
override string RemoteEndPoint
Remote endpoint.
override bool CheckLive()
Checks if the connection is live.
override async Task< bool > ProcessBindingSpecificStanza(Stanza Stanza, XmlElement StanzaElement)
Processes a binding-specific stanza.
override Task< bool > StreamError(string ErrorXml, string Reason)
Returns a stream error.
override async Task< bool > BeginWrite(string Xml, EventHandlerAsync< DeliveryEventArgs > Callback, object State)
Starts sending an XML fragment to the client.
CaseInsensitiveString From
From address
const string SaslNamespace
urn:ietf:params:xml:ns:xmpp-sasl
Definition: XmppServer.cs:113
Represents a case-insensitive string.
static readonly CaseInsensitiveString Empty
Empty case-insensitive string
Interface for authentication mechanisms.
bool Allowed(SslStream SslStream)
Checks if a mechanism is allowed during the current conditions.
Interface for sniffers. Sniffers can be added to ICommunicationLayer classes to eavesdrop on communic...
Definition: ISniffer.cs:10
XmppConnectionState
State of XMPP connection.