Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
NeuroFeaturesProcessor.cs
1using SkiaSharp;
2using System;
3using System.Collections.Generic;
4using System.IO;
5using System.Text;
6using System.Text.RegularExpressions;
7using System.Threading.Tasks;
8using System.Xml;
9using Waher.Content;
16using Waher.Events;
25using Waher.Script;
27using Waher.Security;
38
40{
44 public static class NeuroFeaturesProcessor
45 {
49 public const string NeuroFeaturesNamespace = "https://paiwise.tagroot.io/Schema/NeuroFeatures.xsd";
50
51 private static readonly Cache<CaseInsensitiveString, Token> tokenCache =
52 new Cache<CaseInsensitiveString, Token>(int.MaxValue, TimeSpan.MaxValue, TimeSpan.FromHours(1));
53
54 #region Smart Contract interface
55
56 #region General
57
68 internal static async Task ContractSigned(Contract Contract, bool ContractIsLocked,
69 Dictionary<CaseInsensitiveString, Parameter> TransientParameters,
71 {
72 if (Contract?.ForMachines is null || Contract.ForMachinesNamespace != NeuroFeaturesNamespace)
73 return;
74
75 if ((TransientParameters?.Count ?? 0) > 0)
76 {
77 await RejectContract(Contract, ContractIsLocked,
78 "Transient parameters not permitted for transparency reasons.",
79 true, Legal, EDaler);
80 return;
81 }
82
83 switch (Contract.ForMachinesLocalName)
84 {
85 case "Create":
86 if (!OnlyMissingTrustProvider(Contract, out string TrustProviderRole, out XmlDocument ForMachines))
87 return;
88
89 await CreateTokens(ForMachines.DocumentElement, Contract, ContractIsLocked, TrustProviderRole, Legal, EDaler);
90 break;
91
92 case "Destroy":
93 if (!OnlyMissingTrustProvider(Contract, out TrustProviderRole, out ForMachines))
94 return;
95
96 await DestroyTokens(ForMachines.DocumentElement, Contract, ContractIsLocked, TrustProviderRole, Legal, EDaler);
97 break;
98
99 case "Transfer":
100 if (!OnlyMissingTrustProvider(Contract, out TrustProviderRole, out ForMachines))
101 return;
102
103 await TransferTokens(ForMachines.DocumentElement, Contract, ContractIsLocked, TrustProviderRole, Legal, EDaler);
104 break;
105
106 default:
107 await RejectContract(Contract, ContractIsLocked, "Rejected Neuro-Feature contract: Unrecognized element: " + Contract.ForMachinesLocalName, true, Legal, EDaler);
108 break;
109 }
110 }
111
112 internal static Task RejectContract(Contract Contract, bool ContractIsLocked, string Reason, bool FailContract, LegalComponent Legal, EDalerComponent EDaler)
113 {
114 return RejectContract(Contract, ContractIsLocked, Reason, FailContract, Legal, EDaler, string.Empty);
115 }
116
117 internal static async Task RejectContract(Contract Contract, bool ContractIsLocked, string Reason, bool FailContract,
118 LegalComponent Legal, EDalerComponent EDaler, string EventId, params KeyValuePair<string, object>[] Tags)
119 {
120 Log.Error(Reason, Contract.ContractId.Value, string.Empty, EventId, Tags);
121
122 if (FailContract)
123 {
125
126 if (ContractIsLocked)
127 Semaphore = null;
128 else
130
131 try
132 {
133 Contract.State = ContractState.Failed;
134 Contract.Sign(Legal);
135
136 await Database.Update(Contract);
137 await Legal.SendContractUpdatedEvent(Contract, false);
138 }
139 finally
140 {
142 }
143 }
144
145 Dictionary<CaseInsensitiveString, bool> SendTo = new Dictionary<CaseInsensitiveString, bool>();
146
147 foreach (ClientSignature Signature in Contract.ClientSignatures)
148 SendTo[Signature.BareJid] = true;
149
150 foreach (CaseInsensitiveString Jid in SendTo.Keys)
151 {
152 await EDaler.Server.SendMessage("chat", string.Empty, EDaler.MainDomain.Address, Jid, "en",
153 "<body>" + XML.Encode(Reason) + "</body>");
154 }
155 }
156
157 private static bool OnlyMissingTrustProvider(Contract Contract, out string TrustProviderRole, out XmlDocument ForMachines)
158 {
159 TrustProviderRole = null;
160 ForMachines = null;
161
162 if (Contract.State != ContractState.BeingSigned || Contract.ClientSignatures is null)
163 return false;
164
165 if ((Contract.Roles?.Length ?? 0) < 2)
166 return false;
167
168 string CreatorRole = null;
169 string OwnerRole = null;
170 CaseInsensitiveString TrustProviderLegalId = null;
171
172 ForMachines = new XmlDocument()
173 {
174 PreserveWhitespace = true
175 };
176 ForMachines.LoadXml(Contract.ForMachines);
177
178 foreach (XmlElement E in ForMachines.DocumentElement.ChildNodes)
179 {
180 if (E.NamespaceURI != Contract.ForMachinesNamespace)
181 continue;
182
183 switch (E.LocalName)
184 {
185 case "TrustProvider":
186 TrustProviderLegalId = PaiwiseProcessor.GetLegalId(E, Contract, out TrustProviderRole);
187 break;
188
189 case "Creator":
190 if (CaseInsensitiveString.IsNullOrEmpty(PaiwiseProcessor.GetLegalId(E, Contract, out CreatorRole)))
191 return false;
192
193 break;
194
195 case "Owner":
196 PaiwiseProcessor.GetLegalId(E, Contract, out OwnerRole);
197 break;
198 }
199 }
200
201 if (string.IsNullOrEmpty(TrustProviderRole) || !string.IsNullOrEmpty(TrustProviderLegalId))
202 return false;
203
204 if (string.IsNullOrEmpty(OwnerRole))
205 OwnerRole = CreatorRole;
206
207 Dictionary<string, SignatureStat> Signatures = new Dictionary<string, SignatureStat>();
208
209 foreach (Role Role in Contract.Roles)
210 {
211 if (Role.CanRevoke)
212 return false;
213
214 if (Role.Name == TrustProviderRole)
215 {
216 if (Role.MinCount != 1 || Role.MaxCount != 1)
217 return false;
218 }
219 else
220 {
221 if (Role.Name == CreatorRole || Role.Name == OwnerRole)
222 {
223 if (Role.MinCount != 1 || Role.MaxCount != 1)
224 return false;
225 }
226
227 Signatures[Role.Name] = new SignatureStat()
228 {
229 Min = Role.MinCount,
230 Max = Role.MaxCount,
231 Count = 0
232 };
233 }
234 }
235
236 foreach (ClientSignature Signature in Contract.ClientSignatures)
237 {
238 if (Signature.Role == TrustProviderRole)
239 return false;
240
241 if (Signatures.TryGetValue(Signature.Role, out SignatureStat Stat))
242 Stat.Count++;
243 else
244 return false;
245 }
246
247 foreach (SignatureStat Stat in Signatures.Values)
248 {
249 if (Stat.Count < Stat.Min || Stat.Count > Stat.Max)
250 return false;
251 }
252
253 if (!(Contract.Parts is null))
254 {
255 foreach (Part Part in Contract.Parts)
256 {
257 if (Part.Role == TrustProviderRole && !LegalIdentityConfiguration.IsMeApproved(Part.LegalId))
258 {
259 // TODO: Forward contract in message to intended auctioneer
260 return false;
261 }
262 }
263 }
264
265 return true;
266 }
267
268 private class SignatureStat
269 {
270 public int Min;
271 public int Max;
272 public int Count;
273 }
274
275 private class TokenIDReference
276 {
277 public Guid TokenId;
278 public TokenIdMethod Method;
279 public Token Token;
280 public StateMachine Machine;
281 public int ShortIdLength;
282 public string ShortIdAlphabet;
283 }
284
285 private static async Task<TokenIDReference> GetTokenId(XmlElement Value, Contract Contract, bool ContractIsLocked, LegalComponent Legal, EDalerComponent EDaler)
286 {
287 int ShortIdLength = XML.Attribute(Value, "shortIdLength", 0);
288 string ShortIdAlphabet = XML.Attribute(Value, "shortIdAlphabet", "0123456789");
289
290 foreach (XmlNode N in Value.ChildNodes)
291 {
292 if (!(N is XmlElement E))
293 continue;
294
295 switch (E.LocalName)
296 {
297 case "Random":
298 return new TokenIDReference()
299 {
300 TokenId = Guid.NewGuid(),
301 Method = TokenIdMethod.ByTrustProvider,
302 ShortIdLength = ShortIdLength,
303 ShortIdAlphabet = ShortIdAlphabet
304 };
305
306 case "Unique":
307 return new TokenIDReference()
308 {
309 TokenId = Guid.Empty,
310 Method = TokenIdMethod.Unique,
311 ShortIdLength = ShortIdLength,
312 ShortIdAlphabet = ShortIdAlphabet
313 };
314
315 default:
316 if (!(await PaiwiseProcessor.GetParameterValue(Value, Contract) is string s))
317 {
318 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Invalid Token ID.", true, Legal, EDaler);
319 return null;
320 }
321
322 if (s != s.ToLower())
323 {
324 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Token IDs must be lower-case.", true, Legal, EDaler);
325 return null;
326 }
327
328 if (!Guid.TryParse(s, out Guid TokenGuid))
329 {
330 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Token IDs must be GUIDs.", true, Legal, EDaler);
331 return null;
332 }
333
334 return new TokenIDReference()
335 {
336 TokenId = TokenGuid,
337 Method = TokenIdMethod.ByCreator,
338 ShortIdLength = ShortIdLength,
339 ShortIdAlphabet = ShortIdAlphabet
340 };
341 }
342 }
343
344 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Undefined Token ID.", true, Legal, EDaler);
345 return null;
346 }
347
348 internal static async Task<Token> GetToken(CaseInsensitiveString TokenId, bool OnlyIfLocal)
349 {
351 return null;
352
353 if (tokenCache.TryGetValue(TokenId, out Token Result))
354 return Result;
355
356 int i = TokenId.IndexOf('@');
357 if (i < 0)
358 Result = await Database.FindFirstIgnoreRest<Token>(new FilterFieldEqualTo("ShortId", TokenId));
359 else
360 {
361 CaseInsensitiveString Domain = TokenId.Substring(i + 1);
362
363 if (eDaler.IsComponentDomain(Domain, true))
364 Result = await Database.FindFirstIgnoreRest<Token>(new FilterFieldEqualTo("TokenId", TokenId));
365 else
366 {
367 if (OnlyIfLocal)
368 return null;
369
370 StringBuilder Xml = new StringBuilder();
371
372 Xml.Append("<token id='");
373 Xml.Append(XML.Encode(TokenId.Value));
374 Xml.Append("' xmlns='");
375 Xml.Append(NeuroFeaturesNamespace);
376 Xml.Append("'/>");
377
378 IqResultEventArgs e = await eDaler.Server.IqRequest("get", legal.MainDomain.Address, Domain, string.Empty, Xml.ToString());
379 if (!e.Ok || e.FirstElement is null || !Token.TryParse(e.FirstElement, out Result))
380 Result = null;
381 }
382 }
383
384 if (!(Result is null))
385 tokenCache[TokenId] = Result;
386
387 return Result;
388 }
389
390 internal static async Task<(int, int, int)> DeleteExpiredTokens()
391 {
392 int NrTokens = 0;
393 int NrEvents = 0;
394 int NrTags = 0;
395
396 IEnumerable<Token> Deleted = await Database.FindDelete<Token>(
397 new FilterFieldLesserThan("Expires", DateTime.Today.AddDays(-1)));
398
399 foreach (Token Token in Deleted)
400 {
401 tokenCache.Remove(Token.TokenId);
403 tokenCache.Remove(Token.ShortId);
404
405 NrTokens++;
406
407 IEnumerable<TokenEvent> Events = await Database.FindDelete<TokenEvent>(
408 new FilterFieldEqualTo("TokenId", Token.TokenId));
409
410 foreach (TokenEvent Event in Events)
411 NrEvents++;
412
413 IEnumerable<TokenTag> Tags = await Database.FindDelete<TokenTag>(
414 new FilterFieldEqualTo("TokenId", Token.TokenId));
415
416 foreach (TokenTag Tag in Tags)
417 NrTags++;
418 }
419
420 return (NrTokens, NrEvents, NrTags);
421 }
422
423 #endregion
424
425 #region Create Contract
426
430 private enum CommissionPaidBy
431 {
435 Creator,
436
440 Owner
441 }
442
443 private static async Task<bool> CreateTokens(XmlElement Create, Contract Contract, bool ContractIsLocked, string TrustProviderRole, LegalComponent Legal,
445 {
446 Dictionary<Guid, TokenIDReference> ProposedTokenIDs = new Dictionary<Guid, TokenIDReference>();
447 List<TokenIDReference> AllTokenIDs = new List<TokenIDReference>();
448 SortedDictionary<CaseInsensitiveString, TokenTagReference> TagReferences = null;
449 CaseInsensitiveString[] ValuatorIds = null;
450 CaseInsensitiveString[] AssessorIds = null;
451 CaseInsensitiveString[] CertifierIds = null;
452 CaseInsensitiveString[] WitnessIds = null;
453 CaseInsensitiveString CreatorId = null;
454 CaseInsensitiveString OwnerId = null;
455 CaseInsensitiveString TrustProviderLegalId = null;
456 string Currency = null;
457 string Reference = null;
458 string FriendlyName = null;
459 string Category = null;
460 string Description = null;
461 string GlyphContentType = null;
462 byte[] Glyph = null;
463 XmlElement Definition = null;
464 decimal? Value = null;
465 decimal? CommissionPercent = null;
466 CommissionPaidBy CommissionPaidBy = CommissionPaidBy.Owner;
467 int? GlyphWidth = null;
468 int? GlyphHeight = null;
469 DateTime Expires = DateTime.MaxValue;
470 bool CreatorCanDestroy = false;
471 bool OwnerCanDestroyBatch = false;
472 bool OwnerCanDestroyIndividual = false;
473 bool CertifierCanDestroy = false;
474
475 foreach (XmlElement E in Create.ChildNodes)
476 {
477 if (E.NamespaceURI == Contract.ForMachinesNamespace)
478 {
479 switch (E.LocalName)
480 {
481 case "TokenID":
482 TokenIDReference TokenRef = await GetTokenId(E, Contract, ContractIsLocked, Legal, EDaler);
483 if (TokenRef is null)
484 return false;
485
486 AllTokenIDs.Add(TokenRef);
487
488 if (TokenRef.Method != TokenIdMethod.Unique)
489 {
490 if (ProposedTokenIDs.ContainsKey(TokenRef.TokenId))
491 {
492 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Duplicate Token IDs used.", true, Legal, EDaler);
493 return false;
494 }
495 else
496 ProposedTokenIDs[TokenRef.TokenId] = TokenRef;
497 }
498 break;
499
500 case "Creator":
501 CreatorId = PaiwiseProcessor.GetLegalId(E, Contract);
502 break;
503
504 case "Owner":
505 OwnerId = PaiwiseProcessor.GetLegalId(E, Contract);
506 break;
507
508 case "Valuator":
509 ValuatorIds = PaiwiseProcessor.GetLegalIds(E, Contract);
510 break;
511
512 case "Assessor":
513 AssessorIds = PaiwiseProcessor.GetLegalIds(E, Contract);
514 break;
515
516 case "Certifier":
517 CertifierIds = PaiwiseProcessor.GetLegalIds(E, Contract);
518 break;
519
520 case "Witness":
521 WitnessIds = PaiwiseProcessor.GetLegalIds(E, Contract);
522 break;
523
524 case "TrustProvider":
525 TrustProviderLegalId = PaiwiseProcessor.GetLegalId(E, Contract, out TrustProviderRole);
526 break;
527
528 case "Value":
529 if (!(await PaiwiseProcessor.GetParameterValue(E, Contract) is decimal d))
530 {
531 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Invalid value.", true, Legal, EDaler);
532 return false;
533 }
534
535 Value = d;
536 break;
537
538 case "Currency":
539 if (!(await PaiwiseProcessor.GetParameterValue(E, Contract) is string s2))
540 {
541 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Invalid currency.", true, Legal, EDaler);
542 return false;
543 }
544
545 Currency = s2;
546 break;
547
548 case "CommissionPercent":
549 if (!(await PaiwiseProcessor.GetParameterValue(E, Contract) is decimal d2) || d2 < 0 || d2 > 100)
550 {
551 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Invalid commission (%).", true, Legal, EDaler);
552 return false;
553 }
554
555 CommissionPercent = d2;
556 break;
557
558 case "CommissionPaidBy":
559 if (!Enum.TryParse(E.InnerText, out CommissionPaidBy CommissionPaidBy2))
560 {
561 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Invalid commission payer.", true, Legal, EDaler);
562 return false;
563 }
564
565 CommissionPaidBy = CommissionPaidBy2;
566 break;
567
568 case "Expires":
569 if (!(await PaiwiseProcessor.GetParameterValue(E, Contract) is DateTime TP) || (Expires = TP.ToUniversalTime()).Date <= DateTime.UtcNow.Date)
570 {
571 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Invalid expiry date.", true, Legal, EDaler);
572 return false;
573 }
574 break;
575
576 case "CreatorCanDestroy":
577 if (!(await PaiwiseProcessor.GetParameterValue(E, Contract) is bool b))
578 {
579 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Invalid CreatorCanDestroy property value.", true, Legal, EDaler);
580 return false;
581 }
582
583 CreatorCanDestroy = b;
584 break;
585
586 case "OwnerCanDestroyBatch":
587 if (!(await PaiwiseProcessor.GetParameterValue(E, Contract) is bool b2))
588 {
589 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Invalid OwnerCanDestroyBatch property value.", true, Legal, EDaler);
590 return false;
591 }
592
593 OwnerCanDestroyBatch = b2;
594 break;
595
596 case "OwnerCanDestroyIndividual":
597 if (!(await PaiwiseProcessor.GetParameterValue(E, Contract) is bool b4))
598 {
599 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Invalid OwnerCanDestroyIndividual property value.", true, Legal, EDaler);
600 return false;
601 }
602
603 OwnerCanDestroyIndividual = b4;
604 break;
605
606 case "CertifierCanDestroy":
607 if (!(await PaiwiseProcessor.GetParameterValue(E, Contract) is bool b3))
608 {
609 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Invalid CertifierCanDestroy property value.", true, Legal, EDaler);
610 return false;
611 }
612
613 CertifierCanDestroy = b3;
614 break;
615
616 case "Reference":
617 object Ref = await PaiwiseProcessor.GetParameterValue(E, Contract);
618
619 if (Ref is string s3)
620 Reference = s3;
621 else if (Ref is CaseInsensitiveString cis)
622 Reference = cis;
623 else
624 {
625 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Invalid reference.", true, Legal, EDaler);
626 return false;
627 }
628 break;
629
630 case "Definition":
631 foreach (XmlNode N in E.ChildNodes)
632 {
633 if (N is XmlElement E2)
634 {
635 if (Definition is null)
636 Definition = E2;
637 else
638 {
639 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Definition can only be one element.", true, Legal, EDaler);
640 return false;
641 }
642 }
643 }
644 break;
645
646 case "Tag":
647 if (TagReferences is null)
648 TagReferences = new SortedDictionary<CaseInsensitiveString, TokenTagReference>();
649
650 string TagName = XML.Attribute(E, "name");
651
652 if (TagReferences.ContainsKey(TagName))
653 {
654 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Duplicated tag name.", true, Legal, EDaler);
655 return false;
656 }
657
658 object TagValue = await PaiwiseProcessor.GetParameterValue(E, Contract);
659
660 if (TagValue is decimal d3)
661 TagReferences[TagName] = new NumberTokenTagReference(TagName, d3);
662 else if (TagValue is string s)
663 TagReferences[TagName] = new StringTokenTagReference(TagName, s);
664 else if (TagValue is CaseInsensitiveString cis2)
665 TagReferences[TagName] = new StringTokenTagReference(TagName, cis2.Value);
666 else if (TagValue is bool b5)
667 TagReferences[TagName] = new BooleanTokenTagReference(TagName, b5);
668 else if (TagValue is DateTime TP2)
669 TagReferences[TagName] = new DateTimeTokenTagReference(TagName, TP2);
670 else if (TagValue is TimeSpan TS)
671 TagReferences[TagName] = new TimeSpanTokenTagReference(TagName, TS);
672 else if (TagValue is Duration Dr)
673 TagReferences[TagName] = new DurationTokenTagReference(TagName, Dr);
674 else if (TagValue is byte[] Bin)
675 TagReferences[TagName] = new BinaryTokenTagReference(TagName, Bin);
676 else if (TagValue is null)
677 {
678 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Unable to evaluate tag " + TagName + ".", true, Legal, EDaler);
679 return false;
680 }
681 else
682 {
683 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Unrecognized value for tag " + TagName + ".", true, Legal, EDaler);
684 return false;
685 }
686 break;
687
688 case "FriendlyName":
689 if (!(await PaiwiseProcessor.GetParameterValue(E, Contract) is string s4))
690 {
691 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Invalid friendly name.", true, Legal, EDaler);
692 return false;
693 }
694
695 FriendlyName = Category = Description = s4;
696 break;
697
698 case "Category":
699 if (!(await PaiwiseProcessor.GetParameterValue(E, Contract) is string s5))
700 {
701 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Invalid category.", true, Legal, EDaler);
702 return false;
703 }
704
705 Category = s5;
706 break;
707
708 case "Description":
709 if (!(await PaiwiseProcessor.GetParameterValue(E, Contract) is string s6))
710 {
711 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Invalid description.", true, Legal, EDaler);
712 return false;
713 }
714
715 Description = s6;
716 break;
717
718 case "Glyph":
719 try
720 {
721 GlyphContentType = XML.Attribute(E, "contentType");
722 if (!GlyphContentType.StartsWith("image/"))
723 {
724 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Invalid glyph content type.", true, Legal, EDaler);
725 return false;
726 }
727
728 Glyph = Convert.FromBase64String(E.InnerText);
729
730 object Decoded = await InternetContent.DecodeAsync(GlyphContentType, Glyph, null);
731 if (!(Decoded is SKImage Image))
732 {
733 if (Decoded is IDisposable Disposable)
734 Disposable.Dispose();
735
736 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Glyph not an image.", true, Legal, EDaler);
737 return false;
738 }
739
740 GlyphWidth = Image.Width;
741 GlyphHeight = Image.Height;
742
743 Image.Dispose();
744 }
745 catch (Exception)
746 {
747 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Invalid glyph.", true, Legal, EDaler);
748 return false;
749 }
750 break;
751 }
752 }
753 }
754
755 if (AllTokenIDs.Count == 0)
756 {
757 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: No Token IDs.", true, Legal, EDaler);
758 return false;
759 }
760
761 if (Definition is null)
762 {
763 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Definition missing.", true, Legal, EDaler);
764 return false;
765 }
766
767 if (!Contract.TryGetSchemaReference(Definition.NamespaceURI, out SchemaReference DefinitionSchema))
768 {
769 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Definition schema not found.", true, Legal, EDaler);
770 return false;
771 }
772
773 if (string.IsNullOrEmpty(CreatorId))
774 {
775 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Missing Creator ID.", true, Legal, EDaler);
776 return false;
777 }
778
779 if (!Value.HasValue || Value.Value <= 0)
780 {
781 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Value must be positive.", true, Legal, EDaler);
782 return false;
783 }
784
785 if (string.IsNullOrEmpty(Currency) || Currency.Length != 3)
786 {
787 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Invalid currency.", true, Legal, EDaler);
788 return false;
789 }
790
791 if (string.IsNullOrEmpty(TrustProviderRole))
792 {
793 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Trust Provider role not defined.", true, Legal, EDaler);
794 return false;
795 }
796
797 if (!string.IsNullOrEmpty(TrustProviderLegalId) &&
798 !LegalIdentityConfiguration.IsMeApproved(TrustProviderLegalId))
799 {
800 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Trust Provider ID does not represent the current server.", true, Legal, EDaler);
801 return false;
802 }
803
804 if (!CommissionPercent.HasValue)
805 {
806 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Commission missing.", true, Legal, EDaler);
807 return false;
808 }
809
810 if (CommissionPercent.Value < await MarketplaceProcessor.GetMinCommission())
811 {
812 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Commission too low.", true, Legal, EDaler);
813 return false;
814 }
815
816 if (Expires.Date <= DateTime.UtcNow.Date)
817 {
818 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Must expire on a future date, if to expire.", true, Legal, EDaler);
819 return false;
820 }
821
822 if (!Contract.Duration.HasValue ||
823 DateTime.UtcNow.Date + Contract.Duration.Value < Expires.Date.AddDays(1))
824 {
825 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Duration must cover expiry date.", true, Legal, EDaler);
826 return false;
827 }
828
829 if (string.IsNullOrEmpty(FriendlyName))
830 {
831 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: No friendly name provided.", true, Legal, EDaler);
832 return false;
833 }
834
835 if (string.IsNullOrEmpty(Category))
836 {
837 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: No category provided.", true, Legal, EDaler);
838 return false;
839 }
840
841 if (string.IsNullOrEmpty(Description))
842 {
843 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: No description provided.", true, Legal, EDaler);
844 return false;
845 }
846
847 if (Glyph is null || string.IsNullOrEmpty(GlyphContentType) ||
848 !GlyphWidth.HasValue || !GlyphHeight.HasValue || GlyphWidth <= 0 || GlyphHeight <= 0)
849 {
850 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: A valid glyph has not been provided.", true, Legal, EDaler);
851 return false;
852 }
853
854 if (OwnerId is null)
855 OwnerId = CreatorId;
856
857 CaseInsensitiveString OwnerJid = null;
858 CaseInsensitiveString CreatorJid = null;
859 CaseInsensitiveString[] CertifierJids = CertifierIds is null ? null : new CaseInsensitiveString[CertifierIds.Length];
860
861 foreach (ClientSignature Signature in Contract.ClientSignatures)
862 {
863 if (Signature.LegalId == OwnerId)
864 OwnerJid = Signature.BareJid;
865
866 if (Signature.LegalId == CreatorId)
867 CreatorJid = Signature.BareJid;
868
869 if (!(CertifierIds is null))
870 {
871 int i, c = CertifierIds.Length;
872
873 for (i = 0; i < c; i++)
874 {
875 if (Signature.LegalId == CertifierIds[i])
876 CertifierJids[i] = Signature.BareJid;
877 }
878 }
879 }
880
882 {
883 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Owner JID not accessible.", true, Legal, EDaler);
884 return false;
885 }
886
887 if (CaseInsensitiveString.IsNullOrEmpty(CreatorJid))
888 {
889 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Creator JID not accessible.", true, Legal, EDaler);
890 return false;
891 }
892
893 if (!(CertifierIds is null))
894 {
895 int i, c = CertifierIds.Length;
896
897 for (i = 0; i < c; i++)
898 {
899 if (CaseInsensitiveString.IsNullOrEmpty(CertifierJids[i]))
900 {
901 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Certifier JID not accessible.", true, Legal, EDaler);
902 return false;
903 }
904 }
905 }
906
907 List<Token> Tokens = new List<Token>();
908 Dictionary<StateMachine, Token> StateMachines = null;
909
910 DateTime Created = DateTime.UtcNow;
911 StringBuilder sb = new StringBuilder();
912
914
915 string NormalizedDefinition = sb.ToString();
916 StateMachine SingletonStateMachine = null;
917 Contract SingletonStateMachineContract = null;
918 int Ordinal = 0;
919
920 foreach (TokenIDReference TokenID in AllTokenIDs)
921 {
922 TokenTagReference[] TagReferences2;
923
924 if (TagReferences is null)
925 TagReferences2 = null;
926 else
927 {
928 TagReferences2 = new TokenTagReference[TagReferences.Count];
929 TagReferences.Values.CopyTo(TagReferences2, 0);
930 }
931
932 Token Token = new Token()
933 {
934 TokenIdMethod = TokenID.Method,
935 TokenId = TokenID.TokenId.ToString() + "@" + EDaler.MainDomain.Address,
936 Ordinal = ++Ordinal,
937 BatchSize = AllTokenIDs.Count,
938 Visibility = Contract.Visibility,
939 Creator = CreatorId,
940 CreatorJid = CreatorJid,
941 Owner = OwnerId,
942 OwnerJid = OwnerJid,
944 Updated = Created,
945 Definition = NormalizedDefinition,
946 DefinitionNamespace = Definition.NamespaceURI,
947 DefinitionSchemaDigest = DefinitionSchema.Digest,
948 DefinitionSchemaHashFunction = DefinitionSchema.Algorithm,
949 Tags = TagReferences2,
950 Expires = Expires,
951 CreatorCanDestroy = CreatorCanDestroy,
952 OwnerCanDestroyBatch = OwnerCanDestroyBatch,
953 OwnerCanDestroyIndividual = OwnerCanDestroyIndividual,
954 CertifierCanDestroy = CertifierCanDestroy,
955 ArchiveRequired = Contract.ArchiveRequired,
956 ArchiveOptional = Contract.ArchiveOptional,
957 Value = Value.Value,
958 Currency = Currency,
959 CreationContract = Contract.ContractId,
960 OwnershipContract = Contract.ContractId,
961 Reference = Reference,
962 Valuator = ValuatorIds,
963 Assessor = AssessorIds,
964 Certifier = CertifierIds,
965 CertifierJids = CertifierJids,
966 Witness = WitnessIds,
967 TrustProvider = await Gateway.ContractsClient.GetLatestApprovedLegalId(),
968 TrustProviderJid = Gateway.ContractsClient.Client.BareJID,
969 FriendlyName = FriendlyName,
970 Category = Category,
971 Description = Description,
972 Glyph = Glyph,
973 GlyphContentType = GlyphContentType,
974 GlyphWidth = GlyphWidth.Value,
975 GlyphHeight = GlyphHeight.Value
976 };
977
978 TokenID.Token = Token;
979
980 if (TokenID.Method == TokenIdMethod.Unique)
981 {
982 if (!(TagReferences2 is null))
983 {
984 Dictionary<string, bool> TagNames = new Dictionary<string, bool>();
985
986 foreach (TokenTagReference Ref in TagReferences2)
987 TagNames[Ref.Name] = true;
988
989 LinkedList<XmlElement> ToCheck = new LinkedList<XmlElement>();
990
991 ToCheck.AddLast(Definition);
992
993 while (!(ToCheck.First is null))
994 {
995 XmlElement E = ToCheck.First.Value;
996 ToCheck.RemoveFirst();
997
998 foreach (XmlAttribute Attr in E.Attributes)
999 TagNames.Remove(Attr.Value);
1000
1001 TagNames.Remove(E.InnerText);
1002
1003 foreach (XmlNode N in E.ChildNodes)
1004 {
1005 if (N is XmlElement E2)
1006 ToCheck.AddLast(E2);
1007 }
1008 }
1009
1010 if (TagNames.Count > 0)
1011 {
1012 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Uniqueness requires all tags to be referenced by the machine-readable XML definition of the token, either in attributes or element values.", true, Legal, EDaler);
1013 return false;
1014 }
1015 }
1016
1017 Token.TokenId = Token.ComputeUniqueGuid().ToString() + "@" + EDaler.MainDomain.Address;
1018 }
1019
1020 Token.ShortId = CalcShortId(Token.TokenId, TokenID.ShortIdLength, TokenID.ShortIdAlphabet);
1021
1022 switch (Token.DefinitionNamespace)
1023 {
1026
1027 try
1028 {
1030 if (StateMachine is null)
1031 {
1032 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Not able to parse State-Machine definition.", true, Legal, EDaler);
1033 return false;
1034 }
1035 }
1036 catch (Exception ex)
1037 {
1038 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Exception occurred when parsing state-machine definition: " + ex.Message + "\r\n\r\n" + ex.StackTrace, true, Legal, EDaler);
1039 return false;
1040 }
1041
1042 TokenID.Machine = StateMachine;
1043
1044 if (string.IsNullOrEmpty(TokenID.Token.MachineId = StateMachine.StateMachineId))
1045 {
1046 StateMachine Temp = await Database.FindFirstIgnoreRest<StateMachine>(
1047 new FilterFieldEqualTo("StateMachineId", Token.TokenId));
1048
1049 if (Temp is null)
1050 StateMachine.StateMachineId = Token.TokenId;
1051 else
1052 {
1053 string Id;
1054
1055 do
1056 {
1057 Id = Guid.NewGuid().ToString();
1058 Id += Token.TokenId.Substring(Id.Length);
1059
1060 Temp = await Database.FindFirstIgnoreRest<StateMachine>(
1061 new FilterFieldEqualTo("StateMachineId", Id));
1062 }
1063 while (!(Temp is null));
1064
1065 StateMachine.StateMachineId = Id;
1066 }
1067
1068 TokenID.Token.MachineId = StateMachine.StateMachineId;
1069 }
1070 else // Singleton machine
1071 {
1072 if (SingletonStateMachine is null)
1073 {
1074 SingletonStateMachine = await Database.FindFirstIgnoreRest<StateMachine>(
1075 new FilterFieldEqualTo("StateMachineId", StateMachine.StateMachineId));
1076 }
1077
1078 if (SingletonStateMachine is null)
1079 SingletonStateMachine = StateMachine;
1080 else
1081 {
1082 if (SingletonStateMachine.XmlDefinition != Token.Definition)
1083 {
1084 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Attempted to change the definition of a singleton State-Machine.", true, Legal, EDaler);
1085 return false;
1086 }
1087
1088 if (SingletonStateMachine.DefinitionContractId != StateMachine.DefinitionContractId)
1089 {
1090 if (SingletonStateMachineContract is null)
1091 {
1092 SingletonStateMachineContract = await legal.GetContract(SingletonStateMachineContract.ContractId);
1093 if (SingletonStateMachineContract is null)
1094 {
1095 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Unable to access original creation contract.", true, Legal, EDaler);
1096 return false;
1097 }
1098
1099 if (SingletonStateMachineContract.State != ContractState.Signed)
1100 {
1101 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Original creation contract not in a signed state.", true, Legal, EDaler);
1102 return false;
1103 }
1104
1105 Dictionary<string, string> Parts = new Dictionary<string, string>();
1106
1107 if (!(SingletonStateMachineContract.ClientSignatures is null))
1108 {
1109 foreach (ClientSignature Signature in SingletonStateMachineContract.ClientSignatures)
1110 Parts[Signature.LegalId] = Signature.Role;
1111 }
1112
1113 if (!(Contract.ClientSignatures is null))
1114 {
1115 foreach (ClientSignature Signature in Contract.ClientSignatures)
1116 {
1117 if (!Parts.TryGetValue(Signature.LegalId, out string Role))
1118 continue;
1119
1120 if (Role != Signature.Role)
1121 {
1122 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Role mismatch compared to original creation contract, for part " + Signature.LegalId, true, Legal, EDaler);
1123 return false;
1124 }
1125
1126 Parts.Remove(Signature.LegalId);
1127 }
1128 }
1129
1130 if (Parts.Count > 0)
1131 {
1132 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: All parts of the original creation contract need to sign the new contract.", true, Legal, EDaler);
1133 return false;
1134 }
1135 }
1136 }
1137
1138 break;
1139 }
1140 }
1141
1142 if (StateMachines is null)
1143 StateMachines = new Dictionary<StateMachine, Token>();
1144
1145 StateMachine.Expires = Token.Expires;
1146 StateMachine.ArchiveRequired = Token.ArchiveRequired;
1147 StateMachine.ArchiveOptional = Token.ArchiveOptional;
1148 StateMachines[StateMachine] = Token;
1149 break;
1150 }
1151
1152 Token.Sign();
1153 Tokens.Add(Token);
1154 }
1155
1156 using (Semaphore Lock = await Semaphores.BeginWrite("NeuroFeature.Tokens.IDs")) // Only for creation and destruction
1157 {
1158 await Database.StartBulk();
1159 try
1160 {
1161 foreach (TokenIDReference TokenID in AllTokenIDs)
1162 {
1163 Token Prev = await GetToken(TokenID.Token.TokenId, false);
1164 Token PrevShort = await GetToken(TokenID.Token.ShortId, false);
1165
1166 if (!(Prev is null) || !(PrevShort is null))
1167 {
1168 if (TokenID.Method == TokenIdMethod.ByTrustProvider)
1169 {
1170 do
1171 {
1172 TokenID.TokenId = Guid.NewGuid();
1173 TokenID.Token.TokenId = TokenID.TokenId.ToString() + "@" + EDaler.MainDomain.Address;
1174 TokenID.Token.ShortId = CalcShortId(TokenID.Token.TokenId, TokenID.ShortIdLength, TokenID.ShortIdAlphabet);
1175
1176 Prev = await GetToken(TokenID.Token.TokenId, false);
1177 PrevShort = await GetToken(TokenID.Token.ShortId, false);
1178 }
1179 while (!(Prev is null) || !(PrevShort is null));
1180 }
1181 else
1182 {
1183 if (!(Prev is null))
1184 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Token with same ID already exists.", true, Legal, EDaler);
1185 else
1186 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Token with same short ID already exists.", true, Legal, EDaler);
1187
1188 return false;
1189 }
1190 }
1191 }
1192
1193 if (await MarketplaceProcessor.SignContract(Contract, ContractIsLocked, Legal, TrustProviderRole) is null)
1194 return false;
1195
1196 int i = Contract.ContractId.IndexOf('@');
1197 if (i <= 0 || !Guid.TryParse(Contract.ContractId.Substring(0, i), out Guid TransactionId))
1198 TransactionId = Guid.NewGuid();
1199
1200 decimal Commission = Value.Value * AllTokenIDs.Count * CommissionPercent.Value * 0.01m;
1201 string Ref = "iotsc:" + Contract.ContractId;
1202
1203 if (Commission > 0)
1204 {
1205 string PaymentUri = PaiwiseProcessor.GenerateContractualPaymentUri(TransactionId,
1206 CommissionPaidBy == CommissionPaidBy.Creator ? CreatorId : OwnerId, true,
1207 Gateway.ContractsClient.Client.BareJID, false, Currency, Commission, null, Ref,
1208 Contract.ContractId, null, 1, out _, out _);
1209
1210 if (!string.IsNullOrEmpty(await PaiwiseProcessor.ProcessPayment(PaymentUri, EDaler)))
1211 {
1212 await RejectContract(Contract, ContractIsLocked, "Unable to process payment for token creation. Tokens not created.",
1213 true, Legal, EDaler, "TokenPayment",
1214 new KeyValuePair<string, object>("Creator", CreatorId),
1215 new KeyValuePair<string, object>("Owner", OwnerId),
1216 new KeyValuePair<string, object>("Value", Value),
1217 new KeyValuePair<string, object>("Commission", Commission),
1218 new KeyValuePair<string, object>("Currency", Currency));
1219
1220 return false;
1221 }
1222 }
1223
1224 await Database.Insert(Tokens);
1225
1226 if (!(StateMachines is null))
1227 await Database.Insert(StateMachines.Keys);
1228
1229 if (!(TagReferences is null) && TagReferences.Count > 0)
1230 {
1231 try
1232 {
1233 List<TokenTag> Tags = new List<TokenTag>();
1234
1235 foreach (Token Token in Tokens)
1236 {
1237 foreach (TokenTagReference TagRef in TagReferences.Values)
1238 Tags.Add(TagRef.CreateTag(Token));
1239 }
1240
1241 await Database.Insert(Tags);
1242 }
1243 catch (Exception ex)
1244 {
1245 Log.Exception(ex);
1246 }
1247 }
1248
1249 try
1250 {
1251 List<TokenEvent> Events = new List<TokenEvent>();
1252
1253 foreach (Token Token in Tokens)
1254 {
1255 Events.Add(new Created()
1256 {
1257 ArchiveOptional = Token.ArchiveOptional,
1258 ArchiveRequired = Token.ArchiveRequired,
1259 Expires = Token.Expires,
1260 Creator = Token.Creator,
1261 Owner = Token.Owner,
1262 Currency = Token.Currency,
1263 Value = Token.Value,
1264 OwnershipContract = Contract.ContractId,
1265 TokenId = Token.TokenId,
1266 Timestamp = DateTime.UtcNow,
1267 Personal = false
1268 });
1269 }
1270
1271 await Database.Insert(Events);
1272 }
1273 catch (Exception ex)
1274 {
1275 Log.Exception(ex);
1276 }
1277
1278 // TODO: Protect integrity with transaction, commit, rollback, etc.
1279 }
1280 finally
1281 {
1282 await Database.EndBulk();
1283 }
1284 }
1285
1286 if (!(StateMachines is null))
1287 {
1288 foreach (KeyValuePair<StateMachine, Token> P in StateMachines)
1289 await StateMachineProcessor.Start(P.Key, P.Value, Legal, EDaler);
1290 }
1291
1292 if (!CaseInsensitiveString.IsNullOrEmpty(OwnerJid))
1293 {
1294 StringBuilder Xml = new StringBuilder();
1295
1296 foreach (Token Token in Tokens)
1297 {
1298 Xml.Clear();
1299
1300 Xml.Append("<tokenAdded xmlns='");
1301 Xml.Append(NeuroFeaturesNamespace);
1302 Xml.Append("'>");
1303 Token.Serialize(Xml, false, true);
1304 Xml.Append("</tokenAdded>");
1305
1306 if (await SendTokenMessage(EDaler, Xml.ToString(), OwnerJid, true, true))
1307 await TokenAdded(Token, OwnerJid);
1308 }
1309 }
1310
1311 return true;
1312 }
1313
1324 internal static Task<bool> SendTokenMessage(EDalerComponent EDaler, string Xml, string RecipientJid, bool ToClient, bool ToBroker)
1325 {
1326 return SendTokenMessage(EDaler, Xml, new XmppAddress(RecipientJid), ToClient, ToBroker);
1327 }
1328
1339 internal static async Task<bool> SendTokenMessage(EDalerComponent EDaler, string Xml, XmppAddress Recipient, bool ToClient, bool ToBroker)
1340 {
1341 if (ToClient)
1342 {
1343 await EDaler.Server.SendMessage(string.Empty, string.Empty, EDaler.MainDomain, Recipient,
1344 string.Empty, Xml);
1345 }
1346
1347 if (EDaler.Server.IsServerDomain(Recipient.Domain, true))
1348 return true;
1349 else
1350 {
1351 if (ToBroker)
1352 {
1353 CaseInsensitiveString Component = await EDaler.Legal.GetComponent(Recipient.Domain, NeuroFeaturesNamespace);
1355 await ComponentSynchronization.SendMessage(EDaler.MainDomain.Address, Component, Recipient.Domain, Xml);
1356 }
1357
1358 return false;
1359 }
1360 }
1361
1362 private static string CalcShortId(string TokenId, int ShortIdLength, string ShortIdAlphabet)
1363 {
1364 if (ShortIdLength <= 0)
1365 return string.Empty;
1366
1367 byte[] Bin = Hashes.ComputeSHA256Hash(Encoding.UTF8.GetBytes(TokenId));
1368 int i, j, c = Bin.Length;
1369
1370 for (i = 4, j = 0; i < c; i++, j = (j + 1) & 3)
1371 Bin[j] ^= Bin[i];
1372
1373 int Seed = BitConverter.ToInt32(Bin, 0);
1374 Random Rnd = new Random(Seed);
1375 char[] ch = new char[ShortIdLength];
1376
1377 c = ShortIdAlphabet.Length;
1378 if (c <= 0)
1379 return string.Empty;
1380
1381 for (i = 0; i < ShortIdLength; i++)
1382 ch[i] = ShortIdAlphabet[Rnd.Next(c)];
1383
1384 return new string(ch);
1385 }
1386
1391 public static async Task<Dictionary<string, object>> ReindexTokenReferences()
1392 {
1393 Dictionary<string, object> Counts = new Dictionary<string, object>();
1394 IEnumerable<Token> Tokens = await Database.Find<Token>();
1395 StringBuilder Xml = new StringBuilder();
1396
1397 foreach (Token Token in Tokens)
1398 {
1399 Xml.Clear();
1400
1401 Xml.Append("<tokenAdded xmlns='");
1402 Xml.Append(NeuroFeaturesNamespace);
1403 Xml.Append("'>");
1404 Token.Serialize(Xml, false, true);
1405 Xml.Append("</tokenAdded>");
1406
1408
1409 if (await SendTokenMessage(eDaler, Xml.ToString(), Owner, false, true))
1410 {
1411 await TokenAdded(Token, Token.OwnerJid);
1412 Inc(Counts, eDaler.Server.Domain);
1413 }
1414 else
1415 Inc(Counts, Owner.Domain);
1416 }
1417
1418 return Counts;
1419 }
1420
1425 public static async Task<Dictionary<string, object>> TokenOwnerStatistics()
1426 {
1427 Dictionary<string, object> Counts = new Dictionary<string, object>();
1428 IEnumerable<Token> Tokens = await Database.Find<Token>();
1429
1430 foreach (Token Token in Tokens)
1431 {
1433 Inc(Counts, Owner.Domain);
1434 }
1435
1436 return Counts;
1437 }
1438
1439 private static void Inc(Dictionary<string, object> Counts, string Key)
1440 {
1441 if (Counts.TryGetValue(Key, out object Obj) && Obj is int Count)
1442 Counts[Key] = Count + 1;
1443 else
1444 Counts[Key] = 1;
1445 }
1446
1447 #endregion
1448
1449 #region Destroy Contract
1450
1451 private static async Task<bool> DestroyTokens(XmlElement Destroy, Contract Contract, bool ContractIsLocked, string TrustProviderRole, LegalComponent Legal,
1453 {
1454 List<string> TokenIDs = new List<string>();
1455
1456 foreach (XmlElement E in Destroy.ChildNodes)
1457 {
1458 if (E.NamespaceURI == Contract.ForMachinesNamespace)
1459 {
1460 switch (E.LocalName)
1461 {
1462 case "TokenID":
1463 if (!(await PaiwiseProcessor.GetParameterValue(E, Contract) is string TokenId))
1464 return false;
1465
1466 TokenIDs.Add(TokenId);
1467 break;
1468 }
1469 }
1470 }
1471
1472 if (TokenIDs.Count == 0)
1473 {
1474 await RejectContract(Contract, ContractIsLocked, "Rejected token destruction contract: No Token IDs.", true, Legal, EDaler);
1475 return false;
1476 }
1477
1478 Dictionary<string, KeyValuePair<string, Token>> AuthorizedBy = new Dictionary<string, KeyValuePair<string, Token>>();
1479 Dictionary<string, KeyValuePair<string, List<Token>>> TokensPerBatch = new Dictionary<string, KeyValuePair<string, List<Token>>>();
1480 List<Token> Tokens = new List<Token>();
1481 int i;
1482
1483 using (Semaphore Lock = await Semaphores.BeginWrite("NeuroFeature.Tokens.IDs")) // Only for creation and destruction
1484 {
1485 foreach (string TokenID in TokenIDs)
1486 {
1487 Token Token = await GetToken(TokenID, false);
1488
1489 if (Token is null)
1490 {
1491 await RejectContract(Contract, ContractIsLocked, "Rejected token destruction contract: Token referenced that does not exist.", true, Legal, EDaler);
1492 return false;
1493 }
1494 else if (!Contract.Duration.HasValue ||
1495 DateTime.UtcNow.Date + Contract.Duration.Value < Token.Expires.Date.AddDays(1))
1496 {
1497 await RejectContract(Contract, ContractIsLocked, "Rejected token destruction contract: Duration must cover token expiry date.", true, Legal, EDaler);
1498 return false;
1499 }
1500 else
1501 {
1502 if (!(Contract.ClientSignatures is null))
1503 {
1504 foreach (ClientSignature Signature in Contract.ClientSignatures)
1505 {
1506 if (Signature.LegalId == Token.Creator ||
1507 Signature.BareJid == Token.CreatorJid)
1508 {
1510 {
1511 AuthorizedBy[TokenID] = new KeyValuePair<string, Token>(Signature.LegalId, Token);
1512 break;
1513 }
1514 }
1515
1516 if (Signature.LegalId == Token.Owner ||
1517 Signature.BareJid == Token.OwnerJid)
1518 {
1520 {
1521 AuthorizedBy[TokenID] = new KeyValuePair<string, Token>(Signature.LegalId, Token);
1522 break;
1523 }
1524 else if (Token.OwnerCanDestroyBatch)
1525 {
1526 if (!TokensPerBatch.TryGetValue(Token.CreationContract, out KeyValuePair<string, List<Token>> TokensInBatch))
1527 {
1528 TokensInBatch = new KeyValuePair<string, List<Token>>(Signature.LegalId, new List<Token>());
1529 TokensPerBatch[Token.CreationContract] = TokensInBatch;
1530 }
1531
1532 TokensInBatch.Value.Add(Token);
1533 }
1534 }
1535
1536 if (!(Token.Certifier is null))
1537 {
1538 int c = Token.Certifier.Length;
1539
1540 for (i = 0; i < c; i++)
1541 {
1542 if (Signature.LegalId == Token.Certifier[i] ||
1543 Signature.BareJid == Token.CertifierJids[i])
1544 {
1546 {
1547 AuthorizedBy[TokenID] = new KeyValuePair<string, Token>(Signature.LegalId, Token);
1548 break;
1549 }
1550 }
1551 }
1552 }
1553 }
1554 }
1555 }
1556 }
1557
1558 if (TokensPerBatch.Count > 0)
1559 {
1560 foreach (KeyValuePair<string, List<Token>> TokensInBatch in TokensPerBatch.Values)
1561 {
1562 if (TokensInBatch.Value[0].BatchSize == TokensInBatch.Value.Count)
1563 {
1564 foreach (Token Token in TokensInBatch.Value)
1565 AuthorizedBy[Token.TokenId] = new KeyValuePair<string, Token>(TokensInBatch.Key, Token);
1566 }
1567 }
1568 }
1569
1570 if (AuthorizedBy.Count != TokenIDs.Count)
1571 {
1572 await RejectContract(Contract, ContractIsLocked, "Rejected token destruction contract: Not authorized to destroy token referenced in contract.", true, Legal, EDaler);
1573 return false;
1574 }
1575
1576 if (await MarketplaceProcessor.SignContract(Contract, ContractIsLocked, Legal, TrustProviderRole) is null)
1577 return false;
1578
1579 List<TokenEvent> Events = new List<TokenEvent>();
1580
1581 foreach (KeyValuePair<string, Token> P in AuthorizedBy.Values)
1582 {
1583 Destroyed Event = new Destroyed()
1584 {
1585 ArchiveOptional = P.Value.ArchiveOptional,
1586 ArchiveRequired = P.Value.ArchiveRequired,
1587 Expires = P.Value.Expires,
1588 Owner = P.Key,
1589 OwnershipContract = Contract.ContractId,
1590 TokenId = P.Value.TokenId,
1591 Value = 0,
1592 Currency = string.Empty,
1593 Timestamp = DateTime.UtcNow,
1594 Personal = false
1595 };
1596
1597 Tokens.Add(P.Value);
1598 Events.Add(Event);
1599 }
1600
1601 await Database.StartBulk();
1602 try
1603 {
1604 await DeletePersonalEvents(TokenIDs);
1605 await Database.Delete(Tokens);
1606
1607 try
1608 {
1609 await Database.Insert(Events);
1610 }
1611 catch (Exception ex)
1612 {
1613 Log.Exception(ex);
1614 }
1615
1616 foreach (Token Token in Tokens)
1617 {
1618 tokenCache.Remove(Token.TokenId);
1620 tokenCache.Remove(Token.ShortId);
1621 }
1622
1623 // TODO: Protect integrity with transaction, commit, rollback, etc.
1624 }
1625 finally
1626 {
1627 await Database.EndBulk();
1628 }
1629
1630 i = 0;
1631 foreach (KeyValuePair<string, Token> P in AuthorizedBy.Values)
1632 await StateMachineProcessor.EventGenerated(P.Value, Events[i++]);
1633 }
1634
1635 StringBuilder Xml = new StringBuilder();
1636
1637 foreach (Token Token in Tokens)
1638 {
1639 string PrevOwner = Token.OwnerJid;
1640
1641 if (!string.IsNullOrEmpty(PrevOwner))
1642 {
1643 Token.Owner = string.Empty;
1644 Token.OwnerJid = string.Empty;
1645 Token.OwnershipContract = Contract.ContractId;
1646 Token.Sign();
1647
1648 Xml.Clear();
1649
1650 Xml.Append("<tokenRemoved xmlns='");
1651 Xml.Append(NeuroFeaturesNamespace);
1652 Xml.Append("'>");
1653 Token.Serialize(Xml, false, true);
1654 Xml.Append("</tokenRemoved>");
1655
1656 if (await SendTokenMessage(EDaler, Xml.ToString(), PrevOwner, true, true))
1657 await TokenRemoved(Token, PrevOwner);
1658 }
1659 }
1660
1661 return true;
1662 }
1663
1664 internal static async Task DestroyToken(Token Token, EDalerComponent EDaler)
1665 {
1666 await DeletePersonalEvents(new string[] { Token.TokenId });
1667 await Database.Delete(Token);
1668
1669 Destroyed Event = new Destroyed()
1670 {
1671 ArchiveOptional = Token.ArchiveOptional,
1672 ArchiveRequired = Token.ArchiveRequired,
1673 Expires = Token.Expires,
1674 Owner = Token.Owner,
1675 OwnershipContract = Token.OwnershipContract,
1676 TokenId = Token.TokenId,
1677 Value = 0,
1678 Currency = string.Empty,
1679 Timestamp = DateTime.UtcNow,
1680 Personal = false
1681 };
1682
1683 await Database.Insert(Event);
1684
1685 tokenCache.Remove(Token.TokenId);
1687 tokenCache.Remove(Token.ShortId);
1688
1689 // TODO: Protect integrity with transaction, commit, rollback, etc.
1690
1691 await StateMachineProcessor.EventGenerated(Token, Event);
1692
1693 string PrevOwner = Token.OwnerJid;
1694 if (!string.IsNullOrEmpty(PrevOwner))
1695 {
1696 StringBuilder Xml = new StringBuilder();
1697
1698 Token.Owner = string.Empty;
1699 Token.OwnerJid = string.Empty;
1700 Token.OwnershipContract = string.Empty;
1701 Token.Sign();
1702
1703 Xml.Clear();
1704
1705 Xml.Append("<tokenRemoved xmlns='");
1706 Xml.Append(NeuroFeaturesNamespace);
1707 Xml.Append("'>");
1708 Token.Serialize(Xml, false, true);
1709 Xml.Append("</tokenRemoved>");
1710
1711 if (await SendTokenMessage(EDaler, Xml.ToString(), PrevOwner, true, true))
1712 await TokenRemoved(Token, PrevOwner);
1713 }
1714 }
1715
1716 internal static async Task DeletePersonalEvents(IEnumerable<string> TokenIds)
1717 {
1718 foreach (string TokenId in TokenIds)
1719 {
1720 try
1721 {
1723 new FilterFieldEqualTo("TokenId", TokenId),
1724 new FilterFieldEqualTo("Personal", true)));
1725 }
1726 catch (Exception ex)
1727 {
1728 Log.Exception(ex);
1729 }
1730 }
1731 }
1732
1733 #endregion
1734
1735 #region Transfer Contract
1736
1737 private static async Task<bool> TransferTokens(XmlElement Transfer, Contract Contract, bool ContractIsLocked, string TrustProviderRole, LegalComponent Legal,
1739 {
1740 List<string> TokenIDs = new List<string>();
1741 string Currency = null;
1742 string OwnershipContract = null;
1743 decimal? Value = null;
1744 decimal? CommissionPercent = null;
1745
1746 foreach (XmlElement E in Transfer.ChildNodes)
1747 {
1748 if (E.NamespaceURI == Contract.ForMachinesNamespace)
1749 {
1750 switch (E.LocalName)
1751 {
1752 case "TokenID":
1753 if (!(await PaiwiseProcessor.GetParameterValue(E, Contract) is string TokenId))
1754 return false;
1755
1756 TokenIDs.Add(TokenId);
1757 break;
1758
1759 case "Value":
1760 if (!(await PaiwiseProcessor.GetParameterValue(E, Contract) is decimal d))
1761 {
1762 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Invalid value.", true, Legal, EDaler);
1763 return false;
1764 }
1765
1766 Value = d;
1767 break;
1768
1769 case "Currency":
1770 if (!(await PaiwiseProcessor.GetParameterValue(E, Contract) is string s))
1771 {
1772 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Invalid currency.", true, Legal, EDaler);
1773 return false;
1774 }
1775
1776 Currency = s;
1777 break;
1778
1779 case "CommissionPercent":
1780 if (!(await PaiwiseProcessor.GetParameterValue(E, Contract) is decimal d2) || d2 < 0 || d2 > 100)
1781 {
1782 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Invalid commission (%).", true, Legal, EDaler);
1783 return false;
1784 }
1785
1786 CommissionPercent = d2;
1787 break;
1788
1789 case "OwnershipContract":
1790 if (!(await PaiwiseProcessor.GetParameterValue(E, Contract) is string s2))
1791 {
1792 await RejectContract(Contract, ContractIsLocked, "Rejected token creation contract: Invalid Ownership contract reference.", true, Legal, EDaler);
1793 return false;
1794 }
1795
1796 OwnershipContract = s2;
1797 break;
1798 }
1799 }
1800 }
1801
1802 if (TokenIDs.Count == 0)
1803 {
1804 await RejectContract(Contract, ContractIsLocked, "Rejected token transfer contract: No Token IDs.", true, Legal, EDaler);
1805 return false;
1806 }
1807
1808 if (!Value.HasValue || Value.Value <= 0)
1809 {
1810 await RejectContract(Contract, ContractIsLocked, "Rejected token transfer contract: Value must be positive.", true, Legal, EDaler);
1811 return false;
1812 }
1813
1814 if (string.IsNullOrEmpty(Currency) || Currency.Length != 3)
1815 {
1816 await RejectContract(Contract, ContractIsLocked, "Rejected token transfer contract: Invalid currency.", true, Legal, EDaler);
1817 return false;
1818 }
1819
1820 if (!CommissionPercent.HasValue)
1821 {
1822 await RejectContract(Contract, ContractIsLocked, "Rejected token transfer contract: Commission missing.", true, Legal, EDaler);
1823 return false;
1824 }
1825
1826 if (CommissionPercent.Value < await MarketplaceProcessor.GetMinCommission())
1827 {
1828 await RejectContract(Contract, ContractIsLocked, "Rejected token transfer contract: Commission too low.", true, Legal, EDaler);
1829 return false;
1830 }
1831
1832 List<Token> Tokens = new List<Token>();
1833 CaseInsensitiveString SellerId = null;
1834 CaseInsensitiveString SellerJid = null;
1835 CaseInsensitiveString BuyerId = null;
1836 CaseInsensitiveString BuyerJid = null;
1837 CaseInsensitiveString TrustProviderId = null;
1838
1839 using (Semaphore Lock = await Semaphores.BeginRead("NeuroFeature.Tokens.IDs")) // Will only read, not change Token IDs in the system
1840 {
1841 foreach (string TokenID in TokenIDs)
1842 {
1843 Token Token = await GetToken(TokenID, false);
1844
1845 if (Token is null)
1846 {
1847 await RejectContract(Contract, ContractIsLocked, "Rejected token transfer contract: Token referenced that does not exist.", true, Legal, EDaler);
1848 return false;
1849 }
1850 else if (Token.OwnershipContract != OwnershipContract)
1851 {
1852 await RejectContract(Contract, ContractIsLocked, "Rejected token transfer contract: Incorrect ownership contract reference.", true, Legal, EDaler);
1853 return false;
1854 }
1855 else if (!Contract.Duration.HasValue ||
1856 DateTime.UtcNow.Date + Contract.Duration.Value < Token.Expires.Date.AddDays(1))
1857 {
1858 await RejectContract(Contract, ContractIsLocked, "Rejected token transfer contract: Duration must cover token expiry date.", true, Legal, EDaler);
1859 return false;
1860 }
1861 else
1862 {
1863 if (!(Contract.ClientSignatures is null))
1864 {
1865 foreach (ClientSignature Signature in Contract.ClientSignatures)
1866 {
1867 bool Identified = false;
1868
1869 if ((Signature.LegalId == Token.Owner || Signature.BareJid == Token.OwnerJid))
1870 {
1872 {
1873 SellerId = Signature.LegalId;
1874 SellerJid = Signature.BareJid;
1875 }
1876 else if (SellerId != Signature.LegalId)
1877 {
1878 await RejectContract(Contract, ContractIsLocked, "Rejected token transfer contract: Multiple sellers in one contract not permitted.", true, Legal, EDaler);
1879 return false;
1880 }
1881
1882 Identified = true;
1883 }
1884
1885 if (Signature.LegalId == Token.TrustProvider || Signature.BareJid == Token.TrustProviderJid)
1886 {
1887 if (CaseInsensitiveString.IsNullOrEmpty(TrustProviderId))
1888 {
1890 {
1891 // TODO: Forward contract in message to intended trust provider, to validate transaction.
1892 await RejectContract(Contract, ContractIsLocked, "Rejected token transfer contract: Token not hosted by this trust provider.", true, Legal, EDaler);
1893 return false;
1894 }
1895
1896 TrustProviderId = Signature.LegalId;
1897 }
1898 else if (TrustProviderId != Signature.LegalId)
1899 {
1900 await RejectContract(Contract, ContractIsLocked, "Rejected token transfer contract: Multiple trust providers in one contract not permitted.", true, Legal, EDaler);
1901 return false;
1902 }
1903
1904 Identified = true;
1905 }
1906
1907 if (!Identified)
1908 {
1910 {
1911 BuyerId = Signature.LegalId;
1912 BuyerJid = Signature.BareJid;
1913 }
1914 else if (BuyerId != Signature.LegalId)
1915 {
1916 await RejectContract(Contract, ContractIsLocked, "Rejected token transfer contract: Only one buyer allowed (buyer=not current owner or trust provider).", true, Legal, EDaler);
1917 return false;
1918 }
1919 }
1920 }
1921
1923 {
1924 await RejectContract(Contract, ContractIsLocked, "Rejected token transfer contract: No seller identified for token.", true, Legal, EDaler);
1925 return false;
1926 }
1927
1929 {
1930 await RejectContract(Contract, ContractIsLocked, "Rejected token transfer contract: No buyer identified for token.", true, Legal, EDaler);
1931 return false;
1932 }
1933
1934 if (!CaseInsensitiveString.IsNullOrEmpty(TrustProviderId))
1935 {
1936 await RejectContract(Contract, ContractIsLocked, "Rejected token transfer contract: Trust provider already signed.", true, Legal, EDaler);
1937 return false;
1938 }
1939
1940 Tokens.Add(Token);
1941 }
1942 }
1943 }
1944
1945 if (Tokens.Count != TokenIDs.Count)
1946 {
1947 await RejectContract(Contract, ContractIsLocked, "Rejected token transfer contract: Not authorized to transfer token referenced in contract.", true, Legal, EDaler);
1948 return false;
1949 }
1950
1951 if (SellerJid == BuyerJid)
1952 {
1953 await RejectContract(Contract, ContractIsLocked, "Rejected token transfer contract: Buyer and Seller cannot be the same.", true, Legal, EDaler);
1954 return false;
1955 }
1956
1957 if (await MarketplaceProcessor.SignContract(Contract, ContractIsLocked, Legal, TrustProviderRole) is null)
1958 return false;
1959
1960 int i = Contract.ContractId.IndexOf('@');
1961 if (i <= 0 || !Guid.TryParse(Contract.ContractId.Substring(0, i), out Guid TransactionId))
1962 TransactionId = Guid.NewGuid();
1963
1964 decimal Total = Value.Value * TokenIDs.Count;
1965 decimal Commission = Total * CommissionPercent.Value * 0.01m;
1966 string Ref = "iotsc:" + Contract.ContractId;
1967
1968 string PaymentUri1 = PaiwiseProcessor.GenerateContractualPaymentUri(TransactionId, BuyerId, true,
1969 SellerId, true, Currency, Total, null, Ref, Contract.ContractId, null, 1, out _, out _);
1970
1971 if (!string.IsNullOrEmpty(await PaiwiseProcessor.ProcessPayment(PaymentUri1, EDaler)))
1972 {
1973 await RejectContract(Contract, ContractIsLocked, "Unable to process payment for token transfer. Tokens not created.",
1974 true, Legal, EDaler, "TokenPayment",
1975 new KeyValuePair<string, object>("Buyer", BuyerId.Value),
1976 new KeyValuePair<string, object>("Seller", SellerId.Value),
1977 new KeyValuePair<string, object>("Value", Value),
1978 new KeyValuePair<string, object>("Commission", Commission),
1979 new KeyValuePair<string, object>("Currency", Currency));
1980
1981 return false;
1982 }
1983
1984 if (Commission > 0)
1985 {
1986 string PaymentUri2 = PaiwiseProcessor.GenerateContractualPaymentUri(Guid.NewGuid(), SellerId, true,
1987 Gateway.ContractsClient.Client.BareJID, false, Currency, Commission, null, Ref,
1988 Contract.ContractId, null, 1, out _, out _);
1989
1990 if (!string.IsNullOrEmpty(await PaiwiseProcessor.ProcessPayment(PaymentUri2, EDaler)))
1991 {
1992 await RejectContract(Contract, ContractIsLocked, "Unable to process commission payment for token transfer.",
1993 false, Legal, EDaler, "TokenPayment",
1994 new KeyValuePair<string, object>("Buyer", BuyerId.Value),
1995 new KeyValuePair<string, object>("Seller", SellerId.Value),
1996 new KeyValuePair<string, object>("Value", Value),
1997 new KeyValuePair<string, object>("Commission", Commission),
1998 new KeyValuePair<string, object>("Currency", Currency));
1999 }
2000 }
2001
2002 List<TokenEvent> Events = new List<TokenEvent>();
2003
2004 foreach (Token Token in Tokens)
2005 {
2006 Token.Value = Value.Value;
2007 Token.Currency = Currency;
2008 Token.Owner = BuyerId;
2009 Token.OwnerJid = BuyerJid;
2010 Token.OwnershipContract = Contract.ContractId;
2011 Token.Sign();
2012
2013 Events.Add(new Transferred()
2014 {
2015 ArchiveOptional = Token.ArchiveOptional,
2016 ArchiveRequired = Token.ArchiveRequired,
2017 Expires = Token.Expires,
2018 Seller = SellerId,
2019 Owner = BuyerId,
2020 OwnershipContract = Contract.ContractId,
2021 Value = Value.Value,
2022 Commission = Commission,
2023 Currency = Currency,
2024 TokenId = Token.TokenId,
2025 Timestamp = DateTime.UtcNow,
2026 Personal = false
2027 });
2028 }
2029
2030 await Database.StartBulk();
2031 try
2032 {
2033 await DeletePersonalEvents(TokenIDs);
2034 await Database.Update(Tokens);
2035
2036 try
2037 {
2038 await Database.Insert(Events);
2039 }
2040 catch (Exception ex)
2041 {
2042 Log.Exception(ex);
2043 }
2044
2045 // TODO: Protect integrity with transaction, commit, rollback, etc.
2046 }
2047 finally
2048 {
2049 await Database.EndBulk();
2050 }
2051
2052 int c = Tokens.Count;
2053
2054 for (i = 0; i < c; i++)
2055 {
2056 try
2057 {
2058 await StateMachineProcessor.EventGenerated(Tokens[i], Events[i]);
2059 }
2060 catch (Exception ex)
2061 {
2062 Log.Exception(ex, Tokens[i].TokenId.Value);
2063 }
2064 }
2065 }
2066
2067 StringBuilder Xml = new StringBuilder();
2068
2069 foreach (Token Token in Tokens)
2070 {
2071 Xml.Clear();
2072
2073 Xml.Append("<tokenRemoved xmlns='");
2074 Xml.Append(NeuroFeaturesNamespace);
2075 Xml.Append("'>");
2076 Token.Serialize(Xml, false, true);
2077 Xml.Append("</tokenRemoved>");
2078
2079 if (await SendTokenMessage(EDaler, Xml.ToString(), SellerJid, true, true))
2080 await TokenRemoved(Token, SellerJid);
2081
2082 Xml.Clear();
2083
2084 Xml.Append("<tokenAdded xmlns='");
2085 Xml.Append(NeuroFeaturesNamespace);
2086 Xml.Append("'>");
2087 Token.Serialize(Xml, false, true);
2088 Xml.Append("</tokenAdded>");
2089
2090 if (await SendTokenMessage(EDaler, Xml.ToString(), BuyerJid, true, true))
2091 await TokenAdded(Token, BuyerJid);
2092 }
2093
2094 return true;
2095 }
2096
2097 #endregion
2098
2099 #endregion
2100
2101 #region XMPP interface
2102
2103 private static EDalerComponent eDaler = null;
2104 private static LegalComponent legal = null;
2105
2106 #region Handlers
2107
2108 internal static void RegisterHandlers(EDalerComponent EDaler)
2109 {
2110 eDaler = EDaler;
2111
2112 EDaler.RegisterIqGetHandler("token", NeuroFeaturesNamespace, GetTokenHandler, true);
2113 EDaler.RegisterIqGetHandler("tokens", NeuroFeaturesNamespace, GetTokensHandler, false);
2114 EDaler.RegisterIqGetHandler("totals", NeuroFeaturesNamespace, GetTotalsHandler, false);
2115 EDaler.RegisterIqSetHandler("noteText", NeuroFeaturesNamespace, SetNoteTextHandler, false);
2116 EDaler.RegisterIqSetHandler("noteXml", NeuroFeaturesNamespace, SetNoteXmlHandler, false);
2117 EDaler.RegisterIqGetHandler("events", NeuroFeaturesNamespace, GetEventsHandler, false);
2118 EDaler.RegisterIqGetHandler("creationAttributes", NeuroFeaturesNamespace, GetCreationAttributesHandler, false);
2119 EDaler.RegisterIqGetHandler("description", NeuroFeaturesNamespace, GetDescriptionHandler, false);
2120
2121 EDaler.RegisterIqSetHandler("tokenAdded", NeuroFeaturesNamespace, TokenAddedIqHandler, false);
2122 EDaler.RegisterMessageHandler("tokenAdded", NeuroFeaturesNamespace, TokenAddedMessageHandler, false);
2123 EDaler.RegisterIqSetHandler("tokenRemoved", NeuroFeaturesNamespace, TokenRemovedIqHandler, false);
2124 EDaler.RegisterMessageHandler("tokenRemoved", NeuroFeaturesNamespace, TokenRemovedMessageHandler, false);
2125 }
2126
2127 internal static void UnregisterHandlers(EDalerComponent EDaler)
2128 {
2129 eDaler = null;
2130
2131 EDaler.UnregisterIqGetHandler("token", NeuroFeaturesNamespace, GetTokenHandler, true);
2132 EDaler.UnregisterIqGetHandler("tokens", NeuroFeaturesNamespace, GetTokensHandler, false);
2133 EDaler.UnregisterIqGetHandler("totals", NeuroFeaturesNamespace, GetTotalsHandler, false);
2134 EDaler.UnregisterIqSetHandler("noteText", NeuroFeaturesNamespace, SetNoteTextHandler, false);
2135 EDaler.UnregisterIqSetHandler("noteXml", NeuroFeaturesNamespace, SetNoteXmlHandler, false);
2136 EDaler.UnregisterIqGetHandler("events", NeuroFeaturesNamespace, GetEventsHandler, false);
2137 EDaler.UnregisterIqGetHandler("creationAttributes", NeuroFeaturesNamespace, GetCreationAttributesHandler, false);
2138 EDaler.UnregisterIqGetHandler("description", NeuroFeaturesNamespace, GetDescriptionHandler, false);
2139
2140 EDaler.UnregisterIqSetHandler("tokenAdded", NeuroFeaturesNamespace, TokenAddedIqHandler, false);
2141 EDaler.UnregisterMessageHandler("tokenAdded", NeuroFeaturesNamespace, TokenAddedMessageHandler, false);
2142 EDaler.UnregisterIqSetHandler("tokenRemoved", NeuroFeaturesNamespace, TokenRemovedIqHandler, false);
2143 EDaler.UnregisterMessageHandler("tokenRemoved", NeuroFeaturesNamespace, TokenRemovedMessageHandler, false);
2144 }
2145
2146 internal static void RegisterHandlers(LegalComponent Legal)
2147 {
2148 legal = Legal;
2149
2150 Legal.RegisterIqGetHandler("contractTokens", NeuroFeaturesNamespace, GetContractTokensHandler, false);
2151 }
2152
2153 internal static void UnregisterHandlers(LegalComponent Legal)
2154 {
2155 legal = Legal;
2156
2157 Legal.UnregisterIqGetHandler("contractTokens", NeuroFeaturesNamespace, GetContractTokensHandler, false);
2158 }
2159
2160 #endregion
2161
2162 #region GetToken
2163
2164 private static async Task GetTokenHandler(object Sender, IqEventArgs e)
2165 {
2166 Token Token = await GetToken(e);
2167 if (Token is null)
2168 return;
2169
2170 StringBuilder Xml = new StringBuilder();
2171 Token.Serialize(Xml, true, true);
2172
2173 await e.IqResult(Xml.ToString(), e.To);
2174 }
2175
2176 private static async Task<Token> GetToken(IqEventArgs e)
2177 {
2178 string TokenId = XML.Attribute(e.Query, "id");
2179
2180 Token Token = await GetToken(TokenId, false);
2181 if (Token is null)
2182 {
2183 await e.IqErrorItemNotFound(e.To, "Token not found.", "en");
2184 return null;
2185 }
2186
2187 if (!await IsAuthorizedAccess(Token, e))
2188 return null;
2189
2190 return Token;
2191 }
2192
2193 internal static async Task<bool> IsAuthorizedAccess(Token Token, IqEventArgs e)
2194 {
2195 switch (Token.Visibility)
2196 {
2197 case ContractVisibility.Public:
2198 case ContractVisibility.PublicSearchable:
2199 return true;
2200
2201 case ContractVisibility.DomainAndParts:
2202 if (eDaler.Server.IsServerDomain(e.From.Domain, true))
2203 return true;
2204 break;
2205 }
2206
2207 if (Token.OwnerJid == e.From.BareJid)
2208 return true;
2209
2211 return true;
2212
2213 Contract Contract = await legal.GetContract(Token.OwnershipContract);
2214 if (Contract is null)
2215 {
2216 await e.IqErrorServiceUnavailable(e.To, "Ownership contract of token not accessible at this time.", "en");
2217 return false;
2218 }
2219
2220 if (!await Contract.CanRead(e.From, eDaler.Server, legal))
2221 {
2222 await e.IqErrorForbidden(e.To, "You do not have access to this token.", "en");
2223 return false;
2224 }
2225
2226 return true;
2227 }
2228
2229 #endregion
2230
2231 #region GetTokens
2232
2233 private static async Task GetTokensHandler(object Sender, IqEventArgs e)
2234 {
2235 int Offset = XML.Attribute(e.Query, "offset", 0);
2236 int MaxCount = XML.Attribute(e.Query, "maxCount", int.MaxValue);
2237 bool References = XML.Attribute(e.Query, "references", true);
2238
2239 IEnumerable<TokenReference> TokenReferences = await Database.Find<TokenReference>(Offset, MaxCount,
2240 new FilterFieldEqualTo("OwnerJid", e.From.BareJid), "-Updated");
2241 string Xml;
2242
2243 if (References)
2244 Xml = SerializeTokenReferences(TokenReferences);
2245 else
2246 {
2247 List<Token> Tokens = new List<Token>();
2248
2249 foreach (TokenReference Ref in TokenReferences)
2250 {
2251 Token Token = await GetToken(Ref.TokenId, false);
2252 if (!(Token is null))
2253 Tokens.Add(Token);
2254 }
2255
2256 Xml = SerializeTokenReferences(Tokens);
2257 }
2258
2259 await e.IqResult(Xml, e.To);
2260 }
2261
2262 private static async Task GetContractTokensHandler(object Sender, IqEventArgs e)
2263 {
2264 CaseInsensitiveString ContractId = XML.Attribute(e.Query, "contractId");
2265 int Offset = XML.Attribute(e.Query, "offset", 0);
2266 int MaxCount = XML.Attribute(e.Query, "maxCount", int.MaxValue);
2267 bool References = XML.Attribute(e.Query, "references", true);
2268
2269 XmppAddress ContractAddr = new XmppAddress(ContractId);
2270 if (!legal.IsComponentDomain(ContractAddr.Domain, true))
2271 {
2272 await e.IqErrorBadRequest(e.To, "Contract not hosted on this server.", "en");
2273 return;
2274 }
2275
2276 Contract Contract = await legal.GetContract(ContractId);
2277 if (Contract is null)
2278 {
2279 await e.IqErrorItemNotFound(e.To, "Contract not found.", "en");
2280 return;
2281 }
2282
2283 if (!await Contract.CanRead(e.From, eDaler.Server, legal))
2284 {
2285 await e.IqErrorForbidden(e.To, "Access to contract denied.", "en");
2286 return;
2287 }
2288
2289 string Xml;
2290 IEnumerable<Token> Tokens = await Database.Find<Token>(Offset, MaxCount,
2291 new FilterFieldEqualTo("CreationContract", ContractId), "-Created");
2292
2293 if (References)
2294 {
2295 List<TokenReference> TokenReferences = new List<TokenReference>();
2296
2297 foreach (Token Token in Tokens)
2298 {
2299 TokenReferences.Add(new TokenReference()
2300 {
2301 TokenId = Token.TokenId
2302 });
2303 }
2304
2305 Xml = SerializeTokenReferences(TokenReferences);
2306 }
2307 else
2308 Xml = SerializeTokenReferences(Tokens);
2309
2310 await e.IqResult(Xml, e.To);
2311 }
2312
2313 internal static string SerializeTokenReferences(IEnumerable<TokenReference> Tokens)
2314 {
2315 StringBuilder Xml = new StringBuilder();
2316
2317 Xml.Append("<tokenReferences xmlns='");
2318 Xml.Append(NeuroFeaturesNamespace);
2319 Xml.Append("'>");
2320
2321 foreach (TokenReference Token in Tokens)
2322 {
2323 Xml.Append("<ref id='");
2324 Xml.Append(XML.Encode(Token.TokenId));
2325 Xml.Append("'/>");
2326 }
2327
2328 Xml.Append("</tokenReferences>");
2329
2330 return Xml.ToString();
2331 }
2332
2333 internal static string SerializeTokenReferences(IEnumerable<Token> Tokens)
2334 {
2335 StringBuilder Xml = new StringBuilder();
2336
2337 Xml.Append("<tokens xmlns='");
2338 Xml.Append(NeuroFeaturesNamespace);
2339 Xml.Append("'>");
2340
2341 foreach (Token Token in Tokens)
2342 Token.Serialize(Xml, false, true);
2343
2344 Xml.Append("</tokens>");
2345
2346 return Xml.ToString();
2347 }
2348
2349 #endregion
2350
2351 #region GetTotals
2352
2353 private static async Task GetTotalsHandler(object Sender, IqEventArgs e)
2354 {
2355 StringBuilder Xml = new StringBuilder();
2356 string Currency = null;
2357 decimal Total = 0;
2358 int Nr = 0;
2359
2360 Xml.Append("<totals xmlns='");
2361 Xml.Append(NeuroFeaturesNamespace);
2362 Xml.Append("'>");
2363
2364 foreach (TokenReference TokenRef in await Database.Find<TokenReference>(new FilterFieldEqualTo("OwnerJid", e.From.BareJid), "Currency"))
2365 {
2366 if (Currency is null || Currency != TokenRef.Currency)
2367 {
2368 if (Nr > 0)
2369 {
2370 Xml.Append("<total nr='");
2371 Xml.Append(Nr.ToString());
2372 Xml.Append("' total='");
2373 Xml.Append(CommonTypes.Encode(Total));
2374 Xml.Append("' currency='");
2375 Xml.Append(XML.Encode(Currency));
2376 Xml.Append("'/>");
2377 }
2378
2379 Nr = 1;
2380 Total = TokenRef.Value;
2381 Currency = TokenRef.Currency;
2382 }
2383 else
2384 {
2385 Nr++;
2386 Total += TokenRef.Value;
2387 }
2388 }
2389
2390 if (Nr > 0)
2391 {
2392 Xml.Append("<total nr='");
2393 Xml.Append(Nr.ToString());
2394 Xml.Append("' total='");
2395 Xml.Append(CommonTypes.Encode(Total));
2396 Xml.Append("' currency='");
2397 Xml.Append(XML.Encode(Currency));
2398 Xml.Append("'/>");
2399 }
2400
2401 Xml.Append("</totals>");
2402
2403 await e.IqResult(Xml.ToString(), e.To);
2404 }
2405
2406 #endregion
2407
2408 #region SetNoteText
2409
2410 private static async Task SetNoteTextHandler(object Sender, IqEventArgs e)
2411 {
2412 string TokenId = XML.Attribute(e.Query, "id");
2413 bool Personal = XML.Attribute(e.Query, "personal", false);
2414
2415 Token Token = await GetToken(TokenId, false);
2416 if (Token is null)
2417 {
2418 await e.IqErrorItemNotFound(e.To, "Token not found.", "en");
2419 return;
2420 }
2421
2423
2424 if (Token.OwnerJid == e.From.BareJid)
2425 {
2426 Event = new NoteText()
2427 {
2428 ArchiveOptional = Token.ArchiveOptional,
2429 ArchiveRequired = Token.ArchiveRequired,
2430 Expires = Token.Expires,
2431 Note = e.Query.InnerText,
2432 Personal = Personal,
2433 Timestamp = DateTime.UtcNow,
2434 TokenId = Token.TokenId
2435 };
2436 }
2437 else
2438 {
2439 if (!await IsApprovedExternalSource(Token, e))
2440 return;
2441
2442 Event = new ExternalNoteText()
2443 {
2444 ArchiveOptional = Token.ArchiveOptional,
2445 ArchiveRequired = Token.ArchiveRequired,
2446 Expires = Token.Expires,
2447 Note = e.Query.InnerText,
2448 Personal = Personal,
2449 Timestamp = DateTime.UtcNow,
2450 TokenId = Token.TokenId,
2451 Source = e.From.BareJid
2452 };
2453 }
2454
2455 await Database.Insert(Event);
2456 await e.IqResult(string.Empty, e.To);
2457
2458 await Task.Run(async () =>
2459 {
2460 try
2461 {
2462 await StateMachineProcessor.EventGenerated(Token, Event);
2463 }
2464 catch (Exception ex)
2465 {
2466 Log.Exception(ex);
2467 }
2468 });
2469 }
2470
2471 private static async Task<bool> IsApprovedExternalSource(Token Token, IqEventArgs e)
2472 {
2474 return true;
2475
2477 {
2478 StateMachineProcessor.CacheRecord Machine = await StateMachineProcessor.GetStateMachine(Token);
2479 if (Machine is null)
2480 {
2481 await e.IqErrorItemNotFound(e.To, "State-machine not found.", "en");
2482 return false;
2483 }
2484
2485 if (Machine.CurrentState is null)
2486 {
2487 await e.IqErrorServiceUnavailable(e.To, "Current state of State-machine not available.", "en");
2488 return false;
2489 }
2490
2491 if (Machine.CurrentState.HasEnded)
2492 {
2493 await e.IqErrorServiceUnavailable(e.To, "State-machine has ended.", "en");
2494 return false;
2495 }
2496
2497 if (!Machine.CurrentState.ContainsSource(e.From.BareJid))
2498 {
2499 await e.IqErrorForbidden(e.To, "You are not an approved external part for this token.", "en");
2500 return false;
2501 }
2502 }
2503 else
2504 {
2505 await e.IqErrorForbidden(e.To, "You are not an approved external part for this token.", "en");
2506 return false;
2507 }
2508
2509 return true;
2510 }
2511
2512 #endregion
2513
2514 #region SetNoteXml
2515
2516 private static async Task SetNoteXmlHandler(object Sender, IqEventArgs e)
2517 {
2518 string TokenId = XML.Attribute(e.Query, "id");
2519 bool Personal = XML.Attribute(e.Query, "personal", false);
2520
2521 Token Token = await GetToken(TokenId, false);
2522 if (Token is null)
2523 {
2524 await e.IqErrorItemNotFound(e.To, "Token not found.", "en");
2525 return;
2526 }
2527
2529 bool Found = false;
2530 string NoteXml = null;
2531 string LocalName = null;
2532 string Namespace = null;
2533
2534 foreach (XmlNode N in e.Query.ChildNodes)
2535 {
2536 if (N is XmlElement E)
2537 {
2538 NoteXml = E.OuterXml;
2539 LocalName = E.LocalName;
2540 Namespace = E.NamespaceURI;
2541
2542 Found = true;
2543 break;
2544 }
2545 }
2546
2547 if (!Found)
2548 {
2549 await e.IqErrorBadRequest(e.To, "Empty XML Note.", "en");
2550 return;
2551 }
2552
2553 if (!(legal is null))
2554 {
2555 XmlDocument Doc = new XmlDocument()
2556 {
2557 PreserveWhitespace = true
2558 };
2559 Doc.LoadXml(NoteXml);
2560
2561 (string, Dictionary<string, ValidationSchema>) P = await legal.ValidateContent(Doc);
2562 if (!string.IsNullOrEmpty(P.Item1))
2563 {
2564 await e.IqErrorBadRequest(e.To, "XML Note invalid: " + P.Item1, "en");
2565 return;
2566 }
2567 }
2568
2569 if (Token.OwnerJid == e.From.BareJid)
2570 {
2571 Event = new NoteXml()
2572 {
2573 ArchiveOptional = Token.ArchiveOptional,
2574 ArchiveRequired = Token.ArchiveRequired,
2575 Expires = Token.Expires,
2576 Personal = Personal,
2577 Timestamp = DateTime.UtcNow,
2578 TokenId = Token.TokenId,
2579 Note = NoteXml,
2580 LocalName = LocalName,
2581 Namespace = Namespace
2582 };
2583 }
2584 else
2585 {
2586 if (!await IsApprovedExternalSource(Token, e))
2587 return;
2588
2589 Event = new ExternalNoteXml()
2590 {
2591 ArchiveOptional = Token.ArchiveOptional,
2592 ArchiveRequired = Token.ArchiveRequired,
2593 Expires = Token.Expires,
2594 Personal = Personal,
2595 Timestamp = DateTime.UtcNow,
2596 TokenId = Token.TokenId,
2597 Note = NoteXml,
2598 LocalName = LocalName,
2599 Namespace = Namespace,
2600 Source = e.From.BareJid
2601 };
2602 }
2603
2604 await Database.Insert(Event);
2605 await e.IqResult(string.Empty, e.To);
2606
2607 await Task.Run(async () =>
2608 {
2609 try
2610 {
2611 await StateMachineProcessor.EventGenerated(Token, Event);
2612 }
2613 catch (Exception ex)
2614 {
2615 Log.Exception(ex);
2616 }
2617 });
2618 }
2619
2620 #endregion
2621
2622 #region GetEvents
2623
2624 private static async Task GetEventsHandler(object Sender, IqEventArgs e)
2625 {
2626 string TokenId = XML.Attribute(e.Query, "id");
2627
2628 Token Token = await GetToken(TokenId, false);
2629 if (Token is null)
2630 {
2631 await e.IqErrorItemNotFound(e.To, "Token not found.", "en");
2632 return;
2633 }
2634
2635 if (!await IsAuthorizedAccess(Token, e))
2636 return;
2637
2638 int Offset = XML.Attribute(e.Query, "offset", 0);
2639 int MaxCount = XML.Attribute(e.Query, "maxCount", int.MaxValue);
2640
2641 IEnumerable<TokenEvent> Events = await GetTokenEvents(Token, Offset, MaxCount);
2642 StringBuilder Xml = new StringBuilder();
2643
2644 Xml.Append("<events xmlns='");
2645 Xml.Append(NeuroFeaturesNamespace);
2646 Xml.Append("'>");
2647
2648 foreach (TokenEvent Event in Events)
2649 Event.Serialize(Xml);
2650
2651 Xml.Append("</events>");
2652
2653 await e.IqResult(Xml.ToString(), e.To);
2654 }
2655
2656 internal static async Task<IEnumerable<TokenEvent>> GetTokenEvents(Token Token, int Offset, int MaxCount)
2657 {
2658
2659 IEnumerable<TokenEvent> Events = await Database.Find<TokenEvent>(Offset, MaxCount,
2660 new FilterFieldEqualTo("TokenId", Token.TokenId), "-Timestamp");
2661
2663 {
2664 IEnumerable<TokenEvent> Events2 = await Database.Find<TokenEvent>(Offset, MaxCount,
2665 new FilterFieldEqualTo("TokenId", Token.MachineId), "-Timestamp");
2666
2667 IEnumerator<TokenEvent> e1 = Events.GetEnumerator();
2668 IEnumerator<TokenEvent> e2 = Events2.GetEnumerator();
2669
2670 LinkedList<TokenEvent> Merged = new LinkedList<TokenEvent>();
2671 bool Has1 = e1.MoveNext();
2672 bool Has2 = e2.MoveNext();
2673
2674 while (Has1 && Has2)
2675 {
2676 if (e1.Current.Timestamp > e2.Current.Timestamp)
2677 {
2678 Merged.AddLast(e1.Current);
2679 Has1 = e1.MoveNext();
2680 }
2681 else
2682 {
2683 Merged.AddLast(e2.Current);
2684 Has2 = e2.MoveNext();
2685 }
2686 }
2687
2688 while (Has1)
2689 {
2690 Merged.AddLast(e1.Current);
2691 Has1 = e1.MoveNext();
2692 }
2693
2694 while (Has2)
2695 {
2696 Merged.AddLast(e2.Current);
2697 Has2 = e2.MoveNext();
2698 }
2699
2700 Events = Merged;
2701 }
2702
2703 return Events;
2704 }
2705
2706 #endregion
2707
2708 #region GetCreationAttributes
2709
2710 private static async Task GetCreationAttributesHandler(object Sender, IqEventArgs e)
2711 {
2712 StringBuilder Xml = new StringBuilder();
2713
2714 Xml.Append("<creationAttributes xmlns='");
2715 Xml.Append(NeuroFeaturesNamespace);
2716 Xml.Append("' currency='");
2717
2718 if (!(eDaler is null))
2719 Xml.Append(XML.Encode(await eDaler.GetDefaultCurrency()));
2720
2721 Xml.Append("' commission='");
2722 Xml.Append(CommonTypes.Encode(await MarketplaceProcessor.GetMinCommission()));
2723 Xml.Append("' trustProvider='");
2724
2727
2728 Xml.Append("'/>");
2729
2730 await e.IqResult(Xml.ToString(), e.To);
2731 }
2732
2733 #endregion
2734
2735 #region GetDescription
2736
2737 private static async Task GetDescriptionHandler(object Sender, IqEventArgs e)
2738 {
2739 Token Token = await GetToken(e);
2740 if (Token is null)
2741 return;
2742
2743 ReportFormat ReportFormat = XML.Attribute(e.Query, "format", ReportFormat.Markdown);
2744 StringBuilder Xml = new StringBuilder();
2745
2746 Xml.Append("<report xmlns='");
2747 Xml.Append(NeuroFeaturesNamespace);
2748 Xml.Append("'>");
2749 Xml.Append(XML.Encode(await GetDescription(Token, ReportFormat)));
2750 Xml.Append("</report>");
2751
2752 await e.IqResult(Xml.ToString(), e.To);
2753 }
2754
2755 internal static async Task<string> GetDescription(Token Token, ReportFormat Format)
2756 {
2758
2759 if (!(Token.Tags is null))
2760 {
2761 foreach (TokenTagReference Tag in Token.Tags)
2762 Variables[Tag.Name] = Tag.Value;
2763 }
2764
2765 return await FormatReport(Token.Description, Format, Variables);
2766 }
2767
2768 internal static async Task<string> FormatReport(string Markdown,
2770 {
2771 MarkdownSettings Settings = new MarkdownSettings()
2772 {
2773 AllowScriptTag = false,
2774 AudioAutoplay = false,
2775 AudioControls = false,
2776 RootFolder = Gateway.RootFolder,
2777 ParseMetaData = false,
2779 VideoAutoplay = false,
2780 VideoControls = false,
2781 EmbedEmojis = false,
2782 EmojiSource = null,
2783 AuthorizeExpression = (Exp) =>
2784 {
2785 XmppServer.CheckExpressionSafe(Exp, true, true, true, out ScriptNode Prohibited);
2786 return Task.FromResult(Prohibited);
2787 }
2788 };
2789
2790 MarkdownDocument Doc = await MarkdownDocument.CreateAsync(Markdown, Settings);
2791
2792 switch (Format)
2793 {
2794 case ReportFormat.Markdown:
2795 return FixLocalFileLinks(await Doc.GenerateMarkdown(true));
2796
2797 case ReportFormat.Html:
2798 string Html = await Doc.GenerateHTML();
2799 return FixLocalFileLinks(HtmlDocument.GetBody(Html));
2800
2801 case ReportFormat.Text:
2802 return FixLocalFileLinks(await Doc.GeneratePlainText());
2803
2804 case ReportFormat.Xaml:
2805 return FixLocalFileLinks(await Doc.GenerateXAML());
2806
2807 case ReportFormat.XamarinXaml:
2808 return FixLocalFileLinks(await Doc.GenerateXamarinForms());
2809
2810 case ReportFormat.SmartContract:
2811 return FixLocalFileLinks(await Doc.GenerateSmartContractXml());
2812
2813 default:
2814 throw new ArgumentException("Unrecognized report format: " + Format.ToString(), nameof(Format));
2815 }
2816 }
2817
2818 private static string FixLocalFileLinks(string Report)
2819 {
2820 if (localFileReference is null)
2821 {
2822 string s = Gateway.RootFolder;
2823 if (string.IsNullOrEmpty(s))
2824 return Report;
2825
2826 if (!string.IsNullOrEmpty(s) && s[s.Length - 1] == Path.DirectorySeparatorChar)
2827 s = s.Substring(0, s.Length - 1);
2828
2829 localFileReference = new Regex("[\"']" + s.Replace("\\", "\\\\") +
2830 "(?'LocalUrl'[^\"']*)[\"']", RegexOptions.Compiled | RegexOptions.Multiline);
2831 }
2832
2833 StringBuilder sb = new StringBuilder();
2834 int i = 0;
2835 int c = Report.Length;
2836
2837 while (i < c)
2838 {
2839 Match M = localFileReference.Match(Report, i);
2840 if (!M.Success)
2841 {
2842 sb.Append(Report.Substring(i, c - i));
2843 i = c;
2844 }
2845 else
2846 {
2847 sb.Append(Report.Substring(i, M.Index));
2848 i = M.Index + M.Length;
2849
2850 sb.Append('"');
2851 sb.Append(Gateway.GetUrl(M.Groups["LocalUrl"].Value.Replace(Path.DirectorySeparatorChar, '/')));
2852 sb.Append('"');
2853 }
2854 }
2855
2856 return sb.ToString();
2857 }
2858
2859 private static Regex localFileReference = null;
2860
2861 #endregion
2862
2863 #region Token Added
2864
2865 private static async Task TokenAddedIqHandler(object Sender, IqEventArgs e)
2866 {
2867 string Error = null;
2868 bool Empty = true;
2869
2870 foreach (XmlNode N in e.Query.ChildNodes)
2871 {
2872 if (N is XmlElement E)
2873 {
2874 Empty = false;
2875
2876 Token Token = IsTokenMessageElement(E, e.From, out string Message);
2877 if (!(Token is null))
2878 await TokenAdded(Token, Token.OwnerJid);
2879 else if (!string.IsNullOrEmpty(Message))
2880 Error = Message;
2881 }
2882 }
2883
2884 if (!string.IsNullOrEmpty(Error))
2885 await e.IqErrorBadRequest(e.To, Error, "en");
2886 else if (Empty)
2887 await e.IqErrorBadRequest(e.To, "No token found.", "en");
2888 else
2889 await e.IqResult(string.Empty, e.To);
2890 }
2891
2892 private static async Task TokenAddedMessageHandler(object Sender, MessageEventArgs e)
2893 {
2894 foreach (XmlNode N in e.Content.ChildNodes)
2895 {
2896 if (N is XmlElement E)
2897 {
2898 Token Token = IsTokenMessageElement(E, e.From, out _);
2899 if (!(Token is null))
2900 await TokenAdded(Token, Token.OwnerJid);
2901 }
2902 }
2903 }
2904
2905 internal static async Task TokenAdded(Token Token, CaseInsensitiveString OwnerJid)
2906 {
2907 TokenReference Ref = await Database.FindFirstDeleteRest<TokenReference>(
2908 new FilterFieldEqualTo("TokenId", Token.TokenId), "-Updated");
2909
2910 if (Ref is null)
2911 {
2912 Ref = new TokenReference()
2913 {
2914 ArchiveOptional = Token.ArchiveOptional,
2915 ArchiveRequired = Token.ArchiveRequired,
2917 Expires = Token.Expires,
2918 OwnerJid = OwnerJid,
2919 TokenId = Token.TokenId,
2920 Updated = Token.Updated,
2921 Value = Token.Value,
2922 Currency = Token.Currency
2923 };
2924
2925 await Database.Insert(Ref);
2926 }
2927 else if (
2928 Ref.OwnerJid != OwnerJid ||
2929 Ref.Updated != Token.Updated ||
2930 Ref.Value != Token.Value ||
2931 Ref.Currency != Token.Currency)
2932 {
2933 Ref.OwnerJid = OwnerJid;
2934 Ref.Updated = Token.Updated;
2935 Ref.Value = Token.Value;
2936 Ref.Currency = Token.Currency;
2937
2938 await Database.Update(Ref);
2939 }
2940 }
2941
2942 private static Token IsTokenMessageElement(XmlElement E, XmppAddress From, out string Message)
2943 {
2944 if (E.LocalName != "token" || E.NamespaceURI != NeuroFeaturesNamespace)
2945 {
2946 Message = "Non-token embedded element.";
2947 return null;
2948 }
2949
2950 if (!Token.TryParse(E, out Token EmbeddedToken))
2951 {
2952 Message = "Unable to parse embedded token.";
2953 return null;
2954 }
2955
2956 if (!legal.Server.IsServerDomain(new XmppAddress(EmbeddedToken.OwnerJid).Domain, true))
2957 {
2958 Message = "Embedded token discarded. Owner not on broker.";
2959 return null;
2960 }
2961
2962 if (From.Address != new XmppAddress(EmbeddedToken.TokenId).Domain)
2963 {
2964 Message = "Embedded token discarded. Sender not host of token.";
2965 return null;
2966 }
2967
2968 Message = null;
2969 return EmbeddedToken;
2970 }
2971
2972 #endregion
2973
2974 #region Token Removed
2975
2976 private static async Task TokenRemovedIqHandler(object Sender, IqEventArgs e)
2977 {
2978 string Error = null;
2979 bool Empty = true;
2980
2981 foreach (XmlNode N in e.Query.ChildNodes)
2982 {
2983 if (N is XmlElement E)
2984 {
2985 Empty = false;
2986
2987 Token Token = IsTokenMessageElement(E, e.From, out string Message);
2988 if (!(Token is null))
2989 await TokenRemoved(Token, Token.OwnerJid);
2990 else if (!string.IsNullOrEmpty(Message))
2991 Error = Message;
2992 }
2993 }
2994
2995 if (!string.IsNullOrEmpty(Error))
2996 await e.IqErrorBadRequest(e.To, Error, "en");
2997 else if (Empty)
2998 await e.IqErrorBadRequest(e.To, "No token found.", "en");
2999 else
3000 await e.IqResult(string.Empty, e.To);
3001 }
3002
3003 private static async Task TokenRemovedMessageHandler(object Sender, MessageEventArgs e)
3004 {
3005 foreach (XmlNode N in e.Content.ChildNodes)
3006 {
3007 if (N is XmlElement E)
3008 {
3009 Token Token = IsTokenMessageElement(E, e.From, out _);
3010 if (!(Token is null))
3011 await TokenRemoved(Token, Token.OwnerJid);
3012 }
3013 }
3014 }
3015
3016 internal static async Task TokenRemoved(Token Token, CaseInsensitiveString OwnerJid)
3017 {
3019 {
3021 new FilterFieldEqualTo("TokenId", Token.TokenId));
3022 }
3023 else
3024 {
3026 new FilterFieldEqualTo("TokenId", Token.TokenId),
3027 new FilterFieldEqualTo("OwnerJid", OwnerJid)));
3028 }
3029 }
3030
3031 #endregion
3032
3033 #endregion
3034
3035 }
3036}
Helps with parsing of commong data types.
Definition: CommonTypes.cs:13
static string Encode(bool x)
Encodes a Boolean for use in XML and other formats.
Definition: CommonTypes.cs:594
static string GetBody(string Html)
Extracts the contents of the BODY element in a HTML string.
Static class managing encoding and decoding of internet content.
static Task< object > DecodeAsync(string ContentType, byte[] Data, Encoding Encoding, KeyValuePair< string, string >[] Fields, Uri BaseUri)
Decodes an object.
Contains a markdown document. This markdown document class supports original markdown,...
Task< string > GenerateMarkdown()
Generates Markdown from the markdown text.
async Task< string > GeneratePlainText()
Generates Plain Text from the markdown text.
async Task< string > GenerateHTML()
Generates HTML from the markdown text.
static Task< MarkdownDocument > CreateAsync(string MarkdownText, params Type[] TransparentExceptionTypes)
Contains a markdown document. This markdown document class supports original markdown,...
Contains settings that the Markdown parser uses to customize its behavior.
Helps with common XML-related tasks.
Definition: XML.cs:19
static string Attribute(XmlElement E, string Name)
Gets the value of an XML attribute.
Definition: XML.cs:914
static string Encode(string s)
Encodes a string for use in XML.
Definition: XML.cs:27
Class representing an event.
Definition: Event.cs:10
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:13
static void Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
Definition: Log.cs:1647
static void Error(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs an error event.
Definition: Log.cs:682
Static class managing the runtime environment of the IoT Gateway.
Definition: Gateway.cs:126
static string GetUrl(string LocalResource)
Gets a URL for a resource.
Definition: Gateway.cs:4167
static ContractsClient ContractsClient
XMPP Contracts Client, if such a compoent is available on the XMPP broker.
Definition: Gateway.cs:4375
static string RootFolder
Web root folder.
Definition: Gateway.cs:2379
Implements an HTTP server.
Definition: HttpServer.cs:36
static Variables CreateVariables()
Creates a new collection of variables, that contains access to the global set of variables.
Definition: HttpServer.cs:1604
Base class for components.
Definition: Component.cs:16
void RegisterIqGetHandler(string LocalName, string Namespace, EventHandlerAsync< IqEventArgs > Handler, bool PublishNamespaceAsFeature)
Registers an IQ-Get handler.
Definition: Component.cs:149
XmppAddress MainDomain
Main/principal domain address
Definition: Component.cs:86
bool IsComponentDomain(CaseInsensitiveString Domain, bool IncludeAlternativeDomains)
Checks if a domain is the component domain, or optionally, an alternative component domain.
Definition: Component.cs:123
XmppServer Server
XMPP Server.
Definition: Component.cs:96
bool UnregisterIqGetHandler(string LocalName, string Namespace, EventHandlerAsync< IqEventArgs > Handler, bool RemoveNamespaceAsFeature)
Unregisters an IQ-Get handler.
Definition: Component.cs:249
Event arguments for IQ queries.
Definition: IqEventArgs.cs:12
XmppAddress From
From address attribute
Definition: IqEventArgs.cs:93
Task IqResult(string Xml, string From)
Returns a response to the current request.
Definition: IqEventArgs.cs:113
Task IqErrorItemNotFound(XmppAddress From, string ErrorText, string Language)
Returns a item-not-found error.
Definition: IqEventArgs.cs:201
XmlElement Query
Query element, if found, null otherwise.
Definition: IqEventArgs.cs:70
XmppAddress To
To address attribute
Definition: IqEventArgs.cs:88
Task IqErrorServiceUnavailable(XmppAddress From, string ErrorText, string Language)
Returns a service-unavailable error.
Definition: IqEventArgs.cs:215
Task IqErrorBadRequest(XmppAddress From, string ErrorText, string Language)
Returns a bad-request error.
Definition: IqEventArgs.cs:159
Task IqErrorForbidden(XmppAddress From, string ErrorText, string Language)
Returns a forbidden error.
Definition: IqEventArgs.cs:229
Event arguments for responses to IQ queries.
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).
XmppAddress From
From address attribute
XmlElement Content
Content element, if found, null otherwise.
Contains information about one XMPP address.
Definition: XmppAddress.cs:9
CaseInsensitiveString Domain
Domain
Definition: XmppAddress.cs:97
CaseInsensitiveString Address
XMPP Address
Definition: XmppAddress.cs:37
CaseInsensitiveString BareJid
Bare JID
Definition: XmppAddress.cs:45
Task< IqResultEventArgs > IqRequest(string Type, string From, string To, string Language, string ContentXml)
Sends an IQ stanza to a recipient.
Definition: XmppServer.cs:3371
bool IsServerDomain(CaseInsensitiveString Domain, bool IncludeAlternativeDomains)
Checks if a domain is the server domain, or optionally, an alternative domain.
Definition: XmppServer.cs:861
CaseInsensitiveString Domain
Domain name.
Definition: XmppServer.cs:882
static bool CheckExpressionSafe(Expression Expression, out ScriptNode Prohibited)
Checks if an expression is safe to execute (if it comes from an external source).
Definition: XmppServer.cs:6733
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 Length
Gets the number of characters in the current CaseInsensitiveString object.
string LowerCase
Lower-case representation of the 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:19
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:879
static Task EndBulk()
Ends bulk-processing of data. Must be called once for every call to StartBulk.
Definition: Database.cs:1494
static Task StartBulk()
Starts bulk-proccessing of data. Must be followed by a call to EndBulk.
Definition: Database.cs:1486
static async Task Update(object Object)
Updates an object in the database.
Definition: Database.cs:626
static async Task Delete(object Object)
Deletes an object in the database.
Definition: Database.cs:717
static Task< IEnumerable< object > > Find(string Collection, params string[] SortOrder)
Finds objects in a given collection.
Definition: Database.cs:247
static async Task Insert(object Object)
Inserts an object into the default collection of the database.
Definition: Database.cs:95
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 lesser than a given value.
Implements an in-memory cache.
Definition: Cache.cs:15
Represents a named semaphore, i.e. an object, identified by a name, that allows single concurrent wri...
Definition: Semaphore.cs:19
async void Dispose()
Disposes of the named semaphore, and releases any locks the object manages.
Definition: Semaphore.cs:161
Static class of application-wide semaphores that can be used to order access to editable objects.
Definition: Semaphores.cs:16
static async Task< Semaphore > BeginRead(string Key)
Waits until the semaphore identified by Key is ready for reading. Each call to BeginRead must be fol...
Definition: Semaphores.cs:53
static async Task< Semaphore > BeginWrite(string Key)
Waits until the semaphore identified by Key is ready for writing. Each call to BeginWrite must be fo...
Definition: Semaphores.cs:90
Base class for all nodes in a parsed script tree.
Definition: ScriptNode.cs:69
Collection of variables.
Definition: Variables.cs:25
Contains methods for simple hash calculations.
Definition: Hashes.cs:59
static byte[] ComputeSHA256Hash(byte[] Data)
Computes the SHA-256 hash of a block of binary data.
Definition: Hashes.cs:348
Manages eDaler on accounts connected to the broker.
async Task< string > GetDefaultCurrency()
Gets the default currency
Marketplace processor, brokering sales of items via tenders and offers defined in smart contracts.
Event raised when a token has been created.
Definition: Created.cs:10
Event raised when a token has been destroyed.
Definition: Destroyed.cs:7
A text note logged on the token from an external source.
An xml note logged on the token from an external source.
An xml note logged on the token.
Definition: NoteXml.cs:9
string LocalName
Local name of root-element in XML
Definition: NoteXml.cs:26
Abstract base class for token events.
Definition: TokenEvent.cs:19
Abstract base class for token events containing notes made by the owner.
Event raised when a token has been transferred.
Definition: Transferred.cs:11
Marketplace processor, brokering sales of items via tenders and offers defined in smart contracts.
static async Task< Dictionary< string, object > > TokenOwnerStatistics()
Reindexes token references, and sends relevant token messages to remote neurons.
const string NeuroFeaturesNamespace
https://paiwise.tagroot.io/Schema/NeuroFeatures.xsd
static async Task< Dictionary< string, object > > ReindexTokenReferences()
Reindexes token references, and sends relevant token messages to remote neurons.
abstract TokenTag CreateTag(Token Token)
Creates a Tag object.
Abstract base class for token tags.
Definition: TokenTag.cs:19
CaseInsensitiveString CreatorJid
JID of Creator of token
Definition: Token.cs:185
bool HasStateMachine
If the token has an associated state-machine.
Definition: Token.cs:1257
Duration? ArchiveOptional
Duration after which token expires, and the required archiving time, the token can optionally be arch...
Definition: Token.cs:382
Guid ComputeUniqueGuid()
Computes a GUID from the defining contents of the token.
Definition: Token.cs:810
string Definition
M2M definition of token, in XML, from the original creation contract.
Definition: Token.cs:249
bool OwnerCanDestroyBatch
If the current owner is allowed to destroy the token.
Definition: Token.cs:442
void Serialize(StringBuilder Xml, bool IncludeNamespace, bool IncludeServerSignature)
Serializes the Token, in normalized form.
Definition: Token.cs:609
CaseInsensitiveString OwnershipContract
ID of contract that details the claims of the current owner
Definition: Token.cs:276
ContractVisibility Visibility
Visibility of token
Definition: Token.cs:167
string DefinitionNamespace
Namespace of M2M definition of token.
Definition: Token.cs:258
CaseInsensitiveString Owner
Current owner of token
Definition: Token.cs:194
CaseInsensitiveString OwnerJid
JID of Current owner of token
Definition: Token.cs:203
CaseInsensitiveString TrustProviderJid
JID of Trust Provider, asserting claims in the token.
Definition: Token.cs:221
CaseInsensitiveString CreationContract
ID of contract that details the creation of the token.
Definition: Token.cs:267
bool IsExternalPart(CaseInsensitiveString BareJid)
Checks if a JID is an external part in the creation of the contract.
Definition: Token.cs:1233
bool CertifierCanDestroy
If a certifier is allowed to destroy the token.
Definition: Token.cs:462
CaseInsensitiveString ShortId
Short ID
Definition: Token.cs:148
CaseInsensitiveString[] CertifierJids
Optional Bare JIDs of parties used to certify claims in the legal document used to create the token.
Definition: Token.cs:316
TokenTagReference[] Tags
Optional tags and their values, attached on created.
Definition: Token.cs:565
DateTime Updated
When token was updated.
Definition: Token.cs:353
string Description
Description Markdown for the token.
Definition: Token.cs:491
Duration? ArchiveRequired
Duration after which token expires, the token is required to be archived.
Definition: Token.cs:372
static bool TryParse(XmlElement Xml, out Token Token)
Serializes the Token, in normalized form.
Definition: Token.cs:832
DateTime Created
When token was created.
Definition: Token.cs:344
bool CreatorCanDestroy
If the creator is allowed to destroy the token.
Definition: Token.cs:432
decimal Value
Latest value of token
Definition: Token.cs:335
CaseInsensitiveString[] Certifier
Optional Legal IDs of parties used to certify claims in the legal document used to create the token.
Definition: Token.cs:306
CaseInsensitiveString MachineId
State Machine ID, if any
Definition: Token.cs:157
string Currency
Currency used to represent latest value.
Definition: Token.cs:230
CaseInsensitiveString TrustProvider
Trust Provider, asserting claims in the token.
Definition: Token.cs:212
DateTime Expires
Expiry date of token.
Definition: Token.cs:362
bool OwnerCanDestroyIndividual
If the current owner is allowed to destroy the token.
Definition: Token.cs:452
CaseInsensitiveString TokenId
Token ID
Definition: Token.cs:139
CaseInsensitiveString Creator
Creator of token
Definition: Token.cs:176
Contains a reference to a token, possibly on another neuron.
string Currency
Currency used to represent latest value.
CaseInsensitiveString OwnerJid
JID of Current owner of token
Paiwise processor, processing payment instructions defined in smart contracts.
Class representing a state machine.
Definition: StateMachine.cs:37
CaseInsensitiveString StateMachineId
ID of State Machine.
Definition: StateMachine.cs:51
CaseInsensitiveString DefinitionContractId
ID of Definition Contract
Definition: StateMachine.cs:76
static StateMachine Parse(Token Token)
Parses the XML representation of a State Machine.
const string StateMachineNamespace
https://paiwise.tagroot.io/Schema/StateMachines.xsd
Component that synchronizes content in the federated network, by sending synchronization messages and...
static async Task SendMessage(CaseInsensitiveString Sender, CaseInsensitiveString Recipient, CaseInsensitiveString RecipientServer, string Xml)
Sends a synchronization message
delegate Task< ScriptNode > AuthorizeExpression(Expression Expression)
Delegate for expression authorization methods.
ReportFormat
Desired report format
Definition: ReportFormat.cs:7
TokenIdMethod
By which mechanism the Token ID was created
Definition: Token.cs:24
Represents a duration value, as defined by the xsd:duration data type: http://www....
Definition: Duration.cs:13