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;
2using System.Net.Security;
3using System.Text;
4using System.Threading.Tasks;
5using System.Xml;
6using Waher.Events;
11
13{
18 {
19 internal readonly WebSocketClientResource webSocketResource;
20 internal WebSocket webSocket;
21 private CaseInsensitiveString from;
22 private CaseInsensitiveString to;
23 private string language;
24 private double version;
25
26 private readonly string remoteEndpoint = string.Empty;
27 internal string streamId = null;
28 internal bool streamOpen = false;
29
40 : base(Server, XmppConnectionState.StreamNegotiation, Sniffers)
41 {
42 this.webSocketResource = WebSocketResource;
43 this.webSocket = WebSocket;
44 this.remoteEndpoint = RemoteEndpoint;
45 }
46
50 public override string Binding => "Web-Socket";
51
55 public double Version
56 {
57 get => this.version;
58 internal set => this.version = value;
59 }
60
65 {
66 get => this.from;
67 internal set => this.from = value;
68 }
69
74 {
75 get => this.to;
76 internal set => this.to = value;
77 }
78
82 public string Language
83 {
84 get => this.language;
85 internal set => this.language = value;
86 }
87
91 public bool IsBound
92 {
93 get => this.isBound;
94 internal set => this.isBound = value;
95 }
96
100 public override string RemoteEndpoint => this.remoteEndpoint;
101
105 public override string Protocol => "XMPP (WS)";
106
110 public override async Task DisposeAsync()
111 {
112 if (!this.disposed)
113 {
114 await this.SetState(XmppConnectionState.Offline);
115
116 if (this.isBound && !(this.Server is null))
117 {
118 this.isBound = false;
119 await this.ProcessFragment("<presence type=\"unavailable\" xmlns=\"jabber:client\"/>");
120 this.Server?.ConnectionClosed(this);
121 }
122
123 this.webSocket?.Close();
124 this.webSocket = null;
125
126 await base.DisposeAsync();
127 }
128 }
129
136 public override async Task<bool> BeginWrite(string Xml, EventHandlerAsync<DeliveryEventArgs> Callback, object State)
137 {
138 if (!this.disposed)
139 {
140 if (string.IsNullOrEmpty(Xml))
141 await Callback.Raise(this, new DeliveryEventArgs(State, true));
142 else if (!(this.webSocket is null))
143 {
144 await this.webSocket.Send(Xml, false, Callback, State);
145 await this.TransmitText(Xml);
146 }
147 }
148
149 return true;
150 }
151
158 public override Task<bool> StreamError(string ErrorXml, string Reason)
159 {
160 return this.ToError("<error xmlns=\"http://etherx.jabber.org/streams\">" + ErrorXml + "</error>", Reason);
161 }
162
163 private async Task<bool> ToError(string ErrorXml, string Reason)
164 {
165 Log.Error("Terminating session. " + Reason, this.FullJid);
166
167 if (string.IsNullOrEmpty(ErrorXml))
168 {
169 await this.SetState(XmppConnectionState.Error);
170 await this.DisposeAsync();
171
172 return false;
173 }
174 else
175 {
176 return await this.BeginWrite(ErrorXml, async (Sender, e) =>
177 {
178 await this.SetState(XmppConnectionState.Error);
179 await this.DisposeAsync();
180 }, null);
181 }
182 }
183
184 internal void TouchConnection()
185 {
186 try
187 {
188 if (!string.IsNullOrEmpty(this.fullJid))
189 this.server.TouchClientConnection(this.fullJid);
190 }
191 catch (Exception ex)
192 {
193 Log.Exception(ex);
194 }
195 }
196
197 internal async Task<bool> ProcessFragment(string Xml)
198 {
199 XmlDocument Doc;
200
201 try
202 {
203 if (!string.IsNullOrEmpty(this.fullJid))
204 this.server.TouchClientConnection(this.fullJid);
205
206 await this.ReceiveText(Xml);
207
208 if (Xml.StartsWith("</"))
209 {
210 await this.Information("Terminating session on client request.");
211
212 return await this.BeginWrite("</stream:stream>", async (Sender, e) =>
213 {
214 await this.SetState(XmppConnectionState.Offline);
215 await this.DisposeAsync();
216 }, null);
217 }
218 else
219 {
220 Doc = new XmlDocument()
221 {
222 PreserveWhitespace = true
223 };
224 Doc.LoadXml("<stream:stream xmlns:stream=\"http://etherx.jabber.org/streams\" xmlns=\"jabber:client\">" +
225 Xml + "</stream:stream>");
226
227 XmlElement Root = Doc.DocumentElement;
228 XmlElement StanzaElement = null;
229
230 foreach (XmlNode N in Root.ChildNodes)
231 {
232 if (N is XmlElement E)
233 {
234 StanzaElement = E;
235 break;
236 }
237 }
238
239 if (!(StanzaElement is null))
240 {
241 Stanza Stanza = new Stanza(Root, StanzaElement, StanzaElement.InnerXml);
242 return await this.ProcessStanza(Stanza);
243 }
244 }
245 }
246 catch (Exception ex)
247 {
248 await this.Exception(ex);
249 return await this.StreamError("<bad-format xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>", ex.Message);
250 }
251
252 return true;
253 }
254
259 protected override SslStream GetSslStream()
260 {
261 return this.webSocket.ClientConnection.Stream as SslStream;
262 }
263
270 protected override async Task<bool> ProcessBindingSpecificStanza(Stanza Stanza, XmlElement StanzaElement)
271 {
272 switch (StanzaElement.LocalName)
273 {
274 case "open":
275 if (StanzaElement.NamespaceURI != WebSocketClientResource.FramingNamespace)
276 return await this.StreamErrorInvalidNamespace();
277
278 await this.InitStream();
279 break;
280
281 case "close":
282 if (StanzaElement.NamespaceURI != WebSocketClientResource.FramingNamespace)
283 return await this.StreamErrorInvalidNamespace();
284
285 string Tx = "<close xmlns='" + WebSocketClientResource.FramingNamespace + "'/>";
286
287 this.webSocket?.Send(Tx, false, async (Sender, e) =>
288 {
289 await this.DisposeAsync();
290 }, null);
291
292 await this.TransmitText(Tx);
293 break;
294
295 default:
296 if (!await this.StreamError("<unsupported-stanza-type xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>", "Unsupported stanza: " + StanzaElement.LocalName))
297 return false;
298 break;
299 }
300
301 return true;
302 }
303
304 internal async Task InitStream()
305 {
306 StringBuilder sb = new StringBuilder();
307
308 sb.Append("<features xmlns=\"http://etherx.jabber.org/streams\">");
309
310 if (!this.isAuthenticated)
311 {
312 await this.SetState(XmppConnectionState.Authenticating);
313
314 sb.Append("<mechanisms xmlns='" + XmppServer.SaslNamespace + "'>");
315
316 SslStream SslStream = this.webSocket.ClientConnection.Stream as SslStream;
317 foreach (IAuthenticationMechanism Mechanism in XmppServer.mechanisms)
318 {
319 if (Mechanism.Allowed(SslStream))
320 {
321 sb.Append("<mechanism>");
322 sb.Append(Mechanism.Name);
323 sb.Append("</mechanism>");
324 }
325 }
326
327 sb.Append("</mechanisms>");
328
329 if (await this.server.CanRegister(this))
330 sb.Append("<register xmlns='http://jabber.org/features/iq-register'/>");
331 }
332 else if (!this.isBound)
333 {
334 await this.SetState(XmppConnectionState.Binding);
335
336 sb.Append("<bind xmlns='" + XmppClientConnection.BindNamespace + "'/>");
337 sb.Append("<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>");
338 }
339
340 sb.Append("</features>");
341
342 string Tx = sb.ToString();
343 await this.webSocket.Send(Tx);
344 await this.TransmitText(Tx);
345 }
346
351 public override bool CheckLive()
352 {
353 return !this.disposed && this.State != XmppConnectionState.Error && this.State != XmppConnectionState.Offline && this.webSocket.CheckLive();
354 }
355
356 }
357}
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
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:682
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.
Task ReceiveText(string Text)
Called when text has been received.
Event arguments for delivery events.
Class handling a web-socket.
Definition: WebSocket.cs:100
Task Close()
Closes the connection.
Definition: WebSocket.cs:865
async Task Send(string Payload, int MaxFrameLength)
Sends a text payload, possibly in multiple frames.
Definition: WebSocket.cs:618
bool CheckLive()
Checks if the connection is live.
Definition: WebSocket.cs:1016
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:10
const string FramingNamespace
urn:ietf:params:xml:ns:xmpp-framing
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 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.
WebSocketSession(WebSocketClientResource WebSocketResource, WebSocket WebSocket, string RemoteEndpoint, XmppServer Server, params ISniffer[] Sniffers)
Web-socket Session
override string RemoteEndpoint
Remote endpoint.
CaseInsensitiveString From
From address
const string SaslNamespace
urn:ietf:params:xml:ns:xmpp-sasl
Definition: XmppServer.cs:108
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:11
XmppConnectionState
State of XMPP connection.