Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
ProvisioningComponent.cs
1using System;
3using System.IO;
5using System.Security.Cryptography.X509Certificates;
6using System.Text;
7using System.Text.RegularExpressions;
8using System.Threading.Tasks;
9using System.Xml;
10using Waher.Content;
12using Waher.Events;
26using Waher.Things;
28
30{
35 {
39 public const string NamespaceProvisioningTokenIeeeV1 = "urn:ieee:iot:prov:t:1.0";
40
44 public const string NamespaceProvisioningTokenNeuroFoundationV1 = "urn:nf:iot:prov:t:1.0";
45
49 public const string NamespaceProvisioningDeviceIeeeV1 = "urn:ieee:iot:prov:d:1.0";
50
54 public const string NamespaceProvisioningDeviceNeuroFoundationV1 = "urn:nf:iot:prov:d:1.0";
55
59 public const string NamespaceProvisioningOwnerIeeeV1 = "urn:ieee:iot:prov:o:1.0";
60
64 public const string NamespaceProvisioningOwnerNeuroFoundationV1 = "urn:nf:iot:prov:o:1.0";
65
69 public const string NamespaceIoTDiscoveryXsfV0 = "urn:xmpp:iot:discovery";
70
74 public const string NamespaceIoTDiscoveryIeeeV1 = "urn:ieee:iot:disco:1.0";
75
79 public const string NamespaceIoTDiscoveryNeuroFoundationV1 = "urn:nf:iot:disco:1.0";
80
84 public const string NamespaceSoftwareUpdatesIeeeV1 = "urn:ieee:iot:swu:1.0";
85
89 public const string NamespaceSoftwareUpdatesNeuroFoundationV1 = "urn:nf:iot:swu:1.0";
90
96 public static string NamespaceProvisioningToken(NamespaceSet Version)
97 {
98 switch (Version)
99 {
100 case NamespaceSet.XsfV0:
102 default:
103 case NamespaceSet.NeuroFoundationV1: return NamespaceProvisioningTokenNeuroFoundationV1;
104 }
105 }
106
112 public static string NamespaceProvisioningDevice(NamespaceSet Version)
113 {
114 switch (Version)
115 {
116 case NamespaceSet.XsfV0:
118 default:
119 case NamespaceSet.NeuroFoundationV1: return NamespaceProvisioningDeviceNeuroFoundationV1;
120 }
121 }
122
128 public static string NamespaceProvisioningOwner(NamespaceSet Version)
129 {
130 switch (Version)
131 {
132 case NamespaceSet.XsfV0:
134 default:
135 case NamespaceSet.NeuroFoundationV1: return NamespaceProvisioningOwnerNeuroFoundationV1;
136 }
137 }
138
144 public static string NamespaceIoTDiscovery(NamespaceSet Version)
145 {
146 return Version switch
147 {
148 NamespaceSet.XsfV0 => NamespaceIoTDiscoveryXsfV0,
149 NamespaceSet.IeeeV1 => NamespaceIoTDiscoveryIeeeV1,
151 };
152 }
153
159 public static string NamespaceSoftwareUpdates(NamespaceSet Version)
160 {
161 switch (Version)
162 {
163 case NamespaceSet.XsfV0:
165 default:
166 case NamespaceSet.NeuroFoundationV1: return NamespaceSoftwareUpdatesNeuroFoundationV1;
167 }
168 }
169
170 private Cache<int, KeyValuePair<string, KeyValuePair<byte[], X509Certificate2>>> tokenRequests = new Cache<int, KeyValuePair<string, KeyValuePair<byte[], X509Certificate2>>>(int.MaxValue, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1), true);
171 private readonly Cache<string, bool> challengedTokens = new Cache<string, bool>(int.MaxValue, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1), true);
172 private readonly Dictionary<string, KeyValuePair<byte[], X509Certificate2>> certificates = new Dictionary<string, KeyValuePair<byte[], X509Certificate2>>();
173 private readonly GeoSpatialComponent geo;
174 private int seqNr = 0;
175
184 string Name, GeoSpatialComponent Geo)
185 : base(Server, Subdomain, Name)
186 {
187 this.geo = Geo;
188
189 #region Neuro-Foundation V1 handlers
190
191 this.RegisterIqGetHandler("getToken", NamespaceProvisioningTokenNeuroFoundationV1, this.GetTokenHandler, true);
192 this.RegisterIqGetHandler("getTokenChallengeResponse", NamespaceProvisioningTokenNeuroFoundationV1, this.GetTokenChallengeResponseHandler, false);
193 this.RegisterIqGetHandler("getCertificate", NamespaceProvisioningTokenNeuroFoundationV1, this.GetCertificateHandler, false);
194
195 this.RegisterIqGetHandler("isFriend", NamespaceProvisioningDeviceNeuroFoundationV1, this.IsFriendHandler, true);
196 this.RegisterIqGetHandler("canRead", NamespaceProvisioningDeviceNeuroFoundationV1, this.CanReadHandler, false);
197 this.RegisterIqGetHandler("canControl", NamespaceProvisioningDeviceNeuroFoundationV1, this.CanControlHandler, false);
198
199 this.RegisterIqSetHandler("isFriendRule", NamespaceProvisioningOwnerNeuroFoundationV1, this.IsFriendRuleHandler, true);
200 this.RegisterIqSetHandler("canReadRule", NamespaceProvisioningOwnerNeuroFoundationV1, this.CanReadRuleHandler, false);
201 this.RegisterIqSetHandler("canControlRule", NamespaceProvisioningOwnerNeuroFoundationV1, this.CanControlRuleHandler, false);
202 this.RegisterIqSetHandler("clearCache", NamespaceProvisioningOwnerNeuroFoundationV1, this.ClearCacheHandler, false);
203 this.RegisterIqGetHandler("getDevices", NamespaceProvisioningOwnerNeuroFoundationV1, this.GetDevicesHandler, false);
204 this.RegisterIqSetHandler("deleteRules", NamespaceProvisioningOwnerNeuroFoundationV1, this.DeleteRulesHandler, false);
205
206 this.RegisterIqSetHandler("register", NamespaceIoTDiscoveryNeuroFoundationV1, this.RegisterHandler, true);
207 this.RegisterIqSetHandler("mine", NamespaceIoTDiscoveryNeuroFoundationV1, this.MineHandler, false);
208 this.RegisterIqSetHandler("update", NamespaceIoTDiscoveryNeuroFoundationV1, this.UpdateHandler, false);
209 this.RegisterIqSetHandler("remove", NamespaceIoTDiscoveryNeuroFoundationV1, this.RemoveHandler, false);
210 this.RegisterIqSetHandler("unregister", NamespaceIoTDiscoveryNeuroFoundationV1, this.UnregisterHandler, false);
211 this.RegisterIqSetHandler("disown", NamespaceIoTDiscoveryNeuroFoundationV1, this.DisownHandler, false);
212 this.RegisterIqGetHandler("search", NamespaceIoTDiscoveryNeuroFoundationV1, this.SearchHandler, false);
213
214 this.RegisterIqGetHandler("getPackageInfo", NamespaceSoftwareUpdatesNeuroFoundationV1, this.GetPackageInfoHandler, true);
215 this.RegisterIqGetHandler("getPackages", NamespaceSoftwareUpdatesNeuroFoundationV1, this.GetPackagesHandler, false);
216 this.RegisterIqSetHandler("subscribe", NamespaceSoftwareUpdatesNeuroFoundationV1, this.SubscribeHandler, false);
217 this.RegisterIqSetHandler("unsubscribe", NamespaceSoftwareUpdatesNeuroFoundationV1, this.UnsubscribeHandler, false);
218 this.RegisterIqGetHandler("getSubscriptions", NamespaceSoftwareUpdatesNeuroFoundationV1, this.GetSubscriptionsHandler, false);
219
220 #endregion
221
222 #region IEEE V1 handlers
223
224 this.RegisterIqGetHandler("getToken", NamespaceProvisioningTokenIeeeV1, this.GetTokenHandler, true);
225 this.RegisterIqGetHandler("getTokenChallengeResponse", NamespaceProvisioningTokenIeeeV1, this.GetTokenChallengeResponseHandler, false);
226 this.RegisterIqGetHandler("getCertificate", NamespaceProvisioningTokenIeeeV1, this.GetCertificateHandler, false);
227
228 this.RegisterIqGetHandler("isFriend", NamespaceProvisioningDeviceIeeeV1, this.IsFriendHandler, true);
229 this.RegisterIqGetHandler("canRead", NamespaceProvisioningDeviceIeeeV1, this.CanReadHandler, false);
230 this.RegisterIqGetHandler("canControl", NamespaceProvisioningDeviceIeeeV1, this.CanControlHandler, false);
231
232 this.RegisterIqSetHandler("isFriendRule", NamespaceProvisioningOwnerIeeeV1, this.IsFriendRuleHandler, true);
233 this.RegisterIqSetHandler("canReadRule", NamespaceProvisioningOwnerIeeeV1, this.CanReadRuleHandler, false);
234 this.RegisterIqSetHandler("canControlRule", NamespaceProvisioningOwnerIeeeV1, this.CanControlRuleHandler, false);
235 this.RegisterIqSetHandler("clearCache", NamespaceProvisioningOwnerIeeeV1, this.ClearCacheHandler, false);
236 this.RegisterIqGetHandler("getDevices", NamespaceProvisioningOwnerIeeeV1, this.GetDevicesHandler, false);
237 this.RegisterIqSetHandler("deleteRules", NamespaceProvisioningOwnerIeeeV1, this.DeleteRulesHandler, false);
238
239 this.RegisterIqSetHandler("register", NamespaceIoTDiscoveryIeeeV1, this.RegisterHandler, true);
240 this.RegisterIqSetHandler("mine", NamespaceIoTDiscoveryIeeeV1, this.MineHandler, false);
241 this.RegisterIqSetHandler("update", NamespaceIoTDiscoveryIeeeV1, this.UpdateHandler, false);
242 this.RegisterIqSetHandler("remove", NamespaceIoTDiscoveryIeeeV1, this.RemoveHandler, false);
243 this.RegisterIqSetHandler("unregister", NamespaceIoTDiscoveryIeeeV1, this.UnregisterHandler, false);
244 this.RegisterIqSetHandler("disown", NamespaceIoTDiscoveryIeeeV1, this.DisownHandler, false);
245 this.RegisterIqGetHandler("search", NamespaceIoTDiscoveryIeeeV1, this.SearchHandler, false);
246
247 this.RegisterIqGetHandler("getPackageInfo", NamespaceSoftwareUpdatesIeeeV1, this.GetPackageInfoHandler, true);
248 this.RegisterIqGetHandler("getPackages", NamespaceSoftwareUpdatesIeeeV1, this.GetPackagesHandler, false);
249 this.RegisterIqSetHandler("subscribe", NamespaceSoftwareUpdatesIeeeV1, this.SubscribeHandler, false);
250 this.RegisterIqSetHandler("unsubscribe", NamespaceSoftwareUpdatesIeeeV1, this.UnsubscribeHandler, false);
251 this.RegisterIqGetHandler("getSubscriptions", NamespaceSoftwareUpdatesIeeeV1, this.GetSubscriptionsHandler, false);
252
253 #endregion
254
255 #region XSF handlers
256
257 this.RegisterIqSetHandler("register", NamespaceIoTDiscoveryXsfV0, this.RegisterHandler, true);
258 this.RegisterIqSetHandler("mine", NamespaceIoTDiscoveryXsfV0, this.MineHandler, false);
259 this.RegisterIqSetHandler("update", NamespaceIoTDiscoveryXsfV0, this.UpdateHandler, false);
260 this.RegisterIqSetHandler("remove", NamespaceIoTDiscoveryXsfV0, this.RemoveHandler, false);
261 this.RegisterIqSetHandler("unregister", NamespaceIoTDiscoveryXsfV0, this.UnregisterHandler, false);
262 this.RegisterIqSetHandler("disown", NamespaceIoTDiscoveryXsfV0, this.DisownHandler, false);
263 this.RegisterIqGetHandler("search", NamespaceIoTDiscoveryXsfV0, this.SearchHandler, false);
264
265 #endregion
266 }
267
271 public override void Dispose()
272 {
273 #region Neuro-Foundation V1 handlers
274
275 this.UnregisterIqGetHandler("getToken", NamespaceProvisioningTokenNeuroFoundationV1, this.GetTokenHandler, true);
276 this.UnregisterIqGetHandler("getTokenChallengeResponse", NamespaceProvisioningTokenNeuroFoundationV1, this.GetTokenChallengeResponseHandler, false);
277 this.UnregisterIqGetHandler("getCertificate", NamespaceProvisioningTokenNeuroFoundationV1, this.GetCertificateHandler, false);
278
279 this.UnregisterIqGetHandler("isFriend", NamespaceProvisioningDeviceNeuroFoundationV1, this.IsFriendHandler, true);
280 this.UnregisterIqGetHandler("canRead", NamespaceProvisioningDeviceNeuroFoundationV1, this.CanReadHandler, false);
281 this.UnregisterIqGetHandler("canControl", NamespaceProvisioningDeviceNeuroFoundationV1, this.CanControlHandler, false);
282
283 this.UnregisterIqSetHandler("isFriendRule", NamespaceProvisioningOwnerNeuroFoundationV1, this.IsFriendRuleHandler, true);
284 this.UnregisterIqSetHandler("canReadRule", NamespaceProvisioningOwnerNeuroFoundationV1, this.CanReadRuleHandler, false);
285 this.UnregisterIqSetHandler("canControlRule", NamespaceProvisioningOwnerNeuroFoundationV1, this.CanControlRuleHandler, false);
286 this.UnregisterIqSetHandler("clearCache", NamespaceProvisioningOwnerNeuroFoundationV1, this.ClearCacheHandler, false);
287 this.UnregisterIqGetHandler("getDevices", NamespaceProvisioningOwnerNeuroFoundationV1, this.GetDevicesHandler, false);
288 this.UnregisterIqSetHandler("deleteRules", NamespaceProvisioningOwnerNeuroFoundationV1, this.DeleteRulesHandler, false);
289
290 this.UnregisterIqSetHandler("register", NamespaceIoTDiscoveryNeuroFoundationV1, this.UnregisterHandler, true);
291 this.UnregisterIqSetHandler("mine", NamespaceIoTDiscoveryNeuroFoundationV1, this.MineHandler, false);
292 this.UnregisterIqSetHandler("update", NamespaceIoTDiscoveryNeuroFoundationV1, this.UpdateHandler, false);
293 this.UnregisterIqSetHandler("remove", NamespaceIoTDiscoveryNeuroFoundationV1, this.RemoveHandler, false);
294 this.UnregisterIqSetHandler("unregister", NamespaceIoTDiscoveryNeuroFoundationV1, this.UnregisterHandler, false);
295 this.UnregisterIqSetHandler("disown", NamespaceIoTDiscoveryNeuroFoundationV1, this.DisownHandler, false);
296 this.UnregisterIqGetHandler("search", NamespaceIoTDiscoveryNeuroFoundationV1, this.SearchHandler, false);
297
298 this.UnregisterIqGetHandler("getPackageInfo", NamespaceSoftwareUpdatesNeuroFoundationV1, this.GetPackageInfoHandler, true);
299 this.UnregisterIqGetHandler("getPackages", NamespaceSoftwareUpdatesNeuroFoundationV1, this.GetPackagesHandler, false);
300 this.UnregisterIqSetHandler("subscribe", NamespaceSoftwareUpdatesNeuroFoundationV1, this.SubscribeHandler, false);
301 this.UnregisterIqSetHandler("unsubscribe", NamespaceSoftwareUpdatesNeuroFoundationV1, this.UnsubscribeHandler, false);
302 this.UnregisterIqGetHandler("getSubscriptions", NamespaceSoftwareUpdatesNeuroFoundationV1, this.GetSubscriptionsHandler, false);
303
304 #endregion
305
306 #region IEEE V1 handlers
307
308 this.UnregisterIqGetHandler("getToken", NamespaceProvisioningTokenIeeeV1, this.GetTokenHandler, true);
309 this.UnregisterIqGetHandler("getTokenChallengeResponse", NamespaceProvisioningTokenIeeeV1, this.GetTokenChallengeResponseHandler, false);
310 this.UnregisterIqGetHandler("getCertificate", NamespaceProvisioningTokenIeeeV1, this.GetCertificateHandler, false);
311
312 this.UnregisterIqGetHandler("isFriend", NamespaceProvisioningDeviceIeeeV1, this.IsFriendHandler, true);
313 this.UnregisterIqGetHandler("canRead", NamespaceProvisioningDeviceIeeeV1, this.CanReadHandler, false);
314 this.UnregisterIqGetHandler("canControl", NamespaceProvisioningDeviceIeeeV1, this.CanControlHandler, false);
315
316 this.UnregisterIqSetHandler("isFriendRule", NamespaceProvisioningOwnerIeeeV1, this.IsFriendRuleHandler, true);
317 this.UnregisterIqSetHandler("canReadRule", NamespaceProvisioningOwnerIeeeV1, this.CanReadRuleHandler, false);
318 this.UnregisterIqSetHandler("canControlRule", NamespaceProvisioningOwnerIeeeV1, this.CanControlRuleHandler, false);
319 this.UnregisterIqSetHandler("clearCache", NamespaceProvisioningOwnerIeeeV1, this.ClearCacheHandler, false);
320 this.UnregisterIqGetHandler("getDevices", NamespaceProvisioningOwnerIeeeV1, this.GetDevicesHandler, false);
321 this.UnregisterIqSetHandler("deleteRules", NamespaceProvisioningOwnerIeeeV1, this.DeleteRulesHandler, false);
322
323 this.UnregisterIqSetHandler("register", NamespaceIoTDiscoveryIeeeV1, this.UnregisterHandler, true);
324 this.UnregisterIqSetHandler("mine", NamespaceIoTDiscoveryIeeeV1, this.MineHandler, false);
325 this.UnregisterIqSetHandler("update", NamespaceIoTDiscoveryIeeeV1, this.UpdateHandler, false);
326 this.UnregisterIqSetHandler("remove", NamespaceIoTDiscoveryIeeeV1, this.RemoveHandler, false);
327 this.UnregisterIqSetHandler("unregister", NamespaceIoTDiscoveryIeeeV1, this.UnregisterHandler, false);
328 this.UnregisterIqSetHandler("disown", NamespaceIoTDiscoveryIeeeV1, this.DisownHandler, false);
329 this.UnregisterIqGetHandler("search", NamespaceIoTDiscoveryIeeeV1, this.SearchHandler, false);
330
331 this.UnregisterIqGetHandler("getPackageInfo", NamespaceSoftwareUpdatesIeeeV1, this.GetPackageInfoHandler, true);
332 this.UnregisterIqGetHandler("getPackages", NamespaceSoftwareUpdatesIeeeV1, this.GetPackagesHandler, false);
333 this.UnregisterIqSetHandler("subscribe", NamespaceSoftwareUpdatesIeeeV1, this.SubscribeHandler, false);
334 this.UnregisterIqSetHandler("unsubscribe", NamespaceSoftwareUpdatesIeeeV1, this.UnsubscribeHandler, false);
335 this.UnregisterIqGetHandler("getSubscriptions", NamespaceSoftwareUpdatesIeeeV1, this.GetSubscriptionsHandler, false);
336
337 #endregion
338
339 #region XSF handlers
340
341 this.UnregisterIqSetHandler("register", NamespaceIoTDiscoveryXsfV0, this.UnregisterHandler, true);
342 this.UnregisterIqSetHandler("mine", NamespaceIoTDiscoveryXsfV0, this.MineHandler, false);
343 this.UnregisterIqSetHandler("update", NamespaceIoTDiscoveryXsfV0, this.UpdateHandler, false);
344 this.UnregisterIqSetHandler("remove", NamespaceIoTDiscoveryXsfV0, this.RemoveHandler, false);
345 this.UnregisterIqSetHandler("unregister", NamespaceIoTDiscoveryXsfV0, this.UnregisterHandler, false);
346 this.UnregisterIqSetHandler("disown", NamespaceIoTDiscoveryXsfV0, this.DisownHandler, false);
347 this.UnregisterIqGetHandler("search", NamespaceIoTDiscoveryXsfV0, this.SearchHandler, false);
348
349 #endregion
350
351 this.tokenRequests?.Dispose();
352 this.tokenRequests = null;
353
354 this.registryAccounts?.Clear();
355 this.registryAccounts?.Dispose();
356 this.registryAccounts = null;
357 }
358
363 public override bool SupportsAccounts => false;
364
365 #region Provisioning, Tokens
366
367 private Task GetTokenHandler(object Sender, IqEventArgs e)
368 {
369 try
370 {
371 string Base64 = e.Query.InnerText;
372 byte[] Bin = Convert.FromBase64String(Base64);
373 X509Certificate2 Certificate = new X509Certificate2(Bin);
374
375 if (Certificate.Verify())
376 {
378 string Response = Convert.ToBase64String(Bin);
379 Bin = ((RSACryptoServiceProvider)Certificate.PublicKey.Key).Encrypt(Bin, true);
380 string Challenge = Convert.ToBase64String(Bin);
381
382 int SeqNr;
383
384 lock (this.synchObject)
385 {
386 do
387 {
388 SeqNr = this.seqNr++;
389 }
390 while (this.tokenRequests.ContainsKey(SeqNr));
391
392 this.tokenRequests.Add(SeqNr, new KeyValuePair<string, KeyValuePair<byte[], X509Certificate2>>(Response,
393 new KeyValuePair<byte[], X509Certificate2>(Bin, Certificate)));
394 }
395
396 e.IqResult("<getTokenChallenge xmlns='" + e.Query.NamespaceURI + "' seqnr='" +
397 SeqNr.ToString() + "'>" + Challenge + "</getTokenChallenge>", e.To);
398 }
399 else
400 e.IqErrorNotAcceptable(e.To, "Invalid certificate.", "en");
401 }
402 catch (Exception)
403 {
404 e.IqErrorBadRequest(e.To, "Invalid certificate.", "en");
405 }
406
407 return Task.CompletedTask;
408 }
409
410 private Task GetTokenChallengeResponseHandler(object Sender, IqEventArgs e)
411 {
412 XmlElement E = e.Query;
413 int SeqNr = XML.Attribute(E, "seqnr", 0);
414
415 if (!this.tokenRequests.TryGetValue(SeqNr, out KeyValuePair<string, KeyValuePair<byte[], X509Certificate2>> Rec))
416 e.IqErrorItemNotFound(e.To, "Sequence number not recognized.", "en");
417 else
418 {
419 this.tokenRequests.Remove(SeqNr);
420
421 string Response = E.InnerText;
422 if (Response != Rec.Key)
423 e.IqErrorBadRequest(e.To, "Invalid response.", "en");
424 else
425 {
426 byte[] Bin = XmppServer.GetRandomNumbers(64);
427 string Token = Convert.ToBase64String(Bin);
428
429 lock (this.certificates)
430 {
431 this.certificates[Token] = Rec.Value;
432 }
433
434 e.IqResult("<getTokenResponse xmlns='" + e.Query.NamespaceURI + "' token='" + e.To + ":" + Token + "'/>",
435 e.To);
436 }
437 }
438
439 return Task.CompletedTask;
440 }
441
442 private class TokenChallengesRecord
443 {
444 public NamespaceSet QueryVersion;
445 public Dictionary<string, bool> TokensToChallenge;
446 public IqEventArgs e;
447 public List<ThingReference> Nodes = null;
448 public List<string> Fields = null;
449 public FieldType FieldTypes;
450 public string[] ServiceTokens;
451 public string[] DeviceTokens;
452 public string[] UserTokens;
453 public bool Allowed = false;
454 public CaseInsensitiveString Jid;
455 public int NrTokensToChallenge;
456 public bool AllOk = true;
457 public bool Read;
458 }
459
460 private class TokenChallengeRecord
461 {
462 public TokenChallengesRecord Challenges;
463 public string Token;
464 public string Response;
465 }
466
467 private async Task TokenChallengeResponse(object Sender, IqResultEventArgs e)
468 {
469 TokenChallengeRecord Rec2 = (TokenChallengeRecord)e.State;
470 TokenChallengesRecord Rec = Rec2.Challenges;
471 XmlElement E = e.FirstElement;
472 bool Done;
473
474 if (e.Ok && !(E is null) && E.LocalName == "tokenChallengeResponse" && E.InnerText == Rec2.Response)
475 {
476 this.challengedTokens.Add(e.From + " " + Rec2.Token, true);
477
478 lock (Rec.TokensToChallenge)
479 {
480 Rec.TokensToChallenge[Rec2.Token] = true;
481 Rec.NrTokensToChallenge--;
482 Done = Rec.NrTokensToChallenge == 0;
483 }
484 }
485 else
486 {
487 lock (Rec.TokensToChallenge)
488 {
489 Rec.AllOk = false;
490 Rec.NrTokensToChallenge--;
491 Done = Rec.NrTokensToChallenge == 0;
492 }
493 }
494
495 if (Done)
496 {
497 if (Rec.Read)
498 {
499 if (!Rec.AllOk)
500 Rec.Allowed = false;
501
502 await this.TestAndSendCanReadResponse(Rec.Jid, Rec.e, Rec.Allowed, Rec.FieldTypes, Rec.Nodes, Rec.Fields,
503 Rec.ServiceTokens, Rec.DeviceTokens, Rec.UserTokens, Rec.QueryVersion);
504 }
505 else
506 {
507 if (Rec.AllOk)
508 Rec.Allowed = false;
509
510 await this.TestAndSendCanControlResponse(Rec.Jid, Rec.e, Rec.Allowed, Rec.Nodes, Rec.Fields,
511 Rec.ServiceTokens, Rec.DeviceTokens, Rec.UserTokens, Rec.QueryVersion);
512 }
513 }
514 }
515
516 private void AddValidTokens(Dictionary<string, bool> TokensToChallenge, string[] Tokens, string From)
517 {
518 foreach (string Token in Tokens)
519 {
520 if (!TokensToChallenge.ContainsKey(Token) && !this.challengedTokens.ContainsKey(From + " " + Token))
521 {
522 lock (this.certificates)
523 {
524 if (!this.certificates.ContainsKey(Token))
525 continue;
526 }
527
528 TokensToChallenge[Token] = false;
529 }
530 }
531 }
532
533 private Task GetCertificateHandler(object Sender, IqEventArgs e)
534 {
535 KeyValuePair<byte[], X509Certificate2> Rec;
536 string Token = XML.Attribute(e.Query, "token");
537 bool Found;
538 int i;
539
540 i = Token.IndexOf(':');
541 if (i > 0)
542 {
543 string Address = Token[..i];
544 Token = Token[(i + 1)..];
545
546 if (this.IsComponentDomain(Address, true))
547 {
548 e.IqErrorItemNotFound(e.To, "Token not found.", "en");
549 return Task.CompletedTask;
550 }
551 }
552
553 lock (this.certificates)
554 {
555 Found = this.certificates.TryGetValue(Token, out Rec);
556 }
557
558 if (!Found)
559 e.IqErrorItemNotFound(e.To, "Token not found.", "en");
560 else
561 e.IqResult("<certificate xmlns='" + e.Query.NamespaceURI + "'>" + Convert.ToBase64String(Rec.Key) + "</certificate>", e.To);
562
563 return Task.CompletedTask;
564 }
565
566 #endregion
567
568 #region Provisioning, Device interface
569
570 private async Task IsFriendHandler(object Sender, IqEventArgs e)
571 {
573 CaseInsensitiveString RemoteJID = XML.Attribute(e.Query, "jid");
574 NamespaceSet QueryVersion = XmppServerModule.GetVersion(e.Query.NamespaceURI);
575 bool AreFrieds;
576
577 Registration Registration = await this.GetRegistration(JID, string.Empty, string.Empty, string.Empty, QueryVersion, null);
578 if (Registration is null || string.IsNullOrEmpty(Registration.Owner))
579 AreFrieds = false;
580 else
581 {
582 FriendshipRule Rule = await Database.FindFirstDeleteRest<FriendshipRule>(new FilterAnd(
583 new FilterFieldEqualTo("JID", JID), new FilterFieldEqualTo("RemoteJID", RemoteJID)));
584
585 if (!(Rule is null))
586 AreFrieds = Rule.CanSubscribeToPresence;
587 else
588 {
589 string RemoteDomain = new XmppAddress(RemoteJID).Domain;
590
591 Rule = await Database.FindFirstDeleteRest<FriendshipRule>(new FilterAnd(
592 new FilterFieldEqualTo("JID", JID), new FilterFieldEqualTo("RemoteJID", RemoteDomain)));
593
594 if (!(Rule is null))
595 AreFrieds = Rule.CanSubscribeToPresence;
596 else
597 {
598 Rule = await Database.FindFirstDeleteRest<FriendshipRule>(new FilterAnd(
599 new FilterFieldEqualTo("JID", JID), new FilterFieldEqualTo("RemoteJID", string.Empty)));
600
601 if (!(Rule is null))
602 AreFrieds = Rule.CanSubscribeToPresence;
603 else
604 {
605 AreFrieds = false;
606
607 Rule = new FriendshipRule()
608 {
609 JID = JID,
610 RemoteJID = RemoteJID,
611 CanSubscribeToPresence = false
612 };
613
614 await Database.Insert(Rule);
615
616 StringBuilder Xml = new StringBuilder();
617
618 Xml.Append("<isFriend xmlns='");
619 Xml.Append(NamespaceProvisioningOwner(Registration.OwnerVersion));
620 Xml.Append("' jid='");
621 Xml.Append(XML.Encode(JID));
622 Xml.Append("' remoteJid='");
623 Xml.Append(XML.Encode(RemoteJID));
624 Xml.Append("' key='");
625 Xml.Append(XML.Encode(Rule.ObjectId.ToString()));
626 Xml.Append("'/>");
627
628 await this.Server.SendMessage(string.Empty, string.Empty, e.To, new XmppAddress(Registration.Owner), string.Empty, Xml.ToString());
629 }
630 }
631 }
632 }
633
634 await e.IqResult("<isFriendResponse xmlns='" + e.Query.NamespaceURI + "' jid='" + XML.Encode(RemoteJID) + "' result='" +
635 CommonTypes.Encode(AreFrieds) + "'/>", e.To);
636 }
637
638 private async Task CanReadHandler(object Sender, IqEventArgs e)
639 {
640 List<ThingReference> Nodes = null;
641 List<string> Fields = null;
642 XmlElement E = e.Query;
643 FieldType FieldTypes = 0;
644 string ServiceToken = string.Empty;
645 string DeviceToken = string.Empty;
646 string UserToken = string.Empty;
648 NamespaceSet QueryVersion = XmppServerModule.GetVersion(e.Query.NamespaceURI);
649 bool b;
650
651 foreach (XmlAttribute Attr in E.Attributes)
652 {
653 switch (Attr.Name)
654 {
655 case "st":
656 ServiceToken = Attr.Value;
657 break;
658
659 case "dt":
660 DeviceToken = Attr.Value;
661 break;
662
663 case "ut":
664 UserToken = Attr.Value;
665 break;
666
667 case "jid":
668 Jid = Attr.Value;
669 break;
670
671 case "all":
672 if (CommonTypes.TryParse(Attr.Value, out b) && b)
673 FieldTypes |= FieldType.All;
674 break;
675
676 case "h":
677 if (CommonTypes.TryParse(Attr.Value, out b) && b)
678 FieldTypes |= FieldType.Historical;
679 break;
680
681 case "m":
682 if (CommonTypes.TryParse(Attr.Value, out b) && b)
683 FieldTypes |= FieldType.Momentary;
684 break;
685
686 case "p":
687 if (CommonTypes.TryParse(Attr.Value, out b) && b)
688 FieldTypes |= FieldType.Peak;
689 break;
690
691 case "s":
692 if (CommonTypes.TryParse(Attr.Value, out b) && b)
693 FieldTypes |= FieldType.Status;
694 break;
695
696 case "c":
697 if (CommonTypes.TryParse(Attr.Value, out b) && b)
698 FieldTypes |= FieldType.Computed;
699 break;
700
701 case "i":
702 if (CommonTypes.TryParse(Attr.Value, out b) && b)
703 FieldTypes |= FieldType.Identity;
704 break;
705 }
706 }
707
708 foreach (XmlNode N in E.ChildNodes)
709 {
710 switch (N.LocalName)
711 {
712 case "nd":
713 E = (XmlElement)N;
714 Nodes ??= new List<ThingReference>();
715 Nodes.Add(this.ParseNodeInfo(E));
716 break;
717
718 case "f":
719 Fields ??= new List<string>();
720 Fields.Add(XML.Attribute((XmlElement)N, "n"));
721 break;
722 }
723 }
724
725 string[] ServiceTokens = ServiceToken.Split(space, StringSplitOptions.RemoveEmptyEntries);
726 string[] DeviceTokens = DeviceToken.Split(space, StringSplitOptions.RemoveEmptyEntries);
727 string[] UserTokens = UserToken.Split(space, StringSplitOptions.RemoveEmptyEntries);
728 Dictionary<string, bool> TokensToChallenge = null;
729 int NrTokensToChallenge;
730
731 if (ServiceTokens.Length == 0 && DeviceTokens.Length == 0 && UserTokens.Length == 0)
732 NrTokensToChallenge = 0;
733 else
734 {
735 TokensToChallenge = new Dictionary<string, bool>();
736
737 this.AddValidTokens(TokensToChallenge, ServiceTokens, e.From.Address);
738 this.AddValidTokens(TokensToChallenge, DeviceTokens, e.From.Address);
739 this.AddValidTokens(TokensToChallenge, UserTokens, e.From.Address);
740
741 NrTokensToChallenge = TokensToChallenge.Count;
742 if (NrTokensToChallenge > 0)
743 {
744 TokenChallengesRecord Rec = new TokenChallengesRecord()
745 {
746 TokensToChallenge = TokensToChallenge,
747 Nodes = Nodes,
748 Fields = Fields,
749 FieldTypes = FieldTypes,
750 Jid = Jid,
751 ServiceTokens = ServiceTokens,
752 DeviceTokens = DeviceTokens,
753 UserTokens = UserTokens,
754 e = e,
755 NrTokensToChallenge = NrTokensToChallenge,
756 Allowed = true,
757 Read = true,
758 QueryVersion = QueryVersion
759 };
760
761 string[] Tokens = new string[TokensToChallenge.Count];
762 TokensToChallenge.Keys.CopyTo(Tokens, 0);
763
764 foreach (string Token in Tokens)
765 {
766 KeyValuePair<byte[], X509Certificate2> Certificate;
767
768 lock (this.certificates)
769 {
770 Certificate = this.certificates[Token];
771 }
772
773 byte[] Bin = XmppServer.GetRandomNumbers(64);
774 string Response = Convert.ToBase64String(Bin);
775 Bin = ((RSACryptoServiceProvider)Certificate.Value.PublicKey.Key).Encrypt(Bin, true);
776 string Challenge = Convert.ToBase64String(Bin);
777
778 TokenChallengeRecord Rec2 = new TokenChallengeRecord()
779 {
780 Challenges = Rec,
781 Token = Token,
782 Response = Response
783 };
784
785 await this.Server.SendIqRequest("get", e.To, e.From, string.Empty,
786 "<tokenChallenge xmlns='" + NamespaceProvisioningToken(QueryVersion) + "' token='" +
787 XML.Encode(Token) + "'>" + Challenge + "</tokenChallenge>", false, this.TokenChallengeResponse, Rec2);
788 }
789
790 return;
791 }
792 }
793
794 await this.TestAndSendCanReadResponse(Jid, e, true, FieldTypes, Nodes, Fields, ServiceTokens, DeviceTokens, UserTokens, QueryVersion);
795 }
796
797 private async Task TestAndSendCanReadResponse(CaseInsensitiveString Jid, IqEventArgs e, bool Allowed, FieldType FieldTypes,
798 List<ThingReference> Nodes, List<string> Fields, string[] ServiceTokens, string[] DeviceTokens, string[] UserTokens,
799 NamespaceSet QueryVersion)
800 {
801 if (Allowed)
802 {
804 CaseInsensitiveString RemoteJID = Jid;
805
806 List<ThingReference> NodesAllowed = null;
807 CaseInsensitiveString RemoteDomain = GetDomain(RemoteJID);
808
809 if (Nodes is null || Nodes.Count == 0)
810 Nodes = new List<ThingReference>() { ThingReference.Empty };
811
812 Context Context = new Context()
813 {
814 RemoteJid = RemoteJID,
815 RemoteDomain = RemoteDomain,
816 Types = FieldTypes,
817 ServiceTokens = ServiceTokens,
818 DeviceTokens = DeviceTokens,
819 UserTokens = UserTokens
820 };
821
822 if (!(Fields is null))
823 {
824 Context.Names = new Dictionary<string, bool>();
825
826 foreach (string Field in Fields)
827 Context.Names[Field] = true;
828 }
829
830 foreach (ThingReference Node in Nodes)
831 {
832 Registration Registration = await this.GetRegistration(JID, Node.NodeId, Node.SourceId, Node.Partition, QueryVersion, null);
833 if (Registration is null || string.IsNullOrEmpty(Registration.Owner))
834 continue;
835
836 ReadoutRule Rule = await Database.FindFirstDeleteRest<ReadoutRule>(new FilterAnd(
837 new FilterFieldEqualTo("JID", JID),
838 new FilterFieldEqualTo("NodeID", Node.NodeId),
839 new FilterFieldEqualTo("SourceID", Node.SourceId),
840 new FilterFieldEqualTo("Partition", Node.Partition)));
841
842 if (Rule is null)
843 {
844 Rule = new ReadoutRule()
845 {
846 JID = JID,
847 NodeID = Node.NodeId,
848 SourceID = Node.SourceId,
849 Partition = Node.Partition
850 };
851
852 await Database.Insert(Rule);
853 }
854
855 bool? Result = null;
856
857 if (!(Rule.Rules is null))
858 {
859 foreach (Rule Rule2 in Rule.Rules)
860 {
861 Result = Rule2.Evaluate(Context);
862 if (Result.HasValue)
863 break;
864 }
865 }
866
867 if (Result.HasValue)
868 {
869 if (Result.Value)
870 {
871 NodesAllowed ??= new List<ThingReference>();
872 NodesAllowed.Add(Node);
873 }
874
875 continue;
876 }
877
878 StringBuilder Xml = new StringBuilder();
879
880 Xml.Append("<canRead xmlns='");
881 Xml.Append(NamespaceProvisioningOwner(Registration.OwnerVersion));
882 Xml.Append("' jid='");
883 Xml.Append(XML.Encode(JID));
884 Xml.Append("' remoteJid='");
885 Xml.Append(XML.Encode(RemoteJID));
886 Xml.Append("' key='");
887 Xml.Append(XML.Encode(Rule.ObjectId.ToString()));
888
889 if (FieldTypes == FieldType.All)
890 Xml.Append("' all='true");
891 else
892 {
893 if ((FieldTypes & FieldType.Historical) != 0)
894 Xml.Append("' h='true");
895
896 if ((FieldTypes & FieldType.Momentary) != 0)
897 Xml.Append("' m='true");
898
899 if ((FieldTypes & FieldType.Peak) != 0)
900 Xml.Append("' p='true");
901
902 if ((FieldTypes & FieldType.Status) != 0)
903 Xml.Append("' s='true");
904
905 if ((FieldTypes & FieldType.Computed) != 0)
906 Xml.Append("' c='true");
907
908 if ((FieldTypes & FieldType.Identity) != 0)
909 Xml.Append("' i='true");
910 }
911
912 this.AppendTokens(Xml, "st", ServiceTokens);
913 this.AppendTokens(Xml, "dt", DeviceTokens);
914 this.AppendTokens(Xml, "ut", UserTokens);
915
916 Xml.Append("'>");
917
918 if (!Node.IsEmpty)
919 this.AppendNode(Xml, Node);
920
921 if (!(Context.Names is null))
922 {
923 foreach (string FieldName in Context.Names.Keys)
924 {
925 Xml.Append("<f n='");
926 Xml.Append(XML.Encode(FieldName));
927 Xml.Append("'/>");
928 }
929 }
930
931 Xml.Append("</canRead>");
932
933 await this.Server.SendMessage(string.Empty, string.Empty, e.To, new XmppAddress(Registration.Owner), string.Empty, Xml.ToString());
934 }
935
936 await this.SendCanReadResponse(Jid, e, !(NodesAllowed is null), Context.Types, NodesAllowed, Context?.Names?.Keys, QueryVersion);
937 }
938 else
939 await this.SendCanReadResponse(Jid, e, false, 0, null, null, QueryVersion);
940 }
941
942 private void AppendTokens(StringBuilder Xml, string Attribute, string[] Tokens)
943 {
944 if (!(Tokens is null) && Tokens.Length > 0)
945 {
946 bool First = true;
947
948 Xml.Append("' ");
949 Xml.Append(Attribute);
950 Xml.Append("='");
951
952 foreach (string Token in Tokens)
953 {
954 if (First)
955 First = false;
956 else
957 Xml.Append(' ');
958
959 Xml.Append(XML.Encode(Token));
960 }
961 }
962 }
963
964 private Task SendCanReadResponse(CaseInsensitiveString Jid, IqEventArgs e, bool Allowed, FieldType FieldTypes,
965 List<ThingReference> Nodes, IEnumerable<string> Fields, NamespaceSet Version)
966 {
967 StringBuilder Xml = new StringBuilder();
968
969 Xml.Append("<canReadResponse xmlns='");
970 Xml.Append(NamespaceProvisioningOwner(Version));
971 Xml.Append("' result='");
972 Xml.Append(CommonTypes.Encode(Allowed));
973 Xml.Append("' jid='");
974 Xml.Append(XML.Encode(Jid));
975
976 if (Allowed)
977 {
978 if ((FieldTypes & FieldType.All) == FieldType.All)
979 Xml.Append("' all='true");
980 else
981 {
982 if (FieldTypes.HasFlag(FieldType.Momentary))
983 Xml.Append("' m='true");
984
985 if (FieldTypes.HasFlag(FieldType.Peak))
986 Xml.Append("' p='true");
987
988 if (FieldTypes.HasFlag(FieldType.Status))
989 Xml.Append("' s='true");
990
991 if (FieldTypes.HasFlag(FieldType.Computed))
992 Xml.Append("' c='true");
993
994 if (FieldTypes.HasFlag(FieldType.Identity))
995 Xml.Append("' i='true");
996
997 if (FieldTypes.HasFlag(FieldType.Historical))
998 Xml.Append("' h='true");
999 }
1000
1001 if (Nodes is null && Fields is null)
1002 Xml.Append("'/>");
1003 else
1004 {
1005 Xml.Append("'>");
1006
1007 if (!(Nodes is null) && (Nodes.Count != 1 || !Nodes[0].IsEmpty))
1008 {
1009 foreach (ThingReference Node in Nodes)
1010 this.AppendNode(Xml, Node);
1011 }
1012
1013 if (!(Fields is null))
1014 {
1015 foreach (string FieldName in Fields)
1016 {
1017 Xml.Append("<f n='");
1018 Xml.Append(XML.Encode(FieldName));
1019 Xml.Append("'/>");
1020 }
1021 }
1022
1023 Xml.Append("</canReadResponse>");
1024 }
1025 }
1026 else
1027 Xml.Append("'/>");
1028
1029 return e.IqResult(Xml.ToString(), e.To);
1030 }
1031
1032 private void AppendNode(StringBuilder Xml, ThingReference Node)
1033 {
1034 Xml.Append("<nd id='");
1035 Xml.Append(XML.Encode(Node.NodeId));
1036
1037 if (!string.IsNullOrEmpty(Node.SourceId))
1038 {
1039 Xml.Append("' src='");
1040 Xml.Append(XML.Encode(Node.SourceId));
1041 }
1042
1043 if (!string.IsNullOrEmpty(Node.Partition))
1044 {
1045 Xml.Append("' pt='");
1046 Xml.Append(XML.Encode(Node.Partition));
1047 }
1048
1049 Xml.Append("'/>");
1050 }
1051
1052 private static readonly char[] space = new char[] { ' ' };
1053
1060 public bool TryGetCertificate(string Token, out X509Certificate2 Certificate)
1061 {
1062 lock (this.certificates)
1063 {
1064 if (this.certificates.TryGetValue(Token, out KeyValuePair<byte[], X509Certificate2> Rec))
1065 {
1066 Certificate = Rec.Value;
1067 return true;
1068 }
1069 else
1070 {
1071 Certificate = null;
1072 return false;
1073 }
1074 }
1075 }
1076
1077 private async Task CanControlHandler(object Sender, IqEventArgs e)
1078 {
1079 List<ThingReference> Nodes = null;
1080 List<string> ParameterNames = null;
1081 XmlElement E = e.Query;
1082 string ServiceToken = string.Empty;
1083 string DeviceToken = string.Empty;
1084 string UserToken = string.Empty;
1086 NamespaceSet QueryVersion = XmppServerModule.GetVersion(e.Query.NamespaceURI);
1087
1088 foreach (XmlAttribute Attr in E.Attributes)
1089 {
1090 switch (Attr.Name)
1091 {
1092 case "st":
1093 ServiceToken = Attr.Value;
1094 break;
1095
1096 case "dt":
1097 DeviceToken = Attr.Value;
1098 break;
1099
1100 case "ut":
1101 UserToken = Attr.Value;
1102 break;
1103
1104 case "jid":
1105 Jid = Attr.Value;
1106 break;
1107 }
1108 }
1109
1110 foreach (XmlNode N in E.ChildNodes)
1111 {
1112 switch (N.LocalName)
1113 {
1114 case "nd":
1115 Nodes ??= new List<ThingReference>();
1116 E = (XmlElement)N;
1117 Nodes.Add(this.ParseNodeInfo(E));
1118 break;
1119
1120 case "p":
1121 ParameterNames ??= new List<string>();
1122 ParameterNames.Add(XML.Attribute((XmlElement)N, "n"));
1123 break;
1124 }
1125 }
1126
1127 string[] ServiceTokens = ServiceToken.Split(space, StringSplitOptions.RemoveEmptyEntries);
1128 string[] DeviceTokens = DeviceToken.Split(space, StringSplitOptions.RemoveEmptyEntries);
1129 string[] UserTokens = UserToken.Split(space, StringSplitOptions.RemoveEmptyEntries);
1130 Dictionary<string, bool> TokensToChallenge = null;
1131 int NrTokensToChallenge;
1132
1133 if (ServiceTokens.Length == 0 && DeviceTokens.Length == 0 && UserTokens.Length == 0)
1134 NrTokensToChallenge = 0;
1135 else
1136 {
1137 TokensToChallenge = new Dictionary<string, bool>();
1138
1139 this.AddValidTokens(TokensToChallenge, ServiceTokens, e.From.Address);
1140 this.AddValidTokens(TokensToChallenge, DeviceTokens, e.From.Address);
1141 this.AddValidTokens(TokensToChallenge, UserTokens, e.From.Address);
1142
1143 NrTokensToChallenge = TokensToChallenge.Count;
1144 if (NrTokensToChallenge > 0)
1145 {
1146 TokenChallengesRecord Rec = new TokenChallengesRecord()
1147 {
1148 TokensToChallenge = TokensToChallenge,
1149 Nodes = Nodes,
1150 Fields = ParameterNames,
1151 Jid = Jid,
1152 ServiceTokens = ServiceTokens,
1153 DeviceTokens = DeviceTokens,
1154 UserTokens = UserTokens,
1155 e = e,
1156 NrTokensToChallenge = NrTokensToChallenge,
1157 Allowed = true,
1158 Read = false,
1159 QueryVersion = QueryVersion
1160 };
1161
1162 string[] Tokens = new string[TokensToChallenge.Count];
1163 TokensToChallenge.Keys.CopyTo(Tokens, 0);
1164
1165 foreach (string Token in Tokens)
1166 {
1167 KeyValuePair<byte[], X509Certificate2> Certificate;
1168
1169 lock (this.certificates)
1170 {
1171 Certificate = this.certificates[Token];
1172 }
1173
1174 byte[] Bin = XmppServer.GetRandomNumbers(64);
1175 string Response = System.Convert.ToBase64String(Bin);
1176 Bin = ((RSACryptoServiceProvider)Certificate.Value.PublicKey.Key).Encrypt(Bin, true);
1177 string Challenge = System.Convert.ToBase64String(Bin);
1178
1179 TokenChallengeRecord Rec2 = new TokenChallengeRecord()
1180 {
1181 Challenges = Rec,
1182 Token = Token,
1183 Response = Response
1184 };
1185
1186 await this.Server.SendIqRequest("get", e.To, e.From, string.Empty,
1187 "<tokenChallenge xmlns='" + NamespaceProvisioningToken(QueryVersion) + "' token='" +
1188 XML.Encode(Token) + "'>" + Challenge + "</tokenChallenge>", false, this.TokenChallengeResponse, Rec2);
1189 }
1190
1191 return;
1192 }
1193 }
1194
1195 await this.TestAndSendCanControlResponse(Jid, e, true, Nodes, ParameterNames, ServiceTokens, DeviceTokens, UserTokens, QueryVersion);
1196 }
1197
1198 public static string GetDomain(CaseInsensitiveString Jid)
1199 {
1200 CaseInsensitiveString Domain;
1201 int i = Jid.IndexOf('@');
1202 if (i < 0)
1203 Domain = Jid;
1204 else
1205 Domain = Jid.Substring(i + 1);
1206
1207 i = Domain.IndexOf('/');
1208 if (i > 0)
1209 Domain = Domain.Substring(0, i);
1210
1211 return Domain;
1212 }
1213
1214 private async Task TestAndSendCanControlResponse(CaseInsensitiveString Jid, IqEventArgs e, bool Allowed, List<ThingReference> Nodes,
1215 List<string> ParameterNames, string[] ServiceTokens, string[] DeviceTokens, string[] UserTokens, NamespaceSet QueryVersion)
1216 {
1217 if (Allowed)
1218 {
1220 CaseInsensitiveString RemoteJID = Jid;
1221
1222 List<ThingReference> NodesAllowed = null;
1223 CaseInsensitiveString RemoteDomain = GetDomain(RemoteJID);
1224
1225 if (Nodes is null || Nodes.Count == 0)
1226 Nodes = new List<ThingReference>() { ThingReference.Empty };
1227
1228 Context Context = new Context()
1229 {
1230 RemoteJid = RemoteJID,
1231 RemoteDomain = RemoteDomain,
1232 ServiceTokens = ServiceTokens,
1233 DeviceTokens = DeviceTokens,
1234 UserTokens = UserTokens
1235 };
1236
1237 if (!(ParameterNames is null))
1238 {
1239 Context.Names = new Dictionary<string, bool>();
1240
1241 foreach (string ParameterName in ParameterNames)
1242 Context.Names[ParameterName] = true;
1243 }
1244
1245 foreach (ThingReference Node in Nodes)
1246 {
1247 Registration Registration = await this.GetRegistration(JID, Node.NodeId, Node.SourceId, Node.Partition, QueryVersion, null);
1248 if (Registration is null || string.IsNullOrEmpty(Registration.Owner))
1249 continue;
1250
1251 ControlRule Rule = await Database.FindFirstDeleteRest<ControlRule>(new FilterAnd(
1252 new FilterFieldEqualTo("JID", JID),
1253 new FilterFieldEqualTo("NodeID", Node.NodeId),
1254 new FilterFieldEqualTo("SourceID", Node.SourceId),
1255 new FilterFieldEqualTo("Partition", Node.Partition)));
1256
1257 if (Rule is null)
1258 {
1259 Rule = new ControlRule()
1260 {
1261 JID = JID,
1262 NodeID = Node.NodeId,
1263 SourceID = Node.SourceId,
1264 Partition = Node.Partition
1265 };
1266
1267 await Database.Insert(Rule);
1268 }
1269
1270 bool? Result = null;
1271
1272 if (!(Rule.Rules is null))
1273 {
1274 foreach (Rule Rule2 in Rule.Rules)
1275 {
1276 Result = Rule2.Evaluate(Context);
1277 if (Result.HasValue)
1278 break;
1279 }
1280 }
1281
1282 if (Result.HasValue)
1283 {
1284 if (Result.Value)
1285 {
1286 NodesAllowed ??= new List<ThingReference>();
1287 NodesAllowed.Add(Node);
1288 }
1289
1290 continue;
1291 }
1292
1293 StringBuilder Xml = new StringBuilder();
1294
1295 Xml.Append("<canControl xmlns='");
1296 Xml.Append(NamespaceProvisioningOwner(QueryVersion));
1297 Xml.Append("' jid='");
1298 Xml.Append(XML.Encode(JID));
1299 Xml.Append("' remoteJid='");
1300 Xml.Append(XML.Encode(RemoteJID));
1301 Xml.Append("' key='");
1302 Xml.Append(XML.Encode(Rule.ObjectId.ToString()));
1303
1304 this.AppendTokens(Xml, "st", ServiceTokens);
1305 this.AppendTokens(Xml, "dt", DeviceTokens);
1306 this.AppendTokens(Xml, "ut", UserTokens);
1307
1308 Xml.Append("'>");
1309
1310 if (!Node.IsEmpty)
1311 this.AppendNode(Xml, Node);
1312
1313 if (!(Context.Names is null))
1314 {
1315 foreach (string FieldName in Context.Names.Keys)
1316 {
1317 Xml.Append("<p n='");
1318 Xml.Append(XML.Encode(FieldName));
1319 Xml.Append("'/>");
1320 }
1321 }
1322
1323 Xml.Append("</canControl>");
1324
1325 await this.Server.SendMessage(string.Empty, string.Empty, e.To, new XmppAddress(Registration.Owner), string.Empty, Xml.ToString());
1326 }
1327
1328 await this.SendCanControlResponse(Jid, e, !(NodesAllowed is null), NodesAllowed, Context?.Names?.Keys);
1329 }
1330 else
1331 await this.SendCanControlResponse(Jid, e, false, null, null);
1332 }
1333
1334 private Task SendCanControlResponse(CaseInsensitiveString Jid, IqEventArgs e, bool Allowed, List<ThingReference> Nodes,
1335 IEnumerable<string> ParameterNames)
1336 {
1337 StringBuilder Xml = new StringBuilder();
1338
1339 Xml.Append("<canControlResponse xmlns='");
1340 Xml.Append(e.Query.NamespaceURI);
1341 Xml.Append("' result='");
1342 Xml.Append(CommonTypes.Encode(Allowed));
1343 Xml.Append("' jid='");
1344 Xml.Append(XML.Encode(Jid));
1345
1346 if (Allowed)
1347 {
1348 if (Nodes is null && ParameterNames is null)
1349 Xml.Append("'/>");
1350 else
1351 {
1352 Xml.Append("'>");
1353
1354 if (!(Nodes is null))
1355 {
1356 foreach (ThingReference Node in Nodes)
1357 this.AppendNode(Xml, Node);
1358 }
1359
1360 if (!(ParameterNames is null))
1361 {
1362 foreach (string FieldName in ParameterNames)
1363 {
1364 Xml.Append("<p n='");
1365 Xml.Append(XML.Encode(FieldName));
1366 Xml.Append("'/>");
1367 }
1368 }
1369
1370 Xml.Append("</canControlResponse>");
1371 }
1372 }
1373 else
1374 Xml.Append("'/>");
1375
1376 return e.IqResult(Xml.ToString(), e.To);
1377 }
1378
1379 #endregion
1380
1381 #region Provisioning, Owner interface
1382
1383 private async Task IsFriendRuleHandler(object Sender, IqEventArgs e)
1384 {
1385 CaseInsensitiveString OwnerJid = e.From.BareJid;
1386 CaseInsensitiveString JID = XML.Attribute(e.Query, "jid");
1387 CaseInsensitiveString RemoteJID = XML.Attribute(e.Query, "remoteJid");
1388 string Key = XML.Attribute(e.Query, "key");
1389 bool AreFrieds = XML.Attribute(e.Query, "result", false);
1390 RuleRange Range = XML.Attribute(e.Query, "range", RuleRange.Caller);
1391 NamespaceSet QueryVersion = XmppServerModule.GetVersion(e.Query.NamespaceURI);
1393
1394 if (!Guid.TryParse(Key, out Guid ObjectId))
1395 {
1396 await e.IqErrorBadRequest(e.To, "Invalid key.", "en");
1397 return;
1398 }
1399
1400 Rule = await Database.TryLoadObject<FriendshipRule>(ObjectId);
1401 if (Rule is null)
1402 {
1403 await e.IqErrorItemNotFound(e.To, "Key not found.", "en");
1404 return;
1405 }
1406
1407 if (Rule.JID != JID || Rule.RemoteJID != RemoteJID)
1408 {
1409 await e.IqErrorBadRequest(e.To, "Parameters do not match.", "en");
1410 return;
1411 }
1412
1413 Registration Registration = await this.GetRegistration(JID, string.Empty, string.Empty, string.Empty, null, QueryVersion);
1414
1415 if (Registration is null || Registration.Owner != OwnerJid)
1416 {
1417 await e.IqErrorForbidden(e.To, "Access denied. Owner mismatch.", "en");
1418 return;
1419 }
1420
1421 Log.Informational("Presence subscription rule changed.", JID, OwnerJid, "SubscriptionRule",
1422 new KeyValuePair<string, object>("RemoteJID", RemoteJID),
1423 new KeyValuePair<string, object>("Allowed", AreFrieds));
1424
1425 switch (Range)
1426 {
1427 case RuleRange.Caller:
1428 if (Rule.CanSubscribeToPresence != AreFrieds)
1429 {
1430 Rule.CanSubscribeToPresence = AreFrieds;
1431 await Database.Update(Rule);
1432 }
1433 break;
1434
1435 case RuleRange.Domain:
1436 await Database.Delete(Rule);
1437
1438 string RemoteDomain = new XmppAddress(RemoteJID).Domain;
1439
1440 Rule = await Database.FindFirstDeleteRest<FriendshipRule>(new FilterAnd(
1441 new FilterFieldEqualTo("JID", JID), new FilterFieldEqualTo("RemoteJID", RemoteDomain)));
1442
1443 if (!(Rule is null))
1444 {
1445 if (Rule.CanSubscribeToPresence != AreFrieds)
1446 {
1447 Rule.CanSubscribeToPresence = AreFrieds;
1448 await Database.Update(Rule);
1449 }
1450 }
1451 else
1452 {
1453 Rule = new FriendshipRule()
1454 {
1455 JID = JID,
1456 RemoteJID = RemoteDomain,
1457 CanSubscribeToPresence = AreFrieds
1458 };
1459
1460 await Database.Insert(Rule);
1461 }
1462 break;
1463
1464 case RuleRange.All:
1465 await Database.Delete(Rule);
1466
1467 Rule = await Database.FindFirstDeleteRest<FriendshipRule>(new FilterAnd(
1468 new FilterFieldEqualTo("JID", JID), new FilterFieldEqualTo("RemoteJID", string.Empty)));
1469
1470 if (!(Rule is null))
1471 {
1472 if (Rule.CanSubscribeToPresence != AreFrieds)
1473 {
1474 Rule.CanSubscribeToPresence = AreFrieds;
1475 await Database.Update(Rule);
1476 }
1477 }
1478 else
1479 {
1480 Rule = new FriendshipRule()
1481 {
1482 JID = JID,
1483 RemoteJID = string.Empty,
1484 CanSubscribeToPresence = AreFrieds
1485 };
1486
1487 await Database.Insert(Rule);
1488 }
1489 break;
1490
1491 default:
1492 await e.IqErrorBadRequest(e.To, "Invalid range.", "en");
1493 return;
1494 }
1495
1496 await e.IqResult(string.Empty, e.To);
1497
1498 await this.ClearCache(JID, string.Empty, string.Empty, string.Empty, Registration.Version);
1499
1500 if (AreFrieds)
1501 {
1502 Gateway.ScheduleEvent(async (P) =>
1503 {
1504 try
1505 {
1506 await this.RecommendBefriend(RemoteJID, JID, QueryVersion);
1507 }
1508 catch (Exception ex)
1509 {
1510 Log.Exception(ex);
1511 }
1512 }, DateTime.Now.AddSeconds(5), null); // Allows the client some time to prepare for the presence subscription request.
1513 }
1514 }
1515
1524 NamespaceSet Version)
1525 {
1526 await this.Server.GetLastPresence(BareJid1, async (Sender, e) =>
1527 {
1528 try
1529 {
1530 if (e.Ok)
1531 {
1532 await this.Server.SendMessage(string.Empty, string.Empty, this.GetComponentAddress(e.To), e.From, string.Empty,
1533 "<unfriend xmlns='" + NamespaceProvisioningDevice(Version) + "' jid='" + BareJid2 + "'/>");
1534 }
1535 }
1536 catch (Exception ex)
1537 {
1538 Log.Exception(ex);
1539 }
1540 }, null);
1541 }
1542
1543 private XmppAddress GetComponentAddress(XmppAddress ServerAddress)
1544 {
1545 if (ServerAddress.IsEmpty)
1546 return this.MainDomain;
1547 else
1548 return new XmppAddress(this.SubdomainSuffixed + ServerAddress.Address);
1549 }
1550
1557 public async Task ClearCache(CaseInsensitiveString BareJid,
1558 IThingReference[] ThingReferences, NamespaceSet Version)
1559 {
1560 if (ThingReferences is null)
1561 await this.ClearCache(BareJid, string.Empty, string.Empty, string.Empty, Version);
1562 else
1563 {
1564 foreach (IThingReference Reference in ThingReferences)
1565 {
1566 await this.ClearCache(BareJid, Reference.NodeId, Reference.SourceId,
1567 Reference.Partition, Version);
1568 }
1569 }
1570 }
1571
1580 public async Task ClearCache(CaseInsensitiveString BareJid, string NodeId,
1581 string SourceId, string Partition, NamespaceSet Version)
1582 {
1583 await this.Server.GetLastPresence(BareJid, async (Sender, e) =>
1584 {
1585 try
1586 {
1587 StringBuilder Xml = new StringBuilder();
1588
1589 Xml.Append("<clearCache xmlns='");
1590 Xml.Append(NamespaceProvisioningDevice(Version));
1591
1592 if (!string.IsNullOrEmpty(NodeId))
1593 {
1594 Xml.Append("' id='");
1595 Xml.Append(XML.Encode(NodeId));
1596 }
1597
1598 if (!string.IsNullOrEmpty(SourceId))
1599 {
1600 Xml.Append("' src='");
1601 Xml.Append(XML.Encode(SourceId));
1602 }
1603
1604 if (!string.IsNullOrEmpty(Partition))
1605 {
1606 Xml.Append("' pt='");
1607 Xml.Append(XML.Encode(Partition));
1608 }
1609
1610 Xml.Append("'/>");
1611
1612 XmppAddress From = this.GetComponentAddress(e.To);
1613 string XmlString = Xml.ToString();
1614
1615 if (e.Ok)
1616 {
1617 await this.Server.SendIqRequest("set", From, e.From, string.Empty, XmlString, false, (sender2, e2) =>
1618 {
1619 if (!e.Ok)
1620 return this.Server.SendMessage(string.Empty, string.Empty, From, new XmppAddress(BareJid), string.Empty, XmlString);
1621
1622 return Task.CompletedTask;
1623 }, null);
1624 }
1625 else
1626 await this.Server.SendMessage(string.Empty, string.Empty, From, new XmppAddress(BareJid), string.Empty, XmlString);
1627 }
1628 catch (Exception ex)
1629 {
1630 Log.Exception(ex);
1631 }
1632 }, null);
1633 }
1634
1643 NamespaceSet Version)
1644 {
1645 await this.Server.GetLastPresence(BareJid1, async (Sender, e) =>
1646 {
1647 try
1648 {
1649 if (e.Ok)
1650 {
1651 await this.Server.SendMessage(string.Empty, string.Empty, this.GetComponentAddress(e.To), e.From, string.Empty,
1652 "<friend xmlns='" + NamespaceProvisioningDevice(Version) + "' jid='" + BareJid2 + "'/>");
1653 }
1654 }
1655 catch (Exception ex)
1656 {
1657 Log.Exception(ex);
1658 }
1659 }, null);
1660 }
1661
1662 private async Task CanReadRuleHandler(object Sender, IqEventArgs e)
1663 {
1664 CaseInsensitiveString OwnerJid = e.From.BareJid;
1665 CaseInsensitiveString JID = XML.Attribute(e.Query, "jid");
1666 CaseInsensitiveString RemoteJID = XML.Attribute(e.Query, "remoteJid");
1667 ChunkedList<IThingReference> ThingReferences = null;
1668 string Key = XML.Attribute(e.Query, "key");
1669 bool CanRead = XML.Attribute(e.Query, "result", false);
1671
1672 if (!Guid.TryParse(Key, out Guid ObjectId))
1673 {
1674 await e.IqErrorBadRequest(e.To, "Invalid key.", "en");
1675 return;
1676 }
1677
1678 Rule = await Database.TryLoadObject<ReadoutRule>(ObjectId);
1679 if (Rule is null)
1680 {
1681 await e.IqErrorItemNotFound(e.To, "Key not found.", "en");
1682 return;
1683 }
1684
1685 Rule Condition = null;
1687 Partial Partial = null;
1688
1689 foreach (XmlNode N in e.Query.ChildNodes)
1690 {
1691 if (N is XmlElement E)
1692 {
1693 switch (E.LocalName)
1694 {
1695 case "nd":
1696 Node = this.ParseNodeInfo(E);
1697
1698 ThingReferences ??= new ChunkedList<IThingReference>();
1699 ThingReferences.Add(Node);
1700 break;
1701
1702 case "partial":
1703 List<string> Fields = null;
1704 FieldType FieldTypes = (FieldType)0;
1705
1706 foreach (XmlAttribute Attr in E.Attributes)
1707 {
1708 switch (Attr.Name)
1709 {
1710 case "all":
1711 if (CommonTypes.TryParse(Attr.Value, out bool b) && b)
1712 FieldTypes |= FieldType.All;
1713 break;
1714
1715 case "h":
1716 if (CommonTypes.TryParse(Attr.Value, out b) && b)
1717 FieldTypes |= FieldType.Historical;
1718 break;
1719
1720 case "m":
1721 if (CommonTypes.TryParse(Attr.Value, out b) && b)
1722 FieldTypes |= FieldType.Momentary;
1723 break;
1724
1725 case "p":
1726 if (CommonTypes.TryParse(Attr.Value, out b) && b)
1727 FieldTypes |= FieldType.Peak;
1728 break;
1729
1730 case "s":
1731 if (CommonTypes.TryParse(Attr.Value, out b) && b)
1732 FieldTypes |= FieldType.Status;
1733 break;
1734
1735 case "c":
1736 if (CommonTypes.TryParse(Attr.Value, out b) && b)
1737 FieldTypes |= FieldType.Computed;
1738 break;
1739
1740 case "i":
1741 if (CommonTypes.TryParse(Attr.Value, out b) && b)
1742 FieldTypes |= FieldType.Identity;
1743 break;
1744 }
1745 }
1746
1747 foreach (XmlNode N2 in E.ChildNodes)
1748 {
1749 if (N2 is XmlElement E2)
1750 {
1751 switch (E2.LocalName)
1752 {
1753 case "f":
1754 Fields ??= new List<string>();
1755 Fields.Add(XML.Attribute(E2, "n"));
1756 break;
1757 }
1758 }
1759 }
1760
1761 Partial = new Partial()
1762 {
1763 Names = Fields?.ToArray(),
1764 Mask = (int)FieldTypes
1765 };
1766 break;
1767
1768 case "fromJid":
1769 Condition = new IfRemoteJid()
1770 {
1771 Value = RemoteJID
1772 };
1773 break;
1774
1775 case "fromDomain":
1776 Condition = new IfRemoteDomain()
1777 {
1778 Value = GetDomain(RemoteJID)
1779 };
1780 break;
1781
1782 case "fromService":
1783 Condition = new IfServiceToken()
1784 {
1785 Value = XML.Attribute(E, "token")
1786 };
1787 break;
1788
1789 case "fromDevice":
1790 Condition = new IfDeviceToken()
1791 {
1792 Value = XML.Attribute(E, "token")
1793 };
1794 break;
1795
1796 case "fromUser":
1797 Condition = new IfUserToken()
1798 {
1799 Value = XML.Attribute(E, "token")
1800 };
1801 break;
1802
1803 case "all":
1804 Condition = new All();
1805 break;
1806 }
1807 }
1808 }
1809
1810 if (Condition is null)
1811 {
1812 await e.IqErrorBadRequest(e.To, "Rule not specified.", "en");
1813 return;
1814 }
1815
1816 if (Rule.JID != JID || Rule.NodeID != Node.NodeId || Rule.SourceID != Node.SourceId || Rule.Partition != Node.Partition)
1817 {
1818 await e.IqErrorBadRequest(e.To, "Parameters do not match.", "en");
1819 return;
1820 }
1821
1822 NamespaceSet QueryVersion = XmppServerModule.GetVersion(e.Query.NamespaceURI);
1823 Registration Registration = await this.GetRegistration(JID, Rule.NodeID, Rule.SourceID, Rule.Partition, null, QueryVersion);
1824
1825 if (Registration is null || Registration.Owner != OwnerJid)
1826 {
1827 await e.IqErrorForbidden(e.To, "Access denied. Owner mismatch.", "en");
1828 return;
1829 }
1830
1831 if (CanRead)
1832 {
1833 if (!(Partial is null))
1834 Condition.AddChildRule(Partial);
1835 else
1836 Condition.AddChildRule(new Yes());
1837 }
1838 else
1839 Condition.AddChildRule(new No());
1840
1841 Rule.AddChildRule(Condition);
1842
1843 Log.Informational("Readot rule changed.", JID, OwnerJid, "ReadoutRule",
1844 new KeyValuePair<string, object>("RemoteJID", RemoteJID),
1845 new KeyValuePair<string, object>("Allowed", CanRead));
1846
1847 await Database.Update(Rule);
1848
1849 await e.IqResult(string.Empty, e.To);
1850
1851 await this.ClearCache(JID, ThingReferences?.ToArray(), Registration.Version);
1852 }
1853
1854 private async Task CanControlRuleHandler(object Sender, IqEventArgs e)
1855 {
1856 CaseInsensitiveString OwnerJid = e.From.BareJid;
1857 CaseInsensitiveString JID = XML.Attribute(e.Query, "jid");
1858 CaseInsensitiveString RemoteJID = XML.Attribute(e.Query, "remoteJid");
1859 ChunkedList<IThingReference> ThingReferences = null;
1860 string Key = XML.Attribute(e.Query, "key");
1861 bool CanControl = XML.Attribute(e.Query, "result", false);
1863
1864 if (!Guid.TryParse(Key, out Guid ObjectId))
1865 {
1866 await e.IqErrorBadRequest(e.To, "Invalid key.", "en");
1867 return;
1868 }
1869
1870 Rule = await Database.TryLoadObject<ControlRule>(ObjectId);
1871 if (Rule is null)
1872 {
1873 await e.IqErrorItemNotFound(e.To, "Key not found.", "en");
1874 return;
1875 }
1876
1877 Rule Condition = null;
1879 Partial Partial = null;
1880
1881 foreach (XmlNode N in e.Query.ChildNodes)
1882 {
1883 if (N is XmlElement E)
1884 {
1885 switch (E.LocalName)
1886 {
1887 case "nd":
1888 Node = this.ParseNodeInfo(E);
1889
1890 ThingReferences ??= new ChunkedList<IThingReference>();
1891 ThingReferences.Add(Node);
1892 break;
1893
1894 case "partial":
1895 List<string> Parameters = null;
1896
1897 foreach (XmlNode N2 in E.ChildNodes)
1898 {
1899 if (N2 is XmlElement E2)
1900 {
1901 switch (E2.LocalName)
1902 {
1903 case "p":
1904 Parameters ??= new List<string>();
1905 Parameters.Add(XML.Attribute(E2, "n"));
1906 break;
1907 }
1908 }
1909 }
1910
1911 Partial = new Partial()
1912 {
1913 Names = Parameters?.ToArray()
1914 };
1915 break;
1916
1917 case "fromJid":
1918 Condition = new IfRemoteJid()
1919 {
1920 Value = RemoteJID
1921 };
1922 break;
1923
1924 case "fromDomain":
1925 Condition = new IfRemoteDomain()
1926 {
1927 Value = GetDomain(RemoteJID)
1928 };
1929 break;
1930
1931 case "fromService":
1932 Condition = new IfServiceToken()
1933 {
1934 Value = XML.Attribute(E, "token")
1935 };
1936 break;
1937
1938 case "fromDevice":
1939 Condition = new IfDeviceToken()
1940 {
1941 Value = XML.Attribute(E, "token")
1942 };
1943 break;
1944
1945 case "fromUser":
1946 Condition = new IfUserToken()
1947 {
1948 Value = XML.Attribute(E, "token")
1949 };
1950 break;
1951
1952 case "all":
1953 Condition = new All();
1954 break;
1955 }
1956 }
1957 }
1958
1959 if (Condition is null)
1960 {
1961 await e.IqErrorBadRequest(e.To, "Rule not specified.", "en");
1962 return;
1963 }
1964
1965 if (Rule.JID != JID || Rule.NodeID != Node.NodeId || Rule.SourceID != Node.SourceId || Rule.Partition != Node.Partition)
1966 {
1967 await e.IqErrorBadRequest(e.To, "Parameters do not match.", "en");
1968 return;
1969 }
1970
1971 NamespaceSet QueryVersion = XmppServerModule.GetVersion(e.Query.NamespaceURI);
1972 Registration Registration = await this.GetRegistration(JID, Rule.NodeID, Rule.SourceID, Rule.Partition, null, QueryVersion);
1973
1974 if (Registration is null || Registration.Owner != OwnerJid)
1975 {
1976 await e.IqErrorForbidden(e.To, "Access denied. Owner mismatch.", "en");
1977 return;
1978 }
1979
1980 if (CanControl)
1981 {
1982 if (!(Partial is null))
1983 {
1984 Partial.AddChildRule(new Yes());
1985 Condition.AddChildRule(Partial);
1986 }
1987 else
1988 Condition.AddChildRule(new Yes());
1989 }
1990 else
1991 Condition.AddChildRule(new No());
1992
1993 Rule.AddChildRule(Condition);
1994
1995 Log.Informational("Control rule changed.", JID, OwnerJid, "ControlRule",
1996 new KeyValuePair<string, object>("RemoteJID", RemoteJID),
1997 new KeyValuePair<string, object>("Allowed", CanControl));
1998
1999 await Database.Update(Rule);
2000
2001 await e.IqResult(string.Empty, e.To);
2002
2003 await this.ClearCache(JID, ThingReferences?.ToArray(), Registration.Version);
2004 }
2005
2006 private async Task ClearCacheHandler(object Sender, IqEventArgs e)
2007 {
2008 try
2009 {
2010 CaseInsensitiveString Jid = XML.Attribute(e.Query, "jid");
2011 string NodeId = XML.Attribute(e.Query, "id");
2012 string SourceId = XML.Attribute(e.Query, "src");
2013 string Partition = XML.Attribute(e.Query, "pt");
2014 NamespaceSet QueryVersion = XmppServerModule.GetVersion(e.Query.NamespaceURI);
2015
2017 string.IsNullOrEmpty(NodeId) &&
2018 string.IsNullOrEmpty(SourceId) &&
2019 string.IsNullOrEmpty(Partition))
2020 {
2021 foreach (Registration R in await Database.Find<Registration>(
2022 new FilterFieldEqualTo("Owner", e.From.BareJid)))
2023 {
2024 await this.ClearCache(R.JID, R.NodeId, R.SourceId, R.Partition, R.Version);
2025 }
2026 }
2027 else if (string.IsNullOrEmpty(NodeId) &&
2028 string.IsNullOrEmpty(SourceId) &&
2029 string.IsNullOrEmpty(Partition))
2030 {
2031 foreach (Registration R in await Database.Find<Registration>(new FilterAnd(
2032 new FilterFieldEqualTo("Owner", e.From.BareJid),
2033 new FilterFieldEqualTo("JID", Jid))))
2034 {
2035 await this.ClearCache(R.JID, R.NodeId, R.SourceId, R.Partition, R.Version);
2036 }
2037 }
2038 else
2039 {
2040 Registration R = await Database.FindFirstDeleteRest<Registration>(new FilterAnd(
2041 new FilterFieldEqualTo("Owner", e.From.BareJid),
2042 new FilterFieldEqualTo("JID", Jid),
2043 new FilterFieldEqualTo("NodeId", NodeId),
2044 new FilterFieldEqualTo("SourceId", SourceId),
2045 new FilterFieldEqualTo("Partition", Partition)));
2046
2047 if (!(R is null))
2048 await this.ClearCache(Jid, NodeId, SourceId, Partition, R.Version);
2049 }
2050
2051 await e.IqResult(string.Empty, e.To);
2052 }
2053 catch (Exception ex)
2054 {
2055 await e.IqError(ex, e.To);
2056 }
2057 }
2058
2059 private async Task GetDevicesHandler(object Sender, IqEventArgs e)
2060 {
2061 try
2062 {
2063 XmlElement E = e.Query;
2064 int Offset = XML.Attribute(E, "offset", 0);
2065 if (Offset < 0)
2066 {
2067 await e.IqErrorBadRequest(e.To, "Offset must be non-negative.", "en");
2068 return;
2069 }
2070
2071 int MaxCount = XML.Attribute(E, "maxCount", 20);
2072 if (MaxCount > 100)
2073 MaxCount = 100;
2074 else if (MaxCount <= 0)
2075 {
2076 await e.IqErrorBadRequest(e.To, "Maximum count must be positive.", "en");
2077 return;
2078 }
2079
2080 List<KeyValuePair<Registration, IEnumerable<MetaDataTag>>> Found = new List<KeyValuePair<Registration, IEnumerable<MetaDataTag>>>();
2081 bool More = false;
2082
2083 IEnumerable<Registration> Registrations;
2084
2085 Registrations = await Database.Find<Registration>(Offset, MaxCount + 1, new FilterFieldEqualTo("Owner", e.From.BareJid),
2086 "JID", "NodeId", "SourceId", "Partition");
2087
2088 foreach (Registration R in Registrations)
2089 {
2090 if (Found.Count >= MaxCount)
2091 {
2092 More = true;
2093 break;
2094 }
2095
2096 Found.Add(new KeyValuePair<Registration, IEnumerable<MetaDataTag>>(R, await LoadTags(R.ObjectId)));
2097 }
2098
2099 await e.IqResult(ResultSet(Found, More, e.Query.NamespaceURI), e.To);
2100 }
2101 catch (Exception ex)
2102 {
2103 await e.IqError(ex, e.To);
2104 }
2105 }
2106
2107 #endregion
2108
2109 #region Thing Registry XEP-0347
2110
2111 private Cache<CaseInsensitiveString, RegistryAccount> registryAccounts = CreateRegistryAccountsCache();
2112
2113 private static Cache<CaseInsensitiveString, RegistryAccount> CreateRegistryAccountsCache()
2114 {
2116 new Cache<CaseInsensitiveString, RegistryAccount>(int.MaxValue, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(5));
2117
2118 Result.Removed += UpdateRegistryAccount;
2119
2120 return Result;
2121 }
2122
2123 private static async Task UpdateRegistryAccount(object Sender, CacheItemEventArgs<CaseInsensitiveString, RegistryAccount> e)
2124 {
2125 try
2126 {
2127 await Database.Update(e.Value);
2128 }
2129 catch (Exception ex)
2130 {
2131 Log.Exception(ex);
2132 }
2133 }
2134
2135 internal static async Task<RegistryAccount> GetAccount(CaseInsensitiveString JID)
2136 {
2137 if (XmppServerModule.Provisioning?.registryAccounts?.TryGetValue(JID, out RegistryAccount Account) ?? false)
2138 return Account;
2139
2140 Account = await Database.FindFirstDeleteRest<RegistryAccount>(new FilterFieldEqualTo("JID", JID));
2141
2142 if (Account is null)
2143 {
2144 DateTime Now = DateTime.Now;
2145 Account = new RegistryAccount()
2146 {
2147 JID = JID,
2148 FirstAction = Now,
2149 LastAction = Now
2150 };
2151
2152 await Database.Insert(Account);
2153 }
2154 else
2155 Account.LastAction = DateTime.Now; // Account updated when purged from cache.
2156
2157 if (XmppServerModule.Provisioning?.registryAccounts?.TryGetValue(JID, out RegistryAccount Account2) ?? false)
2158 {
2159 Account2.LastAction = DateTime.Now;
2160 return Account2;
2161 }
2162 else
2163 {
2164 XmppServerModule.Provisioning?.registryAccounts?.Add(JID, Account);
2165 return Account;
2166 }
2167 }
2168
2169 private async Task RegisterHandler(object _, IqEventArgs e)
2170 {
2171 try
2172 {
2173 XmlElement E = e.Query;
2174 ThingReference Node;
2175 bool SelfOwned = XML.Attribute(E, "selfOwned", false);
2176 NamespaceSet QueryVersion = XmppServerModule.GetVersion(e.Query.NamespaceURI);
2177
2178 Node = this.ParseNodeInfo(E);
2179 if (!this.ParseTags(e, E,
2180 out Dictionary<string, MetaDataTag> Tags,
2181 out string Key,
2182 out GeoPosition Location,
2183 out string GeoId))
2184 {
2185 return;
2186 }
2187
2189 Registration Registration = await this.GetRegistration(JID, Node.NodeId, Node.SourceId, Node.Partition, QueryVersion, null);
2190
2191 if (!(Registration is null) && !string.IsNullOrEmpty(Registration.Owner))
2192 {
2193 await e.IqResult("<claimed xmlns='" + e.Query.NamespaceURI + "' jid='" + XML.Encode(Registration.Owner) +
2194 "' public='" + CommonTypes.Encode(Registration.IsPublic) + "'/>", e.To);
2195 }
2196 else
2197 {
2198 if (Registration is null)
2199 {
2201 {
2202 JID = JID,
2203 NodeId = Node.NodeId,
2204 SourceId = Node.SourceId,
2205 Partition = Node.Partition,
2206 Key = Key,
2207 NrRegistrations = 1,
2208 NrUpdates = 0,
2209 FirstRegistration = DateTime.UtcNow,
2210 Version = QueryVersion,
2211 Location = Location,
2212 GeoId = GeoId
2213 };
2214
2215 Registration.LastUpdate = Registration.FirstRegistration;
2216
2217 if (SelfOwned)
2218 {
2219 Registration.IsPublic = true;
2220 Registration.Owner = e.From.BareJid;
2221 }
2222 else
2223 {
2224 Registration.IsPublic = false;
2225 Registration.Owner = string.Empty;
2226 }
2227
2229 await SaveNewRegistrationTags(Registration, this.GetArray(Tags));
2230
2232 await this.geo.Publish(Registration);
2233
2234 Log.Informational("Thing registered.", e.From.BareJid, string.Empty, "Register", GetLogParameters(Node, Tags.Values));
2235 }
2236 else
2237 {
2238 bool PrevGeoLocation = Registration.HasGeoLocation;
2239 string PrevGeoId = Registration.GeoId;
2240
2241 Registration.Key = Key;
2243 Registration.LastUpdate = DateTime.UtcNow;
2244 Registration.Location = Location;
2245 Registration.GeoId = GeoId;
2246
2247 if (SelfOwned)
2248 {
2249 Registration.IsPublic = true;
2250 Registration.Owner = e.From.BareJid;
2251 }
2252 else
2253 {
2254 Registration.IsPublic = false;
2255 Registration.Owner = string.Empty;
2256 }
2257
2259 await DeleteTags(Registration);
2260 await SaveNewRegistrationTags(Registration, this.GetArray(Tags));
2261
2263 {
2264 if (PrevGeoLocation)
2265 {
2266 if (PrevGeoId != GeoId)
2267 await this.geo.Delete(PrevGeoId);
2268
2269 await this.geo.Publish(Registration);
2270 }
2271 else
2272 await this.geo.Publish(Registration);
2273 }
2274 else if (PrevGeoLocation)
2275 await this.geo.Delete(PrevGeoId);
2276
2277 Log.Informational("Thing re-registered.", e.From.BareJid, string.Empty, "Register", GetLogParameters(Node, Tags.Values));
2278 }
2279
2280 await e.IqResult(string.Empty, e.To);
2281 }
2282 }
2283 catch (Exception ex)
2284 {
2285 await e.IqError(ex, e.To);
2286 }
2287 }
2288
2289 private async Task<Registration> GetRegistration(CaseInsensitiveString JID, string NodeId, string SourceId, string Partition,
2290 NamespaceSet? ThingVersion, NamespaceSet? OwnerVersion)
2291 {
2292 Registration Result = await Database.FindFirstDeleteRest<Registration>(new FilterAnd(
2293 new FilterFieldEqualTo("JID", JID),
2294 new FilterFieldEqualTo("NodeId", NodeId),
2295 new FilterFieldEqualTo("SourceId", SourceId),
2296 new FilterFieldEqualTo("Partition", Partition)));
2297
2298 if (Result is null)
2299 return null;
2300
2301 bool Updated = false;
2302
2303 if (ThingVersion.HasValue && Result.Version != ThingVersion.Value)
2304 {
2305 Result.Version = ThingVersion.Value;
2306 Updated = true;
2307 }
2308
2309 if (OwnerVersion.HasValue && Result.OwnerVersion != OwnerVersion.Value)
2310 {
2311 Result.OwnerVersion = OwnerVersion.Value;
2312 Updated = true;
2313 }
2314
2315 if (Updated)
2316 await Database.Update(Result);
2317
2318 return Result;
2319 }
2320
2321 private MetaDataTag[] GetArray(Dictionary<string, MetaDataTag> Tags)
2322 {
2323 MetaDataTag[] Result = new MetaDataTag[Tags.Count];
2324 Tags.Values.CopyTo(Result, 0);
2325 return Result;
2326 }
2327
2328 private static KeyValuePair<string, object>[] GetLogParameters(ThingReference Node, IEnumerable<MetaDataTag> Tags)
2329 {
2330 List<KeyValuePair<string, object>> Result = new List<KeyValuePair<string, object>>();
2331
2332 if (!string.IsNullOrEmpty(Node.NodeId))
2333 Result.Add(new KeyValuePair<string, object>("NodeId", Node.NodeId));
2334
2335 if (!string.IsNullOrEmpty(Node.SourceId))
2336 Result.Add(new KeyValuePair<string, object>("SourceId", Node.SourceId));
2337
2338 if (!string.IsNullOrEmpty(Node.Partition))
2339 Result.Add(new KeyValuePair<string, object>("Partition", Node.Partition));
2340
2341 if (!(Tags is null))
2342 {
2343 foreach (MetaDataTag Tag in Tags)
2344 Result.Add(new KeyValuePair<string, object>(Tag.Name, Tag.Value));
2345 }
2346
2347 return Result.ToArray();
2348 }
2349
2350 private static KeyValuePair<string, object>[] GetLogParameters(Registration Node, IEnumerable<MetaDataTag> Tags)
2351 {
2352 List<KeyValuePair<string, object>> Result = new List<KeyValuePair<string, object>>();
2353
2354 if (!string.IsNullOrEmpty(Node.NodeId))
2355 Result.Add(new KeyValuePair<string, object>("NodeId", Node.NodeId));
2356
2357 if (!string.IsNullOrEmpty(Node.SourceId))
2358 Result.Add(new KeyValuePair<string, object>("SourceId", Node.SourceId));
2359
2360 if (!string.IsNullOrEmpty(Node.Partition))
2361 Result.Add(new KeyValuePair<string, object>("Partition", Node.Partition));
2362
2363 foreach (MetaDataTag Tag in Tags)
2364 Result.Add(new KeyValuePair<string, object>(Tag.Name, Tag.Value));
2365
2366 return Result.ToArray();
2367 }
2368
2369 private async Task UpdateHandler(object _, IqEventArgs e)
2370 {
2371 try
2372 {
2373 XmlElement E = e.Query;
2374 ThingReference Node;
2375 CaseInsensitiveString Jid = XML.Attribute(E, "jid", e.From.BareJid).ToLower();
2376 NamespaceSet QueryVersion = XmppServerModule.GetVersion(e.Query.NamespaceURI);
2377
2378 Node = this.ParseNodeInfo(E);
2379 if (!this.ParseTags(e, E,
2380 out Dictionary<string, MetaDataTag> Tags,
2381 out string Key,
2382 out GeoPosition Location,
2383 out string GeoId))
2384 {
2385 return;
2386 }
2387
2388 Registration Registration = await this.GetRegistration(Jid, Node.NodeId, Node.SourceId, Node.Partition, QueryVersion, null);
2389
2390 if (Registration is null)
2391 {
2392 await e.IqErrorItemNotFound(e.To, "Thing not found.", "en");
2393
2394 if (e.From.BareJid != Jid)
2395 await IncAccountFailedUpdates(e.From.BareJid);
2396 }
2397 else if (string.IsNullOrEmpty(Registration.Owner))
2398 {
2399 await e.IqResult("<disowned xmlns='" + e.Query.NamespaceURI + "'/>", e.To);
2400
2401 if (e.From.BareJid != Jid)
2402 await IncAccountFailedUpdates(e.From.BareJid);
2403 }
2404 else if (e.From.BareJid != Jid && e.From.BareJid != Registration.Owner)
2405 {
2406 await e.IqErrorItemNotFound(e.To, "Thing not found.", "en");
2407
2408 if (e.From.BareJid != Jid)
2409 await IncAccountFailedUpdates(e.From.BareJid);
2410 }
2411 else
2412 {
2413 bool PrevGeoLocation = Registration.HasGeoLocation;
2414 string PrevGeoId = Registration.GeoId;
2415
2417 Registration.LastUpdate = DateTime.UtcNow;
2418 Registration.Location = Location;
2419 Registration.GeoId = GeoId;
2420
2422 await UpdateRegistrationTags(Registration, this.GetArray(Tags));
2423
2424 await e.IqResult(string.Empty, e.To);
2425
2427 {
2428 if (PrevGeoLocation)
2429 {
2430 if (PrevGeoId != GeoId)
2431 await this.geo.Delete(PrevGeoId);
2432
2433 await this.geo.Publish(Registration);
2434 }
2435 else
2436 await this.geo.Publish(Registration);
2437 }
2438 else if (PrevGeoLocation)
2439 await this.geo.Delete(PrevGeoId);
2440
2441 Log.Informational("Thing registration updated.", e.From.BareJid, string.Empty,
2442 "Update", GetLogParameters(Node, Tags.Values));
2443
2444 if (e.From.BareJid != Jid)
2445 await IncAccountSuccessfulUpdates(e.From.BareJid);
2446 }
2447 }
2448 catch (Exception ex)
2449 {
2450 await e.IqError(ex, e.To);
2451 }
2452 }
2453
2454 private static async Task IncAccountSuccessfulUpdates(CaseInsensitiveString JID)
2455 {
2456 RegistryAccount Account = await GetAccount(JID);
2457 Account.NrSuccessfulUpdates++;
2458 }
2459
2460 private static async Task IncAccountFailedUpdates(CaseInsensitiveString JID)
2461 {
2462 RegistryAccount Account = await GetAccount(JID);
2463 Account.NrFailedUpdates++;
2464 }
2465
2466 private async Task UnregisterHandler(object _, IqEventArgs e)
2467 {
2468 try
2469 {
2470 XmlElement E = e.Query;
2471 ThingReference Node;
2472
2473 Node = this.ParseNodeInfo(E);
2474
2475 IEnumerable<Registration> Registrations = await Database.Find<Registration>(new FilterAnd(
2476 new FilterFieldEqualTo("JID", e.From.BareJid),
2477 new FilterFieldEqualTo("NodeId", Node.NodeId),
2478 new FilterFieldEqualTo("SourceId", Node.SourceId),
2479 new FilterFieldEqualTo("Partition", Node.Partition)));
2480
2481 foreach (Registration R in Registrations)
2482 {
2483 MetaDataTag[] Tags = await DeleteTags(R);
2484 await this.DeleteRules(R);
2485 await Database.Delete(R);
2486 Log.Informational("Thing unregistered.", e.From.BareJid, string.Empty, "Unregister", GetLogParameters(Node, Tags));
2487 }
2488
2489 await e.IqResult(string.Empty, e.To);
2490 }
2491 catch (Exception ex)
2492 {
2493 await e.IqError(ex, e.To);
2494 }
2495 }
2496
2497 private async Task DeleteRules(Registration R)
2498 {
2499 if (string.IsNullOrEmpty(R.NodeId) && string.IsNullOrEmpty(R.SourceId) && string.IsNullOrEmpty(R.Partition))
2500 await Database.Delete<FriendshipRule>(new FilterFieldEqualTo("JID", R.JID));
2501
2502 await Database.Delete<ReadoutRule>(new FilterAnd(new FilterFieldEqualTo("JID", R.JID),
2503 new FilterFieldEqualTo("NodeID", R.NodeId), new FilterFieldEqualTo("SourceID", R.SourceId), new FilterFieldEqualTo("Partition", R.Partition)));
2504
2505 await Database.Delete<ControlRule>(new FilterAnd(new FilterFieldEqualTo("JID", R.JID),
2506 new FilterFieldEqualTo("NodeID", R.NodeId), new FilterFieldEqualTo("SourceID", R.SourceId), new FilterFieldEqualTo("Partition", R.Partition)));
2507 }
2508
2509 private async Task MineHandler(object _, IqEventArgs e)
2510 {
2511 XmlElement E = e.Query;
2512 bool Public = XML.Attribute(E, "public", false);
2513
2514 if (!this.ParseTags(e, E,
2515 out Dictionary<string, MetaDataTag> Tags,
2516 out string Key,
2517 out GeoPosition Location,
2518 out string GeoId))
2519 {
2520 await e.IqErrorBadRequest(e.To, "Unable to parse tags.", "en");
2521 return;
2522 }
2523
2524 if (string.IsNullOrEmpty(Key))
2525 {
2526 await e.IqErrorForbidden(e.To, "KEY meta tag must be included.", "en");
2527 await IncAccountFailedClaims(e.From.BareJid);
2528 return;
2529 }
2530
2531 IEnumerable<Registration> Registrations = await Database.Find<Registration>(0, int.MaxValue, new FilterFieldEqualTo("Key", Key));
2532 IEnumerable<MetaDataTag> MatchTags;
2533 bool Match;
2534 int c;
2535 foreach (Registration R in Registrations)
2536 {
2537 MatchTags = MatchTags = await Database.Find<MetaDataTag>(new FilterFieldEqualTo("Registration", R.ObjectId));
2538 c = 0;
2539 Match = true;
2540
2541 foreach (MetaDataTag Tag in MatchTags)
2542 {
2543 c++;
2544
2545 if (Tags.TryGetValue(Tag.Name, out MetaDataTag T))
2546 {
2547 if (Tag is MetaDataStringTag StringTag)
2548 {
2549 if (T is MetaDataStringTag T2 && StringTag.StringValue == T2.StringValue)
2550 continue;
2551 }
2552 else if (Tag is MetaDataNumericTag NumericTag)
2553 {
2554 if (T is MetaDataNumericTag T2 && NumericTag.TagValue == T2.TagValue)
2555 continue;
2556 }
2557 }
2558
2559 Match = false;
2560 break;
2561 }
2562
2563 if (!Match || c != Tags.Count)
2564 continue;
2565
2566 await Database.StartBulk();
2567 try
2568 {
2569 bool PrevGeoLocation = R.HasGeoLocation;
2570 string PrevGeoId = R.GeoId;
2571
2572 R.Key = string.Empty;
2573 R.Owner = e.From.BareJid;
2574 R.IsPublic = Public;
2575 R.Location = Location;
2576 R.GeoId = GeoId;
2577
2578 await Database.Update(R);
2579
2580 foreach (MetaDataTag Tag in MatchTags)
2581 {
2582 if (Tag.IsPublic != Public)
2583 {
2584 Tag.IsPublic = Public;
2585 await Database.Update(Tag);
2586 }
2587 }
2588
2589 if (R.HasGeoLocation)
2590 {
2591 if (PrevGeoLocation)
2592 {
2593 if (PrevGeoId != GeoId)
2594 await this.geo.Delete(PrevGeoId);
2595
2596 await this.geo.Publish(R);
2597 }
2598 else
2599 await this.geo.Publish(R);
2600 }
2601 else if (PrevGeoLocation)
2602 await this.geo.Delete(PrevGeoId);
2603 }
2604 finally
2605 {
2606 await Database.EndBulk();
2607 }
2608
2609 StringBuilder Response = new StringBuilder();
2610
2611 Response.Append("<claimed xmlns='");
2612 Response.Append(e.Query.NamespaceURI);
2613 Response.Append("' jid='");
2614 Response.Append(XML.Encode(R.JID));
2615
2616 if (!string.IsNullOrEmpty(R.NodeId))
2617 {
2618 Response.Append("' id='");
2619 Response.Append(XML.Encode(R.NodeId));
2620 }
2621
2622 if (!string.IsNullOrEmpty(R.SourceId))
2623 {
2624 Response.Append("' src='");
2625 Response.Append(XML.Encode(R.SourceId));
2626 }
2627
2628 if (!string.IsNullOrEmpty(R.Partition))
2629 {
2630 Response.Append("' pt='");
2631 Response.Append(XML.Encode(R.Partition));
2632 }
2633
2634 Response.Append("'/>");
2635
2636 await e.IqResult(Response.ToString(), e.To);
2637
2638 // Send message to thing, informing it, it has been claimed:
2639
2640 Response.Clear();
2641
2642 Response.Append("<claimed xmlns='");
2643 Response.Append(NamespaceIoTDiscovery(R.Version));
2644 Response.Append("' jid='");
2645 Response.Append(XML.Encode(e.From.BareJid));
2646 Response.Append("' public='");
2647 Response.Append(CommonTypes.Encode(Public));
2648
2649 if (!string.IsNullOrEmpty(R.NodeId))
2650 {
2651 Response.Append("' id='");
2652 Response.Append(XML.Encode(R.NodeId));
2653 }
2654
2655 if (!string.IsNullOrEmpty(R.SourceId))
2656 {
2657 Response.Append("' src='");
2658 Response.Append(XML.Encode(R.SourceId));
2659 }
2660
2661 if (!string.IsNullOrEmpty(R.Partition))
2662 {
2663 Response.Append("' pt='");
2664 Response.Append(XML.Encode(R.Partition));
2665 }
2666
2667 Response.Append("'/>");
2668
2669 await this.Server.GetLastPresence(R.JID, async (sender, e2) =>
2670 {
2671 if (e2.Ok)
2672 await this.Server.SendIqRequest("set", e.To, e2.From, string.Empty, Response.ToString(), false, null, null);
2673 }, null);
2674
2675 Log.Informational("Thing claimed.", R.JID, e.From.Address, "Claim", GetLogParameters(R, MatchTags));
2676 await IncAccountSuccessfulClaims(e.From.BareJid);
2677 return;
2678 }
2679
2680 await e.IqErrorItemNotFound(e.To, "Thing not found, or already claimed.", "en");
2681 await IncAccountFailedClaims(e.From.BareJid);
2682 }
2683
2684 private static async Task<MetaDataTag[]> DeleteTags(Registration Registration)
2685 {
2686 List<MetaDataTag> Tags = new List<MetaDataTag>();
2687
2688 foreach (MetaDataTag Tag in await Database.FindDelete<MetaDataTag>(new FilterFieldEqualTo("Registration", Registration.ObjectId)))
2689 Tags.Add(Tag);
2690
2691 return Tags.ToArray();
2692 }
2693
2694 private static async Task SaveNewRegistrationTags(Registration Registration, MetaDataTag[] Tags)
2695 {
2696 foreach (MetaDataTag Tag in Tags)
2697 {
2698 if (Tag.IsEmpty)
2699 continue;
2700
2701 Tag.Registration = Registration.ObjectId;
2702 Tag.IsPublic = Registration.IsPublic;
2703
2704 await Database.Insert(Tag);
2705 }
2706 }
2707
2708 private static Task<IEnumerable<MetaDataTag>> LoadTags(Guid RegistrationReference)
2709 {
2710 return Database.Find<MetaDataTag>(new FilterFieldEqualTo("Registration", RegistrationReference));
2711 }
2712
2713 private static async Task<IEnumerable<MetaDataTag>> UpdateRegistrationTags(Registration Registration, MetaDataTag[] Tags)
2714 {
2715 Dictionary<string, MetaDataTag> Tags2 = new Dictionary<string, MetaDataTag>(StringComparer.CurrentCultureIgnoreCase);
2716
2717 foreach (MetaDataTag Tag in await Database.Find<MetaDataTag>(new FilterFieldEqualTo("Registration", Registration.ObjectId)))
2718 Tags2[Tag.Name] = Tag;
2719
2720 if (!(Tags is null))
2721 {
2722 foreach (MetaDataTag Tag in Tags)
2723 {
2724 if (Tags2.TryGetValue(Tag.Name, out MetaDataTag Tag2))
2725 {
2726 if (Tag.IsEmpty)
2727 {
2728 await Database.Delete(Tag2);
2729 Tags2.Remove(Tag.Name);
2730 }
2731 else
2732 {
2733 Tag.ObjectId = Tag2.ObjectId;
2734 Tag.Registration = Registration.ObjectId;
2735 Tag.IsPublic = Registration.IsPublic;
2736
2737 await Database.Update(Tag);
2738 }
2739 }
2740 else if (!Tag.IsEmpty)
2741 {
2742 Tag.Registration = Registration.ObjectId;
2743 Tag.IsPublic = Registration.IsPublic;
2744
2745 await Database.Insert(Tag);
2746 }
2747 }
2748 }
2749
2750 foreach (MetaDataTag Tag in Tags2.Values)
2751 {
2752 if (Tag.IsPublic != Registration.IsPublic)
2753 {
2754 Tag.IsPublic = Registration.IsPublic;
2755 await Database.Update(Tag);
2756 }
2757 }
2758
2759 return Tags2.Values;
2760 }
2761
2762 private static async Task IncAccountSuccessfulClaims(CaseInsensitiveString JID)
2763 {
2764 RegistryAccount Account = await GetAccount(JID);
2765 Account.NrSuccessfulClaims++;
2766 }
2767
2768 private static async Task IncAccountFailedClaims(CaseInsensitiveString JID)
2769 {
2770 RegistryAccount Account = await GetAccount(JID);
2771 Account.NrFailedClaims++;
2772 }
2773
2774 private async Task DisownHandler(object _, IqEventArgs e)
2775 {
2776 try
2777 {
2778 XmlElement E = e.Query;
2779 ThingReference Node;
2780 CaseInsensitiveString Jid = XML.Attribute(E, "jid", string.Empty);
2781
2782 Node = this.ParseNodeInfo(E);
2783
2784 IEnumerable<Registration> Registrations = await Database.Find<Registration>(new FilterAnd(
2785 new FilterFieldEqualTo("JID", Jid),
2786 new FilterFieldEqualTo("NodeId", Node.NodeId),
2787 new FilterFieldEqualTo("SourceId", Node.SourceId),
2788 new FilterFieldEqualTo("Partition", Node.Partition)));
2789
2790 foreach (Registration R in Registrations)
2791 {
2792 if (R.Owner == e.From.BareJid)
2793 {
2794 StringBuilder Request = new StringBuilder();
2795
2796 Request.Append("<disowned xmlns='");
2797 Request.Append(NamespaceIoTDiscovery(R.Version));
2798 AppendNodeReference(Request, Node);
2799 Request.Append("'/>");
2800
2801 await this.Server.GetLastPresence(Jid, async (sender, e3) =>
2802 {
2803 try
2804 {
2805 if (e3.Ok)
2806 {
2807 R.Key = Convert.ToBase64String(Gateway.NextBytes(16));
2808 R.IsPublic = false;
2809 R.Owner = string.Empty;
2810
2811 await this.Server.SendIqRequest("set", e.To, e3.From, string.Empty,
2812 Request.ToString(), false, async (sender2, e4) =>
2813 {
2814 try
2815 {
2816 if (e4.Ok)
2817 {
2818 await e.IqResult(string.Empty, e.To);
2819
2820 IEnumerable<MetaDataTag> Tags = await LoadTags(R.ObjectId);
2821
2822 await Database.StartBulk();
2823 try
2824 {
2825 await Database.Update(R);
2826 await Database.Delete(Tags);
2827 await this.DeleteRules(R);
2828 }
2829 finally
2830 {
2831 await Database.EndBulk();
2832 }
2833
2834 Log.Informational("Thing disowned.", R.JID, Jid, "Disown", GetLogParameters(Node, Tags));
2835
2836 await IncAccountSuccessfulDisownments(e.From.BareJid);
2837 }
2838 else
2839 await e.IqErrorNotAllowed(e.To, "Thing needs to accept disownment.", "en");
2840 }
2841 catch (Exception ex)
2842 {
2843 Log.Exception(ex);
2844 }
2845 }, null);
2846 }
2847 else
2848 await e.IqErrorNotAllowed(e.To, "Unable to reach thing.", "en");
2849 }
2850 catch (Exception ex)
2851 {
2852 Log.Exception(ex);
2853 }
2854 }, null);
2855 }
2856 else
2857 {
2858 await e.IqErrorItemNotFound(e.To, "Thing not found.", "en");
2859 await IncAccountFailedDisownments(e.From.BareJid);
2860 }
2861
2862 return;
2863 }
2864
2865 await e.IqErrorItemNotFound(e.To, "Thing not found.", "en");
2866 await IncAccountFailedDisownments(e.From.BareJid);
2867 }
2868 catch (Exception ex)
2869 {
2870 await e.IqError(ex, e.To);
2871 }
2872 }
2873
2874 private static async Task IncAccountSuccessfulDisownments(CaseInsensitiveString JID)
2875 {
2876 RegistryAccount Account = await GetAccount(JID);
2877 Account.NrSuccessfulDisownments++;
2878 }
2879
2880 private static async Task IncAccountFailedDisownments(CaseInsensitiveString JID)
2881 {
2882 RegistryAccount Account = await GetAccount(JID);
2883 Account.NrFailedDisownments++;
2884 }
2885
2886 private async Task RemoveHandler(object _, IqEventArgs e)
2887 {
2888 try
2889 {
2890 XmlElement E = e.Query;
2891 ThingReference Node;
2892 CaseInsensitiveString Jid = XML.Attribute(E, "jid", string.Empty);
2893
2894 Node = this.ParseNodeInfo(E);
2895
2896 IEnumerable<Registration> Registrations = await Database.Find<Registration>(new FilterAnd(
2897 new FilterFieldEqualTo("JID", Jid),
2898 new FilterFieldEqualTo("NodeId", Node.NodeId),
2899 new FilterFieldEqualTo("SourceId", Node.SourceId),
2900 new FilterFieldEqualTo("Partition", Node.Partition)));
2901
2902 foreach (Registration R in Registrations)
2903 {
2904 if (R.Owner == e.From.BareJid)
2905 {
2906 if (R.IsPublic)
2907 {
2908 R.IsPublic = false;
2909 await Database.Update(R);
2910 IEnumerable<MetaDataTag> Tags = await LoadTags(R.ObjectId);
2911
2912 Log.Informational("Thing removed from public registry.", R.JID, Jid, "Remove", GetLogParameters(Node, Tags));
2913 }
2914
2915 await e.IqResult(string.Empty, e.To);
2916
2917 StringBuilder Request = new StringBuilder();
2918
2919 Request.Append("<removed xmlns='");
2920 Request.Append(NamespaceIoTDiscovery(R.Version));
2921 AppendNodeReference(Request, Node);
2922 Request.Append("'/>");
2923
2924 await this.Server.GetLastPresence(Jid, async (sender, e3) =>
2925 {
2926 try
2927 {
2928 if (e3.Ok)
2929 await this.Server.SendIqRequest("set", e.To, e3.From, string.Empty, Request.ToString(), false, null, null);
2930 }
2931 catch (Exception ex)
2932 {
2933 Log.Exception(ex);
2934 }
2935 }, null);
2936
2937 await IncAccountSuccessfulRemovals(e.From.BareJid);
2938 }
2939 else
2940 {
2941 await e.IqErrorItemNotFound(e.To, "Thing not found.", "en");
2942 await IncAccountFailedRemovals(e.From.BareJid);
2943 }
2944
2945 return;
2946 }
2947
2948 await e.IqErrorItemNotFound(e.To, "Thing not found.", "en");
2949 await IncAccountFailedRemovals(e.From.BareJid);
2950 }
2951 catch (Exception ex)
2952 {
2953 await e.IqError(ex, e.To);
2954 }
2955 }
2956
2957 private static async Task IncAccountSuccessfulRemovals(CaseInsensitiveString JID)
2958 {
2959 RegistryAccount Account = await GetAccount(JID);
2960 Account.NrSuccessfulRemovals++;
2961 }
2962
2963 private static async Task IncAccountFailedRemovals(CaseInsensitiveString JID)
2964 {
2965 RegistryAccount Account = await GetAccount(JID);
2966 Account.NrFailedRemovals++;
2967 }
2968
2969 private async Task SearchHandler(object _, IqEventArgs e)
2970 {
2971 try
2972 {
2973 XmlElement E = e.Query;
2974 XmlElement E2;
2975
2976 int Offset = XML.Attribute(E, "offset", 0);
2977 if (Offset < 0)
2978 {
2979 await e.IqErrorBadRequest(e.To, "Offset must be non-negative.", "en");
2980 return;
2981 }
2982
2983 int MaxCount = XML.Attribute(E, "maxCount", 20);
2984 if (MaxCount > 100)
2985 MaxCount = 100;
2986 else if (MaxCount <= 0)
2987 {
2988 await e.IqErrorBadRequest(e.To, "Maximum count must be positive.", "en");
2989 return;
2990 }
2991
2992 List<SearchOperator> SearchOperators = new List<SearchOperator>();
2993 ChunkedList<Filter> TagFilters = new ChunkedList<Filter>();
2994 Dictionary<Guid, bool> RegistrationMatches = new Dictionary<Guid, bool>();
2995 Dictionary<Guid, bool> BestRegistrationMatches = null;
2996 const int MaxMaxTagCount = 10000;
2997 int MaxTagCount = MaxMaxTagCount;
2998 int MaxRegistrationCount = MaxMaxTagCount;
2999 SearchOperator Op;
3000 string Name;
3001 string NameWildcard;
3002
3003 foreach (XmlNode N in E.ChildNodes)
3004 {
3005 E2 = N as XmlElement;
3006 if (E2 is null)
3007 continue;
3008
3009 Name = XML.Attribute(E2, "name").ToUpper();
3010 if (string.IsNullOrEmpty(Name))
3011 {
3012 await e.IqErrorBadRequest(e.To, "Invalid tag name.", "en");
3013 return;
3014 }
3015
3016 if (Name == "KEY")
3017 {
3018 await e.IqResult("<found xmlns='" + e.Query.NamespaceURI + "' more='false'/>", e.To);
3019 await IncAccountFailedSearches(e.From.BareJid);
3020 return;
3021 }
3022
3023 if (E2.HasAttribute("nameWildcard"))
3024 {
3025 NameWildcard = XML.Attribute(E2, "nameWildcard");
3026 if (string.IsNullOrEmpty(NameWildcard) || !Name.Contains(NameWildcard))
3027 NameWildcard = null;
3028 }
3029 else
3030 NameWildcard = null;
3031
3032 switch (E2.LocalName)
3033 {
3034 case "strEq":
3035 SearchOperators.Add(Op = new StringTagEqualTo(Name, NameWildcard,
3036 XML.Attribute(E2, "value")));
3037 break;
3038
3039 case "strNEq":
3040 SearchOperators.Add(Op = new StringTagNotEqualTo(Name, NameWildcard,
3041 XML.Attribute(E2, "value")));
3042 break;
3043
3044 case "strGt":
3045 SearchOperators.Add(Op = new StringTagGreaterThan(Name, NameWildcard,
3046 XML.Attribute(E2, "value")));
3047 break;
3048
3049 case "strGtEq":
3050 SearchOperators.Add(Op = new StringTagGreaterThanOrEqualTo(Name, NameWildcard,
3051 XML.Attribute(E2, "value")));
3052 break;
3053
3054 case "strLt":
3055 SearchOperators.Add(Op = new StringTagLesserThan(Name, NameWildcard,
3056 XML.Attribute(E2, "value")));
3057 break;
3058
3059 case "strLtEq":
3060 SearchOperators.Add(Op = new StringTagLesserThanOrEqualTo(Name, NameWildcard,
3061 XML.Attribute(E2, "value")));
3062 break;
3063
3064 case "strRegEx":
3065 SearchOperators.Add(Op = new StringTagRegEx(Name, NameWildcard,
3066 XML.Attribute(E2, "value")));
3067 break;
3068
3069 case "strRange":
3070 SearchOperators.Add(Op = new StringTagInRange(Name, NameWildcard,
3071 XML.Attribute(E2, "min"),
3072 XML.Attribute(E2, "minIncluded", true),
3073 XML.Attribute(E2, "max"),
3074 XML.Attribute(E2, "maxIncluded", true)));
3075 break;
3076
3077 case "strNRange":
3078 SearchOperators.Add(Op = new StringTagNotInRange(Name, NameWildcard,
3079 XML.Attribute(E2, "min"),
3080 XML.Attribute(E2, "minIncluded", true),
3081 XML.Attribute(E2, "max"),
3082 XML.Attribute(E2, "maxIncluded", true)));
3083 break;
3084
3085 case "strMask":
3086 SearchOperators.Add(Op = new StringTagMask(Name, NameWildcard,
3087 XML.Attribute(E2, "value"),
3088 XML.Attribute(E2, "wildcard")));
3089 break;
3090
3091 case "numEq":
3092 SearchOperators.Add(Op = new NumericTagEqualTo(Name, NameWildcard,
3093 XML.Attribute(E2, "value", 0.0)));
3094 break;
3095
3096 case "numNEq":
3097 SearchOperators.Add(Op = new NumericTagNotEqualTo(Name, NameWildcard,
3098 XML.Attribute(E2, "value", 0.0)));
3099 break;
3100
3101 case "numGt":
3102 SearchOperators.Add(Op = new NumericTagGreaterThan(Name, NameWildcard,
3103 XML.Attribute(E2, "value", 0.0)));
3104 break;
3105
3106 case "numGtEq":
3107 SearchOperators.Add(Op = new NumericTagGreaterThanOrEqualTo(Name, NameWildcard,
3108 XML.Attribute(E2, "value", 0.0)));
3109 break;
3110
3111 case "numLt":
3112 SearchOperators.Add(Op = new NumericTagLesserThan(Name, NameWildcard,
3113 XML.Attribute(E2, "value", 0.0)));
3114 break;
3115
3116 case "numLtEq":
3117 SearchOperators.Add(Op = new NumericTagLesserThanOrEqualTo(Name, NameWildcard,
3118 XML.Attribute(E2, "value", 0.0)));
3119 break;
3120
3121 case "numRange":
3122 SearchOperators.Add(Op = new NumericTagInRange(Name, NameWildcard,
3123 XML.Attribute(E2, "min", 0.0),
3124 XML.Attribute(E2, "minIncluded", true),
3125 XML.Attribute(E2, "max", 0.0),
3126 XML.Attribute(E2, "maxIncluded", true)));
3127 break;
3128
3129 case "numNRange":
3130 SearchOperators.Add(Op = new NumericTagNotInRange(Name, NameWildcard,
3131 XML.Attribute(E2, "min", 0.0),
3132 XML.Attribute(E2, "minIncluded", true),
3133 XML.Attribute(E2, "max", 0.0),
3134 XML.Attribute(E2, "maxIncluded", true)));
3135 break;
3136
3137 default:
3138 await e.IqResult("<found xmlns='" + e.Query.NamespaceURI + "' more='false'/>", e.To);
3139 await IncAccountFailedSearches(e.From.BareJid);
3140 return;
3141 }
3142
3143 TagFilters.Clear();
3144 TagFilters.Add(new FilterFieldEqualTo("IsPublic", true));
3145
3146 if (Op.HasNameWildcard)
3147 TagFilters.Add(new FilterFieldLikeRegEx("Name", Database.WildcardToRegex(Op.Name, Op.NameWildcard)));
3148 else
3149 TagFilters.Add(new FilterFieldEqualTo("Name", Op.Name));
3150
3151 Op.AddValueFilters(TagFilters);
3152
3153 RegistrationMatches.Clear();
3154
3155 int c = 0;
3156 IEnumerable<MetaDataTag> TagsFound = await Database.Find<MetaDataTag>(0, MaxTagCount,
3157 new FilterAnd(TagFilters.ToArray()));
3158
3159 foreach (MetaDataTag Tag in TagsFound)
3160 {
3161 RegistrationMatches[Tag.Registration] = true;
3162 c++;
3163 }
3164
3165 if (c == 0)
3166 {
3167 await e.IqResult("<found xmlns='" + e.Query.NamespaceURI + "' more='false'/>", e.To);
3168 await IncAccountSuccessfulSearches(e.From.BareJid, 0);
3169 return;
3170 }
3171 else
3172 {
3173 if (c < MaxTagCount)
3174 MaxTagCount = c;
3175
3176 c = RegistrationMatches.Count;
3177 if (c < MaxRegistrationCount)
3178 {
3179 MaxRegistrationCount = c;
3180 BestRegistrationMatches = RegistrationMatches;
3181 }
3182 }
3183 }
3184
3185 if (MaxTagCount == MaxMaxTagCount || BestRegistrationMatches is null)
3186 {
3187 await e.IqErrorBadRequest(e.To, "Too wide a search. Restrict the scope of the search by including restrictive tags.", "en");
3188 return;
3189 }
3190
3191 bool More = false;
3192 List<KeyValuePair<Registration, IEnumerable<MetaDataTag>>> Result = new List<KeyValuePair<Registration, IEnumerable<MetaDataTag>>>();
3193 Dictionary<string, MetaDataTag> TagsSorted = new Dictionary<string, MetaDataTag>(StringComparer.CurrentCultureIgnoreCase);
3194 IEnumerable<MetaDataTag> Tags;
3195
3196 foreach (Guid Registration in BestRegistrationMatches.Keys)
3197 {
3198 bool Applies = true;
3199
3200 TagsSorted.Clear();
3201 Tags = await LoadTags(Registration);
3202
3203 foreach (MetaDataTag Tag2 in Tags)
3204 {
3205 if (Tag2.Name == "KEY")
3206 {
3207 Applies = false;
3208 break;
3209 }
3210
3211 TagsSorted[Tag2.Name] = Tag2;
3212 }
3213
3214 if (!Applies)
3215 continue;
3216
3217 foreach (SearchOperator Op2 in SearchOperators)
3218 {
3219 if (Op2.HasNameWildcard)
3220 {
3221 Regex NameExpression = new Regex(
3223 RegexOptions.Singleline);
3224
3225 bool Match = false;
3226
3227 foreach (MetaDataTag Tag3 in TagsSorted.Values)
3228 {
3229 Match M = NameExpression.Match(Tag3.Name);
3230 if (!M.Success || M.Index > 0 || M.Length != Tag3.Name.Length)
3231 continue;
3232
3233 if (Op2.AppliesTo(Tag3))
3234 {
3235 Match = true;
3236 break;
3237 }
3238 }
3239
3240 if (!Match)
3241 {
3242 Applies = false;
3243 break;
3244 }
3245 }
3246 else if (!TagsSorted.TryGetValue(Op2.Name, out MetaDataTag Tag3) ||
3247 !Op2.AppliesTo(Tag3))
3248 {
3249 Applies = false;
3250 break;
3251 }
3252 }
3253
3254 if (!Applies)
3255 continue;
3256
3257 if (Offset > 0)
3258 Offset--;
3259 else if (Result.Count == MaxCount)
3260 {
3261 More = true;
3262 break;
3263 }
3264 else
3265 {
3267 if (R is null)
3268 await Database.Delete(Tags); // Obsolete tags.
3269 else if (R.IsPublic)
3270 Result.Add(new KeyValuePair<Registration, IEnumerable<MetaDataTag>>(R, Tags));
3271 }
3272 }
3273
3274 await e.IqResult(ResultSet(Result, More, e.Query.NamespaceURI), e.To);
3275
3276 await IncAccountSuccessfulSearches(e.From.BareJid, Result.Count);
3277 }
3278 catch (Exception ex)
3279 {
3280 await e.IqError(ex, e.To);
3281 await IncAccountFailedSearches(e.From.BareJid);
3282 }
3283 }
3284
3285 public static string ResultSet(List<KeyValuePair<Registration, IEnumerable<MetaDataTag>>> Result, bool More, string Namespace)
3286 {
3287 StringBuilder Response = new StringBuilder();
3288
3289 Response.Append("<found xmlns='");
3290 Response.Append(Namespace);
3291 Response.Append("' more='");
3292 Response.Append(CommonTypes.Encode(More));
3293 Response.Append("'>");
3294
3295 foreach (KeyValuePair<Registration, IEnumerable<MetaDataTag>> P in Result)
3296 {
3297 Response.Append("<thing jid='");
3298 Response.Append(XML.Encode(P.Key.JID));
3299
3300 if (!string.IsNullOrEmpty(P.Key.NodeId))
3301 {
3302 Response.Append("' id='");
3303 Response.Append(XML.Encode(P.Key.NodeId));
3304 }
3305
3306 if (!string.IsNullOrEmpty(P.Key.SourceId))
3307 {
3308 Response.Append("' src='");
3309 Response.Append(XML.Encode(P.Key.SourceId));
3310 }
3311
3312 if (!string.IsNullOrEmpty(P.Key.Partition))
3313 {
3314 Response.Append("' pt='");
3315 Response.Append(XML.Encode(P.Key.Partition));
3316 }
3317
3318 Response.Append("'>");
3319
3320 if (!(P.Value is null))
3321 {
3322 foreach (MetaDataTag Tag in P.Value)
3323 {
3324 if (Tag is MetaDataStringTag StrTag)
3325 {
3326 Response.Append("<str name='");
3327 Response.Append(XML.Encode(Tag.Name));
3328 Response.Append("' value='");
3329 Response.Append(XML.Encode(StrTag.TagValue));
3330 Response.Append("'/>");
3331 }
3332 else if (Tag is MetaDataNumericTag NumTag)
3333 {
3334 Response.Append("<num name='");
3335 Response.Append(XML.Encode(Tag.Name));
3336 Response.Append("' value='");
3337 Response.Append(CommonTypes.Encode(NumTag.TagValue));
3338 Response.Append("'/>");
3339 }
3340 }
3341 }
3342
3343 Response.Append("</thing>");
3344 }
3345
3346 Response.Append("</found>");
3347
3348 return Response.ToString();
3349 }
3350
3351 private static async Task IncAccountSuccessfulSearches(CaseInsensitiveString JID, int NrItems)
3352 {
3353 RegistryAccount Account = await GetAccount(JID);
3354 Account.NrSuccessfulSearches++;
3355 Account.NrSearchResultItems += NrItems;
3356 }
3357
3358 private static async Task IncAccountFailedSearches(CaseInsensitiveString JID)
3359 {
3360 RegistryAccount Account = await GetAccount(JID);
3361 Account.NrFailedSearches++;
3362 }
3363
3364 private ThingReference ParseNodeInfo(XmlElement E)
3365 {
3366 string NodeId = XML.Attribute(E, "id");
3367 string SourceId = XML.Attribute(E, "src");
3368 string Partition = XML.Attribute(E, "pt");
3369
3370 if (string.IsNullOrEmpty(NodeId) && string.IsNullOrEmpty(SourceId) && string.IsNullOrEmpty(Partition))
3371 return ThingReference.Empty;
3372 else
3373 return new ThingReference(NodeId, SourceId, Partition);
3374 }
3375
3376 private bool ParseTags(IqEventArgs e, XmlElement E,
3377 out Dictionary<string, MetaDataTag> Tags,
3378 out string Key,
3379 out GeoPosition Location,
3380 out string GeoId)
3381 {
3382 XmlElement E2;
3383 double? Latitude = null;
3384 double? Longitude = null;
3385 double? Altitude = null;
3386
3387 Tags = new Dictionary<string, MetaDataTag>();
3388 Key = null;
3389 Location = null;
3390 GeoId = null;
3391
3392 foreach (XmlNode N in E.ChildNodes)
3393 {
3394 E2 = N as XmlElement;
3395 if (E2 is null)
3396 continue;
3397
3398 string Name = XML.Attribute(E2, "name").ToUpper();
3399 if (Name.Length > 64)
3400 {
3401 e.IqErrorBadRequest(e.To, "Tag names must not be longer than 64 characters.", "en");
3402 return false;
3403 }
3404
3405 if (Name == "R")
3406 {
3407 e.IqErrorBadRequest(e.To, "The R tag is predefined.", "en");
3408 return false;
3409 }
3410
3411 foreach (char ch in Name)
3412 {
3413 if (ch == ':' || ch == '#' || char.IsWhiteSpace(ch))
3414 {
3415 e.IqErrorBadRequest(e.To, "Illegal character in tag name.", "en");
3416 return false;
3417 }
3418 }
3419
3420 switch (N.LocalName)
3421 {
3422 case "str":
3423 string Value = XML.Attribute(E2, "value");
3424 if (Value.Length > 128)
3425 {
3426 e.IqErrorBadRequest(e.To, "String value must not be longer than 128 characters.", "en");
3427 return false;
3428 }
3429
3430 if (Name == "KEY")
3431 Key = Value;
3432 else
3433 Tags[Name] = new MetaDataStringTag(Name, Value);
3434 break;
3435
3436 case "num":
3437 MetaDataNumericTag NumTag = new MetaDataNumericTag(Name, XML.Attribute(E2, "value", 0.0));
3438 Tags[Name] = NumTag;
3439
3440 switch (Name)
3441 {
3442 case "LAT":
3443 Latitude = NumTag.TagValue;
3444 break;
3445
3446 case "LON":
3447 Longitude = NumTag.TagValue;
3448 break;
3449
3450 case "ALT":
3451 Altitude = NumTag.TagValue;
3452 break;
3453 }
3454 break;
3455 }
3456 }
3457
3458 if (Tags.Count > 100) // TODO: Make configurable.
3459 {
3460 e.IqErrorResourceConstraint(e.To, "Too many tags provided. A maximum of 100 meta-data tags allowed.", "en");
3461 return false;
3462 }
3463
3464 if (Latitude.HasValue && Longitude.HasValue && Key is null)
3465 {
3466 Location = new GeoPosition(Latitude.Value, Longitude.Value, Altitude);
3467
3468 StringBuilder sb = new StringBuilder("iotdisco:");
3469 bool First = true;
3470
3471 foreach (MetaDataTag Tag in Tags.Values)
3472 {
3473 switch (Tag.Name)
3474 {
3475 case "LAT":
3476 case "LON":
3477 case "ALT":
3478 continue;
3479 }
3480
3481 if (First)
3482 First = false;
3483 else
3484 sb.Append(';');
3485
3486 if (Tag is MetaDataNumericTag)
3487 sb.Append('#');
3488
3489 sb.Append(Uri.EscapeDataString(Tag.Name));
3490 sb.Append('=');
3491 sb.Append(Uri.EscapeDataString(Tag.StringValue));
3492 }
3493
3494 if (!First)
3495 sb.Append(';');
3496
3497 sb.Append("R=");
3498 sb.Append(Uri.EscapeDataString(this.MainDomain.Address));
3499
3500 GeoId = sb.ToString();
3501 }
3502
3503 return true;
3504 }
3505
3506 internal static void AppendNodeReference(StringBuilder Request, ThingReference Node)
3507 {
3508 if (!string.IsNullOrEmpty(Node.NodeId))
3509 {
3510 Request.Append("' id='");
3511 Request.Append(XML.Encode(Node.NodeId));
3512 }
3513
3514 if (!string.IsNullOrEmpty(Node.SourceId))
3515 {
3516 Request.Append("' src='");
3517 Request.Append(XML.Encode(Node.SourceId));
3518 }
3519
3520 if (!string.IsNullOrEmpty(Node.Partition))
3521 {
3522 Request.Append("' pt='");
3523 Request.Append(XML.Encode(Node.Partition));
3524 }
3525 }
3526
3527 private async Task DeleteRulesHandler(object Sender, IqEventArgs e)
3528 {
3529 try
3530 {
3531 CaseInsensitiveString Jid = XML.Attribute(e.Query, "jid");
3532 string NodeId = XML.Attribute(e.Query, "id");
3533 string SourceId = XML.Attribute(e.Query, "src");
3534 string Partition = XML.Attribute(e.Query, "pt");
3535 NamespaceSet QueryVersion = XmppServerModule.GetVersion(e.Query.NamespaceURI);
3536
3538 string.IsNullOrEmpty(NodeId) &&
3539 string.IsNullOrEmpty(SourceId) &&
3540 string.IsNullOrEmpty(Partition))
3541 {
3542 foreach (Registration R in await Database.Find<Registration>(new FilterFieldEqualTo("Owner", e.From.BareJid)))
3543 {
3544 await this.DeleteRules(R);
3545 await this.ClearCache(R.JID, R.NodeId, R.SourceId, R.Partition, R.Version);
3546 }
3547 }
3548 else if (string.IsNullOrEmpty(NodeId) &&
3549 string.IsNullOrEmpty(SourceId) &&
3550 string.IsNullOrEmpty(Partition))
3551 {
3552 foreach (Registration R in await Database.Find<Registration>(new FilterAnd(
3553 new FilterFieldEqualTo("Owner", e.From.BareJid),
3554 new FilterFieldEqualTo("JID", Jid))))
3555 {
3556 await this.DeleteRules(R);
3557 await this.ClearCache(R.JID, R.NodeId, R.SourceId, R.Partition, R.Version);
3558 }
3559 }
3560 else
3561 {
3562 Registration R = await Database.FindFirstDeleteRest<Registration>(new FilterAnd(
3563 new FilterFieldEqualTo("Owner", e.From.BareJid),
3564 new FilterFieldEqualTo("JID", Jid),
3565 new FilterFieldEqualTo("NodeId", NodeId),
3566 new FilterFieldEqualTo("SourceId", SourceId),
3567 new FilterFieldEqualTo("Partition", Partition)));
3568
3569 if (!(R is null))
3570 {
3571 await this.DeleteRules(R);
3572 await this.ClearCache(Jid, NodeId, SourceId, Partition, R.Version);
3573 }
3574 }
3575
3576 await e.IqResult(string.Empty, e.To);
3577 }
3578 catch (Exception ex)
3579 {
3580 await e.IqError(ex, e.To);
3581 }
3582 }
3583
3584 #endregion
3585
3586 #region Software Updates
3587
3588 private async Task GetPackageInfoHandler(object Sender, IqEventArgs e)
3589 {
3590 CaseInsensitiveString FileName = XML.Attribute(e.Query, "fileName");
3591 Package Package = await this.GetPackage(FileName, e);
3592 if (Package is null)
3593 return;
3594
3595 NamespaceSet QueryVersion = XmppServerModule.GetVersion(e.Query.NamespaceURI);
3596 StringBuilder Xml = new StringBuilder();
3597 Serialize(Package, Xml, QueryVersion);
3598 await e.IqResult(Xml.ToString(), e.To);
3599 }
3600
3601 internal bool CheckSameDomain(IqEventArgs e)
3602 {
3603 if (!this.Server.IsServerDomain(e.From.Domain, true))
3604 {
3605 e.IqErrorForbidden(e.To, "Software updates are not available to domain " + e.From.Domain + ".", "en");
3606 return false;
3607 }
3608 else
3609 return true;
3610 }
3611
3612 internal async Task<Package> GetPackage(CaseInsensitiveString FileName, IqEventArgs e)
3613 {
3614 if (!this.CheckSameDomain(e))
3615 return null;
3616
3618 {
3619 await e.IqErrorBadRequest(e.To, "Missing fileName attribute.", "en");
3620 return null;
3621 }
3622
3623 Package Package = await GetPackage(FileName);
3624 if (Package is null)
3625 {
3626 await e.IqErrorItemNotFound(e.To, "Package with requested file name not found.", "en");
3627 return null;
3628 }
3629
3630 string FullFileName = Path.Combine(XmppServerModule.PackagesFolder, FileName);
3631 if (!File.Exists(FullFileName))
3632 {
3633 await e.IqErrorItemNotFound(e.To, "Package with requested file name not found.", "en");
3634 return null;
3635 }
3636
3637 return Package;
3638 }
3639
3640
3641 private static readonly Dictionary<CaseInsensitiveString, Package> packages = new Dictionary<CaseInsensitiveString, Package>();
3642 private static bool packagesLoaded = false;
3643
3644 internal static async Task<Package> GetPackage(CaseInsensitiveString FileName)
3645 {
3647
3648 lock (packages)
3649 {
3650 if (!packages.TryGetValue(FileName, out Package))
3651 Package = null;
3652 }
3653
3654 if (Package is null)
3655 {
3656 Package = await Database.FindFirstDeleteRest<Package>(new FilterFieldEqualTo("FileName", FileName));
3657 if (Package is null)
3658 return null;
3659
3660 lock (packages)
3661 {
3662 packages[FileName] = Package;
3663 }
3664 }
3665
3666 return Package;
3667 }
3668
3669 private static void Serialize(Package Package, StringBuilder Xml, NamespaceSet? Version)
3670 {
3671 Serialize(Package, "packageInfo", Xml, Version);
3672 }
3673
3674 private static void Serialize(Package Package, string LocalName, StringBuilder Xml, NamespaceSet? Version)
3675 {
3676 Xml.Append('<');
3677 Xml.Append(LocalName);
3678 Xml.Append(" fileName='");
3679 Xml.Append(XML.Encode(Package.FileName));
3680 Xml.Append("' signature='");
3681 Xml.Append(Convert.ToBase64String(Package.Signature));
3682 Xml.Append("' published='");
3683 Xml.Append(XML.Encode(Package.Published));
3684
3685 if (Package.Supersedes != DateTime.MinValue)
3686 {
3687 Xml.Append("' supersedes='");
3688 Xml.Append(XML.Encode(Package.Supersedes));
3689 }
3690
3691 Xml.Append("' created='");
3692 Xml.Append(XML.Encode(Package.Created));
3693 Xml.Append("' url='");
3694 Xml.Append(XML.Encode(Package.Url));
3695 Xml.Append("' bytes='");
3696 Xml.Append(Package.Bytes.ToString());
3697
3698 if (Version.HasValue)
3699 {
3700 Xml.Append("' xmlns='");
3701 Xml.Append(NamespaceSoftwareUpdates(Version.Value));
3702 }
3703
3704 Xml.Append("'/>");
3705 }
3706
3707 private async Task GetPackagesHandler(object Sender, IqEventArgs e)
3708 {
3709 StringBuilder Xml = new StringBuilder();
3710
3711 Xml.Append("<packages xmlns='");
3712 Xml.Append(e.Query.NamespaceURI);
3713 Xml.Append("'>");
3714
3715 foreach (Package Package in await GetPackages())
3716 {
3717 string FullFileName = Path.Combine(XmppServerModule.PackagesFolder, Package.FileName);
3718 if (!File.Exists(FullFileName))
3719 continue;
3720
3721 Serialize(Package, Xml, null);
3722 }
3723
3724 Xml.Append("</packages>");
3725
3726 await e.IqResult(Xml.ToString(), e.To);
3727 }
3728
3729 public static async Task<Package[]> GetPackages()
3730 {
3731 if (!packagesLoaded)
3732 {
3733 foreach (Package Package in await Database.Find<Package>("FileName"))
3734 {
3735 lock (packages)
3736 {
3737 packages[Package.FileName] = Package;
3738 }
3739 }
3740
3741 packagesLoaded = true;
3742 }
3743
3744 lock (packages)
3745 {
3746 Package[] Result = new Package[packages.Count];
3747 packages.Values.CopyTo(Result, 0);
3748 return Result;
3749 }
3750 }
3751
3752 internal async Task NewPackage(Package Package)
3753 {
3754 lock (packages)
3755 {
3756 packages[Package.FileName] = Package;
3757 }
3758
3759 string Serializer(NamespaceSet Version)
3760 {
3761 StringBuilder Xml = new StringBuilder();
3762 Serialize(Package, Xml, Version);
3763 return Xml.ToString();
3764 }
3765 ;
3766
3767 await this.Notify(Package, Serializer);
3768 await this.Notify(null, Serializer);
3769 }
3770
3771 internal async Task PackageDeleted(Package Package)
3772 {
3773 lock (packages)
3774 {
3775 packages.Remove(Package.FileName);
3776 }
3777
3778 string Serializer(NamespaceSet Version)
3779 {
3780 StringBuilder Xml = new StringBuilder();
3781
3782 Xml.Append("<packageDeleted fileName='");
3783 Xml.Append(XML.Encode(Package.FileName));
3784 Xml.Append("' xmlns='");
3785 Xml.Append(NamespaceSoftwareUpdates(Version));
3786 Xml.Append("'/>");
3787
3788 return Xml.ToString();
3789 }
3790 ;
3791
3792 await this.Notify(Package, Serializer);
3793 await this.Notify(null, Serializer);
3794 }
3795
3796 private delegate string SerializePackageDelegate(NamespaceSet Version);
3797
3798 private async Task Notify(Package Package, SerializePackageDelegate Serializer)
3799 {
3800 Dictionary<CaseInsensitiveString, bool> Sent = new Dictionary<CaseInsensitiveString, bool>();
3801
3802 foreach (PackageNotification Notification in await Database.Find<PackageNotification>(
3803 new FilterFieldEqualTo("FileName", Package?.FileName ?? "*")))
3804 {
3805 if (string.Compare(Notification.BareJid, Gateway.XmppClient.BareJID, true) == 0)
3806 continue;
3807
3808 XmppAddress To = new XmppAddress(Notification.BareJid);
3809 XmppAddress From = new XmppAddress(Notification.Domain ?? this.MainDomain.Address);
3810
3811 if (!Sent.ContainsKey(To.Address))
3812 {
3813 string Xml = Serializer(Notification.Version);
3814 await XmppServerModule.Server.SendMessage(string.Empty, string.Empty, From, To, string.Empty, Xml);
3815 Sent[To.Address] = true;
3816 }
3817 }
3818 }
3819
3820 private async Task SubscribeHandler(object Sender, IqEventArgs e)
3821 {
3822 if (!this.CheckSameDomain(e))
3823 return;
3824
3825 CaseInsensitiveString FileName = XML.Attribute(e.Query, "fileName");
3826 NamespaceSet QueryVersion = XmppServerModule.GetVersion(e.Query.NamespaceURI);
3827
3828 if (FileName == "*")
3829 {
3830 LinkedList<object> ToDelete = null;
3831 bool Found = false;
3832
3833 foreach (PackageNotification Notification in await Database.Find<PackageNotification>(new FilterFieldEqualTo("BareJid", e.From.BareJid)))
3834 {
3835 if (Notification.FileName == FileName)
3836 Found = true;
3837 else
3838 {
3839 ToDelete ??= new LinkedList<object>();
3840 ToDelete.AddLast(Notification);
3841 }
3842 }
3843
3844 if (!Found)
3845 {
3846 PackageNotification Notification = new PackageNotification()
3847 {
3848 FileName = FileName,
3849 BareJid = e.From.BareJid,
3850 Domain = e.To.Domain,
3851 Version = QueryVersion
3852 };
3853
3854 await Database.Insert(Notification);
3855 }
3856
3857 if (!(ToDelete is null))
3858 await Database.Delete(ToDelete);
3859 }
3860 else
3861 {
3862 Package Package = await this.GetPackage(FileName, e);
3863 if (Package is null)
3864 {
3865 await e.IqErrorItemNotFound(e.To, "No such software package found.", "en");
3866 return;
3867 }
3868
3869 PackageNotification Notification = await Database.FindFirstDeleteRest<PackageNotification>(new FilterAnd(
3870 new FilterFieldEqualTo("FileName", FileName),
3871 new FilterFieldEqualTo("BareJid", e.From.BareJid)));
3872
3873 if (Notification is null)
3874 {
3875 int Count = 0;
3876
3877 foreach (PackageNotification Subscription in await Database.Find<PackageNotification>(new FilterFieldEqualTo("BareJid", e.From.BareJid)))
3878 Count++;
3879
3880 if (Count > 10)
3881 {
3882 await e.IqErrorNotAllowed(e.To, "Maximum number of subscriptions reached. Either unsubscribe, or use a wildcard subscription.", "en");
3883 return;
3884 }
3885
3886 Notification = new PackageNotification()
3887 {
3888 FileName = FileName,
3889 BareJid = e.From.BareJid,
3890 Domain = e.To.Domain,
3891 Version = QueryVersion
3892 };
3893
3894 await Database.Insert(Notification);
3895 }
3896 }
3897
3898 await e.IqResult(string.Empty, e.To);
3899 }
3900
3901 private async Task UnsubscribeHandler(object Sender, IqEventArgs e)
3902 {
3903 if (!this.CheckSameDomain(e))
3904 return;
3905
3906 CaseInsensitiveString FileName = XML.Attribute(e.Query, "fileName");
3907
3908 if (FileName == "*")
3910 else
3911 {
3912 Package Package = await this.GetPackage(FileName, e);
3913 if (!(Package is null))
3914 {
3915 PackageNotification Notification = await Database.FindFirstDeleteRest<PackageNotification>(new FilterAnd(
3916 new FilterFieldEqualTo("FileName", FileName),
3917 new FilterFieldEqualTo("BareJid", e.From.BareJid)));
3918
3919 if (!(Notification is null))
3920 await Database.Delete(Notification);
3921 }
3922 }
3923
3924 await e.IqResult(string.Empty, e.To);
3925 }
3926
3927 private async Task GetSubscriptionsHandler(object Sender, IqEventArgs e)
3928 {
3929 if (!this.CheckSameDomain(e))
3930 return;
3931
3932 StringBuilder Xml = new StringBuilder();
3933
3934 Xml.Append("<subscriptions xmlns='");
3935 Xml.Append(e.Query.NamespaceURI);
3936 Xml.Append("'>");
3937
3938 foreach (PackageNotification Notification in await Database.Find<PackageNotification>(
3939 new FilterFieldEqualTo("BareJid", e.From.BareJid)))
3940 {
3941 Xml.Append("<subscription>");
3942 Xml.Append(XML.Encode(Notification.FileName));
3943 Xml.Append("</subscription>");
3944 }
3945
3946 Xml.Append("</subscriptions>");
3947
3948 await e.IqResult(Xml.ToString(), e.To);
3949 }
3950
3951 #endregion
3952
3953 }
3954}
Helps with parsing of commong data types.
Definition: CommonTypes.cs:15
static string Encode(bool x)
Encodes a Boolean for use in XML and other formats.
Definition: CommonTypes.cs:596
static bool TryParse(string s, out double Value)
Tries to decode a string encoded double.
Definition: CommonTypes.cs:48
Helps with common XML-related tasks.
Definition: XML.cs:21
static string Attribute(XmlElement E, string Name)
Gets the value of an XML attribute.
Definition: XML.cs:1062
static string Encode(string s)
Encodes a string for use in XML.
Definition: XML.cs:29
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 Informational(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs an informational event.
Definition: Log.cs:344
Static class managing the runtime environment of the IoT Gateway.
Definition: Gateway.cs:147
static DateTime ScheduleEvent(Action< object > Callback, DateTime When, object State)
Schedules a one-time event.
Definition: Gateway.cs:4253
static XmppClient XmppClient
XMPP Client connection of gateway.
Definition: Gateway.cs:4038
Base class for components.
Definition: Component.cs:17
void RegisterIqSetHandler(string LocalName, string Namespace, EventHandlerAsync< IqEventArgs > Handler, bool PublishNamespaceAsFeature)
Registers an IQ-Set handler.
Definition: Component.cs:162
CaseInsensitiveString Subdomain
Subdomain name.
Definition: Component.cs:77
void RegisterIqGetHandler(string LocalName, string Namespace, EventHandlerAsync< IqEventArgs > Handler, bool PublishNamespaceAsFeature)
Registers an IQ-Get handler.
Definition: Component.cs:150
readonly object synchObject
Synchronization object for thread-safe access to internal structures.
Definition: Component.cs:26
bool IsComponentDomain(CaseInsensitiveString Domain, bool IncludeAlternativeDomains)
Checks if a domain is the component domain, or optionally, an alternative component domain.
Definition: Component.cs:124
XmppServer Server
XMPP Server.
Definition: Component.cs:97
bool UnregisterIqGetHandler(string LocalName, string Namespace, EventHandlerAsync< IqEventArgs > Handler, bool RemoveNamespaceAsFeature)
Unregisters an IQ-Get handler.
Definition: Component.cs:250
bool UnregisterIqSetHandler(string LocalName, string Namespace, EventHandlerAsync< IqEventArgs > Handler, bool RemoveNamespaceAsFeature)
Unregisters an IQ-Set handler.
Definition: Component.cs:263
Event arguments for IQ queries.
Definition: IqEventArgs.cs:12
XmppAddress From
From address attribute
Definition: IqEventArgs.cs:93
Task IqErrorNotAcceptable(XmppAddress From, string ErrorText, string Language)
Returns a not-acceptable error.
Definition: IqEventArgs.cs:248
Task IqResult(string Xml, string From)
Returns a response to the current request.
Definition: IqEventArgs.cs:113
Task IqErrorResourceConstraint(XmppAddress From, string ErrorText, string Language)
Returns a resource-constraint error.
Definition: IqEventArgs.cs:178
Task IqErrorItemNotFound(XmppAddress From, string ErrorText, string Language)
Returns a item-not-found error.
Definition: IqEventArgs.cs:206
XmlElement Query
Query element, if found, null otherwise.
Definition: IqEventArgs.cs:70
Task IqErrorNotAllowed(XmppAddress From, string ErrorText, string Language)
Returns a not-allowed error.
Definition: IqEventArgs.cs:192
XmppAddress To
To address attribute
Definition: IqEventArgs.cs:88
async Task IqError(string ErrorType, string Xml, XmppAddress From, string ErrorText, string Language)
Returns an error response to the current request.
Definition: IqEventArgs.cs:139
Task IqErrorBadRequest(XmppAddress From, string ErrorText, string Language)
Returns a bad-request error.
Definition: IqEventArgs.cs:164
Task IqErrorForbidden(XmppAddress From, string ErrorText, string Language)
Returns a forbidden error.
Definition: IqEventArgs.cs:234
Event arguments for responses to IQ queries.
object State
State object passed to the original request.
XmppAddress From
From address attribute
XmlElement FirstElement
First child element of the Response element.
bool Ok
If the response is an OK result response (true), or an error response (false).
Contains information about one XMPP address.
Definition: XmppAddress.cs:9
override string ToString()
object.ToString()
Definition: XmppAddress.cs:190
bool IsEmpty
If the address is empty.
Definition: XmppAddress.cs:183
CaseInsensitiveString Domain
Domain
Definition: XmppAddress.cs:97
CaseInsensitiveString Address
XMPP Address
Definition: XmppAddress.cs:37
CaseInsensitiveString BareJid
Bare JID
Definition: XmppAddress.cs:45
static readonly XmppAddress Empty
Empty address.
Definition: XmppAddress.cs:31
Task< bool > SendMessage(string Type, string Id, string From, string To, string Language, string ContentXml)
Sends a Message stanza to a recipient.
Definition: XmppServer.cs:3862
static byte[] GetRandomNumbers(int NrBytes)
Generates a set of random numbers.
Definition: XmppServer.cs:679
Represents a case-insensitive string.
string Value
String-representation of the case-insensitive string. (Representation is case sensitive....
static readonly CaseInsensitiveString Empty
Empty case-insensitive string
int IndexOf(CaseInsensitiveString value, StringComparison comparisonType)
Reports the zero-based index of the first occurrence of the specified string in the current System....
static bool IsNullOrEmpty(CaseInsensitiveString value)
Indicates whether the specified string is null or an CaseInsensitiveString.Empty string.
CaseInsensitiveString Substring(int startIndex, int length)
Retrieves a substring from this instance. The substring starts at a specified character position and ...
Static interface for database persistence. In order to work, a database provider has to be assigned t...
Definition: Database.cs:21
static Task< IEnumerable< object > > FindDelete(string Collection, params string[] SortOrder)
Finds objects in a given collection and deletes them in the same atomic operation.
Definition: Database.cs:1442
static Task EndBulk()
Ends bulk-processing of data. Must be called once for every call to StartBulk.
Definition: Database.cs:2259
static string WildcardToRegex(string s, string Wildcard)
Converts a wildcard string to a regular expression string.
Definition: Database.cs:2426
static Task StartBulk()
Starts bulk-proccessing of data. Must be followed by a call to EndBulk.
Definition: Database.cs:2251
static async Task Update(object Object)
Updates an object in the database.
Definition: Database.cs:1211
static async Task Delete(object Object)
Deletes an object in the database.
Definition: Database.cs:1291
static Task< IEnumerable< object > > Find(string Collection, params string[] SortOrder)
Finds objects in a given collection.
Definition: Database.cs:238
static async Task Insert(object Object)
Inserts an object into the default collection of the database.
Definition: Database.cs:97
static Task< object > TryLoadObject(string CollectionName, object ObjectId)
Tries to load an object given its Object ID ObjectId and its collection name CollectionName .
Definition: Database.cs:1838
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.
This filter selects objects that have a named field matching a given regular expression.
Implements an in-memory cache.
Definition: Cache.cs:17
bool ContainsKey(KeyType Key)
Checks if a key is available in the cache.
Definition: Cache.cs:404
void Dispose()
IDisposable.Dispose
Definition: Cache.cs:99
bool Remove(KeyType Key)
Removes an item from the cache.
Definition: Cache.cs:616
bool TryGetValue(KeyType Key, out ValueType Value)
Tries to get a value from the cache.
Definition: Cache.cs:311
void Add(KeyType Key, ValueType Value)
Adds an item to the cache.
Definition: Cache.cs:446
void Clear()
Clears the cache.
Definition: Cache.cs:679
Event arguments for cache item removal events.
ValueType Value
Value of item that was removed.
A chunked list is a linked list of chunks of objects of type T .
Definition: ChunkedList.cs:54
void Clear()
Clears the collection.
Definition: ChunkedList.cs:306
void Add(T Item)
Adds an item to the collection.
Definition: ChunkedList.cs:272
T[] ToArray()
Returns an array containing all elements of the collection.
Contains information about a position in a geo-spatial coordinate system.
Definition: GeoPosition.cs:17
const string NamespaceProvisioningOwnerIeeeV1
urn:ieee:iot:prov:o:1.0
const string NamespaceProvisioningTokenNeuroFoundationV1
urn:nf:iot:prov:t:1.0
static string NamespaceProvisioningOwner(NamespaceSet Version)
Returns the namespace for owner provisioning.
static string NamespaceSoftwareUpdates(NamespaceSet Version)
Returns the namespace for Software Updates.
static string NamespaceProvisioningToken(NamespaceSet Version)
Returns the namespace for token provisioning.
bool TryGetCertificate(string Token, out X509Certificate2 Certificate)
Tries to get a certificate for a given token.
const string NamespaceIoTDiscoveryNeuroFoundationV1
urn:nf:iot:disco:1.0
const string NamespaceProvisioningTokenIeeeV1
urn:ieee:iot:prov:t:1.0
const string NamespaceProvisioningDeviceIeeeV1
urn:ieee:iot:prov:d:1.0
override bool SupportsAccounts
If the component supports accounts (true), or if the subdomain name is the only valid address.
static string NamespaceIoTDiscovery(NamespaceSet Version)
Returns the namespace for IoT Discovery.
static string NamespaceProvisioningDevice(NamespaceSet Version)
Returns the namespace for device provisioning.
async Task ClearCache(CaseInsensitiveString BareJid, string NodeId, string SourceId, string Partition, NamespaceSet Version)
Notifies the entity its rule cache should be cleared.
async Task RecommendBefriend(CaseInsensitiveString BareJid1, CaseInsensitiveString BareJid2, NamespaceSet Version)
Notifies the entity identified by BareJid1 , that it should befriend (subscribe to presence from) the...
async Task RecommendUnfriend(CaseInsensitiveString BareJid1, CaseInsensitiveString BareJid2, NamespaceSet Version)
Notifies the entity identified by BareJid1 , that it should remove friendship (presence subscription ...
const string NamespaceProvisioningOwnerNeuroFoundationV1
urn:nf:iot:prov:o:1.0
async Task ClearCache(CaseInsensitiveString BareJid, IThingReference[] ThingReferences, NamespaceSet Version)
Notifies the entity its rule cache should be cleared.
ProvisioningComponent(XmppServer Server, CaseInsensitiveString Subdomain, string Name, GeoSpatialComponent Geo)
Provisioning and registry service component.
const string NamespaceProvisioningDeviceNeuroFoundationV1
urn:nf:iot:prov:d:1.0
Guid ObjectId
Persisted object ID. Is null if object not persisted.
Definition: Registration.cs:43
string GeoId
The ID of the geo-spatial object.
string Partition
Optional partition in which the Node ID is unique.
Definition: Registration.cs:79
string SourceId
Optional ID of source containing node.
Definition: Registration.cs:70
DateTime FirstRegistration
Timestamp of first registration.
bool HasGeoLocation
If the object has a geo-spatial location.
long NrSuccessfulDisownments
Number of successful disownments.
Controls if a device with a remote JID is allowed to control a device with JID.
Definition: ControlRule.cs:11
Controls if a device with a remote JID is allowed to subscribe to the presence of a device with JID.
Controls if a device with a remote JID is allowed to read data from a device with JID.
Definition: ReadoutRule.cs:11
Abstract base class for rules.
Definition: Rule.cs:12
void AddChildRule(Rule Rule)
Adds a child rule.
Definition: Rule.cs:63
virtual ? bool Evaluate(Context Context)
Tries to evaluate the rule.
Definition: Rule.cs:42
Filters things with a named numeric-valued tag equal to a given value.
Filters things with a named numeric-valued tag greater than a given value.
Filters things with a named numeric-valued tag greater than or equal to a given value.
Filters things with a named numeric-valued tag within a given range.
Filters things with a named numeric-valued tag lesser than a given value.
Filters things with a named numeric-valued tag lesser than or equal to a given value.
Filters things with a named numeric-valued tag not equal to a given value.
Filters things with a named numeric-valued tag outside a given range.
abstract void AddValueFilters(ChunkedList< Filter > Filters)
Adds search filters for comparing the value of the tag using the operator.
abstract bool AppliesTo(MetaDataTag Tag)
Checks if the operator applies to a tag.
Filters things with a named string-valued tag equal to a given value.
Filters things with a named string-valued tag greater than a given value.
Filters things with a named string-valued tag greater than or equal to a given value.
Filters things with a named string-valued tag within a given range.
Filters things with a named string-valued tag lesser than a given value.
Filters things with a named string-valued tag lesser than or equal to a given value.
Filters things with a named string-valued tag like a given value.
Filters things with a named string-valued tag not equal to a given value.
Filters things with a named string-valued tag outside a given range.
Filters things with a named string-valued tag matching a regular expression.
Contains information about a software package.
Definition: Package.cs:21
byte[] Signature
Cryptographic signature of package, as calculated by the issuer of the package.
Definition: Package.cs:49
CaseInsensitiveString FileName
Filename of package.
Definition: Package.cs:43
DateTime Published
When package was published.
Definition: Package.cs:79
DateTime Supersedes
Timestamp of superceded package.
Definition: Package.cs:85
DateTime Created
When package record was created
Definition: Package.cs:91
override string StringValue
String-representation of meta-data tag value.
Abstract base class for all meta-data tags.
Definition: MetaDataTag.cs:16
abstract bool IsEmpty
If the tag value is empty.
Definition: MetaDataTag.cs:92
abstract string StringValue
String-representation of meta-data tag value.
Definition: MetaDataTag.cs:82
abstract object Value
Meta-data tag value.
Definition: MetaDataTag.cs:87
Service Module hosting the XMPP broker and its components.
static NamespaceSet GetVersion(string Namespace)
Gets the namespace set version corresponding to a given a namespace.
Base class for all sensor data fields.
Definition: Field.cs:20
Contains a reference to a thing
static ThingReference Empty
Empty thing reference. Can be used by sensors that are not part of a concentrator during readout.
string NodeId
ID of node.
string Partition
Optional partition in which the Node ID is unique.
bool IsEmpty
If the reference is an empty reference.
string SourceId
Optional ID of source containing node.
Interface for thing references.
string Partition
Optional partition in which the Node ID is unique.
string SourceId
Optional ID of source containing node.
Definition: ImplTypes.g.cs:58
class Names(Vector NamesVector)
Contains a collection of distinguished names.
Definition: Names.cs:9
RuleRange
Range of a rule change
Definition: RuleChange.cs:7
NamespaceSet
Namespace versions
Definition: NamespaceSet.cs:7
FieldType
Field Type flags
Definition: FieldType.cs:10