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