From 60ada13945582cb91e266677ab963c7cafa1ac4c Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Sat, 1 Feb 2020 07:07:49 -0600 Subject: [PATCH 01/55] Certs --- cicd/assets/TestRootOne.testcert | Bin 0 -> 747 bytes cicd/assets/TestRootThree.testcert | Bin 0 -> 751 bytes cicd/assets/TestRootTwo.testcert | Bin 0 -> 747 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 cicd/assets/TestRootOne.testcert create mode 100644 cicd/assets/TestRootThree.testcert create mode 100644 cicd/assets/TestRootTwo.testcert diff --git a/cicd/assets/TestRootOne.testcert b/cicd/assets/TestRootOne.testcert new file mode 100644 index 0000000000000000000000000000000000000000..916a33cbc55315c9192ca66e162e482482fd4d65 GIT binary patch literal 747 zcmXqLVtQ`S#CU!IGZP~dlYsbxj*r4`=k>ca|8Lt{xnZ&aFB_*;n@8JsUPeZ4Rt5tx zLlFZZHs(+kW*+X4)Z&t${QMICyi@}@ab6<>14AQo10w@76XPf#*AT)r0&#KbYhqMF zwt|tBfw_s1p8@D1E~X|%MuwxFo0lwm8vcdb=-}4gLWR}glO88+o$cyVdB6Pn@hc4y ziqSpgEuSln-4T9t^c<#nu-#ri1j}-R0h4uZm33rq7{jEGX zZAU|B$V|(x@7(yUCOdTQ^vluT6y>#gLFBoeac$>U?c08P#pBu4E3Z%1KD@B=?<~=c zte+46sdeTR+i>&Wwx&wcyBfwj_s???aX3@husX!Xk-N`St$NaBWA@2>Gny6_{}#3N zx$Ji7q|`*kn#bqeWJXocy?ABg%x!J=tF}iw+DOWiEynpxA zZ{K|#^5o{_@87p`SA>7RIV1L1c$nB3hjxsSbGB7SSG|)HT1BSXRKO^IR78YhE zCKdx(5TB1lj75Yo`_Hv)yauvWESW+3g%`f$TA;#*9H_u}0|qK1L(-{bI~1O)<~B~< z+VO3s?0>h*hj0C9fBkS0%jNYhA6?FLM;$JZ`x^MN`tXYkmA(F(dT*xBPZ04>{!?1< zPe#65`*QobBKMgko(!jD88UtKC(AE#X=E4f5Pa{pC8> z(iyD~_I!3uRdvnofRJUWos;@*twd+n?FFKDw()P+`HGdHy6f=KNq)cC z80_wJ-`+21RlZK~(8Ua?KU#(Aec|l^h82#Xee)e%Z zG3VjDZ?_&5HtzPV-dcWUWAxq`-Fy0?*DX+K-*SJa?#qDlhjwib-1C-swM_S#HnST5 Ds8m1< literal 0 HcmV?d00001 diff --git a/cicd/assets/TestRootThree.testcert b/cicd/assets/TestRootThree.testcert new file mode 100644 index 0000000000000000000000000000000000000000..3ed630ccb6f9ec326a354a292de97e8fc5c7146e GIT binary patch literal 751 zcmXqLVtQ@R#CUlDGZP~dlYo&bL(U%6j@|B?1?_h3%sFkq%f_kI=F#?@mywa1mBB#5 zP|QGtjX9KsnTIzdwYVfGKffd-qbN1iKu(<3$iTqR$lSolz|6!X3dl8taE(A*JUW{g zm5{ArWMyD(V&rE4x{8abiII`vfY#AFl@-agUV+Sxvww7bZa>)ZY?e}&n}Kf9E{=_2 zYjSg6{;lV>Pb*KGc)o-!@=bYIRYZQ|?YXZ+?d)809!<>)W;wdA)lA*ohxzlYjQU-x zuLZ?qR8IXU)ppjjIM1h@C&N0}t#(3oYj>K+6obxtQ9NI)ch0N3XuQFI_ujUAp)32} z^t2Q@1neq#uOM{hQsIZ}Rmx9}v8j8BZElmzR4A}{xoGZ*@2S&VjvZ_>>$z&iFz4m& zVEb(2w2(vD+qk$Y*VmgdcpkW|VP>GN;cctkuas@Q&UE@Q_Ddr2F*moEUFqdGB))&K zK|`#zQP2O`W`>cTnHMxGC(FpJpH-Q)_QO*qW=00a#fAp@27JJfm*r<<{LjL|%*4cE zAPeI2v52vVZ1}&E*KGf^|Lb-yo+5C^e}{9ls}6FY0^<%CsEiD{RbsX}Laf)MzqJ{j zTKM&O(y8TiEFJ=CH zUtqnt#(eLQ1pS#WWD}HkunRo-kmb7BR)J%)oTlY;Lc8baW%AD5W+U@f9{>zY BEbjmS literal 0 HcmV?d00001 diff --git a/cicd/assets/TestRootTwo.testcert b/cicd/assets/TestRootTwo.testcert new file mode 100644 index 0000000000000000000000000000000000000000..9573456af00180f50912fb2f3f87d65c8f0cad15 GIT binary patch literal 747 zcmXqLVtQ`S#CU!IGZP~dlR)lkujdWcx7T@8F1s(14AQo10w@76O$+)*AT)r0&#KbYhqMF zwt|tBfw_s1p8@D1E~X|%Mur_c91jj$Vqi~Z^qpwD_=wS-HEt6EN*+&)<2o#`fScn_ z`1H~@a?;yp=f3KUe(m(+0%3X zfZNkWnO~M=f9U#nc#UP$gyI`D4kc1syp^%#i5J;gG(yxQ_r5VE||IcsaF;E|4pIpzQ()^ zeHnBA1xNO;PZvKIV7hFg&Bu%@ecAJ?jFuMX|Kq8?{$K6+vW{s9%%0xKy#8J~Yd0wQ z3&n^n(f;dSTj|toJcHlo(by8KxJgeUGh4Y^{a^SpF_rv zIGLtLDz)uDr|RW;zUh0et=bkIceA^KPLH0gxtMf0;c;Q?2eSp<^NYfTKdqVOymxcR zl{?+Wm%rF;oo5(&c;d957p){LOoWmi?EJ`*R?i^2&WJTgsZjky2-~^$%(5qBj~T0( z{4ues5Viy{dyx1@bOgK;++qU6lrwaX^&!wTXOVi zWxd&g#N^p$&wSEi*zwn~Yaw&=)5tYHOAk*`X$owTuJyMK<-4HWdTQF4eRDKYRocZI zxi9Un&^Z2bqD9yCvuer;mslF#M)JveK4e`i+Wu~4%uKbnHPS_aectU^-Sut>O92Ll BHrN0F literal 0 HcmV?d00001 From 9d280d959332b8061e40e75673a733a09df6802f Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Fri, 14 Feb 2020 21:19:36 -0600 Subject: [PATCH 02/55] Minor tweak to SemVer parsing. --- .../SemanticVersion.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/RapidField.SolidInstruments.Core/SemanticVersion.cs b/src/RapidField.SolidInstruments.Core/SemanticVersion.cs index 39613509..07ab16cc 100644 --- a/src/RapidField.SolidInstruments.Core/SemanticVersion.cs +++ b/src/RapidField.SolidInstruments.Core/SemanticVersion.cs @@ -674,7 +674,20 @@ private static Boolean Parse(String input, out SemanticVersion result, Boolean r try { - var processedString = input.StartsWith(IgnoredPrefixWord, true, CultureInfo.InvariantCulture) ? input.Skip(IgnoredPrefixWord.Length).ToString().Solidify() : input.Solidify().TrimStart(IgnoredPrefixCharacters); + var solidifiedString = input.Solidify(); + var processedString = solidifiedString.StartsWith(IgnoredPrefixWord, true, CultureInfo.InvariantCulture) ? solidifiedString.Skip(IgnoredPrefixWord.Length).ToString() : solidifiedString.TrimStart(IgnoredPrefixCharacters); + + if (processedString.Length == 0) + { + if (raiseExceptionOnFail) + { + throw new FormatException(ParseFormatExceptionMessage, new ArgumentException("The input string does not contain any version information.", nameof(input))); + } + + result = default; + return false; + } + var regularExpression = new Regex(RegularExpressionPatternForCompleteVersion); var matchGroups = regularExpression.IsMatch(processedString) ? regularExpression.Match(processedString).Groups : null; From cf32c770513676c90223a413d63adddd0de2f162 Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Sun, 16 Feb 2020 12:02:56 -0600 Subject: [PATCH 03/55] Changing messaging nomenclature. --- LICENSE.txt | 2 +- .../ExampleContractsDependencyModule.cs | 14 +- .../ExampleDomainDependencyModule.cs | 18 +- ...ApplicationStartedEventMessageListener.cs} | 12 +- ...ApplicationStoppedEventMessageListener.cs} | 12 +- .../ExceptionRaisedEventMessageListener.cs} | 12 +- .../HeartbeatMessageListener.cs} | 12 +- .../PingRequestMessageListener.cs} | 12 +- .../ApplicationDependencyModule.cs | 8 +- .../ExampleMessagingServiceExecutor.cs | 22 +-- ...e.cs => AzureServiceBusListeningFacade.cs} | 26 +-- .../AzureServiceBusRequestingFacade.cs | 12 +- ...s => AzureServiceBusTransmittingFacade.cs} | 16 +- ...ssageSubscriber.cs => IMessageListener.cs} | 20 +-- ...ngFacade.cs => IMessageListeningFacade.cs} | 32 ++-- .../IMessageRequestingFacade.cs | 18 +- ...agePublisher.cs => IMessageTransmitter.cs} | 20 +-- ...acade.cs => IMessageTransmittingFacade.cs} | 84 ++++----- .../IMessagingClientFactory.cs | 16 +- .../Message.cs | 4 +- .../MessageHandlerRole.cs | 8 +- ...essageSubscriber.cs => MessageListener.cs} | 40 ++--- ...eption.cs => MessageListeningException.cs} | 24 +-- ...ingFacade.cs => MessageListeningFacade.cs} | 167 +++++++++--------- ...cy.cs => MessageListeningFailurePolicy.cs} | 74 ++++---- ... => MessageListeningRetryDurationScale.cs} | 4 +- ...licy.cs => MessageListeningRetryPolicy.cs} | 63 ++++--- ...ssageListeningSecondaryFailureBehavior.cs} | 4 +- .../MessageProcessingAttemptResult.cs | 2 +- .../MessageProcessingInformation.cs | 12 +- .../MessageRequestingFacade.cs | 120 ++++++------- ...ion.cs => MessageTransmissionException.cs} | 24 +-- ...sagePublisher.cs => MessageTransmitter.cs} | 56 +++--- ...Facade.cs => MessageTransmittingFacade.cs} | 94 +++++----- .../MessagingClientFactory.cs | 24 +-- .../MessagingEntityType.cs | 2 +- .../{QueueSubscriber.cs => QueueListener.cs} | 12 +- ...{QueuePublisher.cs => QueueTransmitter.cs} | 14 +- ...equestSubscriber.cs => RequestListener.cs} | 14 +- ...uestPublisher.cs => RequestTransmitter.cs} | 16 +- .../Service/HeartbeatMessage.cs | 8 +- .../Service/HeartbeatSchedule.cs | 24 +-- .../Service/HeartbeatScheduleItem.cs | 42 ++--- .../Service/IHeartbeatScheduleItem.cs | 20 +-- ...Profile.cs => IMessageListeningProfile.cs} | 10 +- ...nProfile.cs => MessageListeningProfile.cs} | 44 ++--- .../Service/MessagingServiceExecutor.cs | 124 ++++++------- .../{TopicSubscriber.cs => TopicListener.cs} | 12 +- ...{TopicPublisher.cs => TopicTransmitter.cs} | 14 +- .../MessageProcessingInformationTests.cs | 12 +- 50 files changed, 728 insertions(+), 728 deletions(-) rename example/RapidField.SolidInstruments.Example.Domain/{MessageSubscribers/ApplicationStartedEventMessageSubscriber.cs => MessageListeners/ApplicationStartedEventMessageListener.cs} (83%) rename example/RapidField.SolidInstruments.Example.Domain/{MessageSubscribers/ApplicationStoppedEventMessageSubscriber.cs => MessageListeners/ApplicationStoppedEventMessageListener.cs} (83%) rename example/RapidField.SolidInstruments.Example.Domain/{MessageSubscribers/ExceptionRaisedEventMessageSubscriber.cs => MessageListeners/ExceptionRaisedEventMessageListener.cs} (83%) rename example/RapidField.SolidInstruments.Example.Domain/{MessageSubscribers/HeartbeatMessageSubscriber.cs => MessageListeners/HeartbeatMessageListener.cs} (88%) rename example/RapidField.SolidInstruments.Example.Domain/{MessageSubscribers/PingRequestMessageSubscriber.cs => MessageListeners/PingRequestMessageListener.cs} (85%) rename src/RapidField.SolidInstruments.Messaging.AzureServiceBus/{AzureServiceBusSubscribingFacade.cs => AzureServiceBusListeningFacade.cs} (80%) rename src/RapidField.SolidInstruments.Messaging.AzureServiceBus/{AzureServiceBusPublishingFacade.cs => AzureServiceBusTransmittingFacade.cs} (74%) rename src/RapidField.SolidInstruments.Messaging/{IMessageSubscriber.cs => IMessageListener.cs} (64%) rename src/RapidField.SolidInstruments.Messaging/{IMessageSubscribingFacade.cs => IMessageListeningFacade.cs} (85%) rename src/RapidField.SolidInstruments.Messaging/{IMessagePublisher.cs => IMessageTransmitter.cs} (62%) rename src/RapidField.SolidInstruments.Messaging/{IMessagePublishingFacade.cs => IMessageTransmittingFacade.cs} (69%) rename src/RapidField.SolidInstruments.Messaging/{MessageSubscriber.cs => MessageListener.cs} (68%) rename src/RapidField.SolidInstruments.Messaging/{MessageSubscribingException.cs => MessageListeningException.cs} (78%) rename src/RapidField.SolidInstruments.Messaging/{MessageSubscribingFacade.cs => MessageListeningFacade.cs} (79%) rename src/RapidField.SolidInstruments.Messaging/{MessageSubscribingFailurePolicy.cs => MessageListeningFailurePolicy.cs} (63%) rename src/RapidField.SolidInstruments.Messaging/{MessageSubscribingRetryDurationScale.cs => MessageListeningRetryDurationScale.cs} (91%) rename src/RapidField.SolidInstruments.Messaging/{MessageSubscribingRetryPolicy.cs => MessageListeningRetryPolicy.cs} (62%) rename src/RapidField.SolidInstruments.Messaging/{MessageSubscribingSecondaryFailureBehavior.cs => MessageListeningSecondaryFailureBehavior.cs} (85%) rename src/RapidField.SolidInstruments.Messaging/{MessagePublishingException.cs => MessageTransmissionException.cs} (78%) rename src/RapidField.SolidInstruments.Messaging/{MessagePublisher.cs => MessageTransmitter.cs} (75%) rename src/RapidField.SolidInstruments.Messaging/{MessagePublishingFacade.cs => MessageTransmittingFacade.cs} (70%) rename src/RapidField.SolidInstruments.Messaging/{QueueSubscriber.cs => QueueListener.cs} (78%) rename src/RapidField.SolidInstruments.Messaging/{QueuePublisher.cs => QueueTransmitter.cs} (84%) rename src/RapidField.SolidInstruments.Messaging/{RequestSubscriber.cs => RequestListener.cs} (78%) rename src/RapidField.SolidInstruments.Messaging/{RequestPublisher.cs => RequestTransmitter.cs} (82%) rename src/RapidField.SolidInstruments.Messaging/Service/{IMessageSubscriptionProfile.cs => IMessageListeningProfile.cs} (90%) rename src/RapidField.SolidInstruments.Messaging/Service/{MessageSubscriptionProfile.cs => MessageListeningProfile.cs} (80%) rename src/RapidField.SolidInstruments.Messaging/{TopicSubscriber.cs => TopicListener.cs} (78%) rename src/RapidField.SolidInstruments.Messaging/{TopicPublisher.cs => TopicTransmitter.cs} (84%) diff --git a/LICENSE.txt b/LICENSE.txt index 33d739e1..8c591691 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -5,7 +5,7 @@ Copyright (c) 2019 RapidField LLC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +to use, copy, modify, merge, transmit, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/example/RapidField.SolidInstruments.Example.Contracts/ExampleContractsDependencyModule.cs b/example/RapidField.SolidInstruments.Example.Contracts/ExampleContractsDependencyModule.cs index 3ecd70ab..37cb80e2 100644 --- a/example/RapidField.SolidInstruments.Example.Contracts/ExampleContractsDependencyModule.cs +++ b/example/RapidField.SolidInstruments.Example.Contracts/ExampleContractsDependencyModule.cs @@ -44,15 +44,15 @@ public ExampleContractsDependencyModule(IConfiguration applicationConfiguration) /// protected override void Configure(ServiceCollection configurator, IConfiguration applicationConfiguration) { - // Register queue publishers. - configurator.AddTransient, QueuePublisher>(); - configurator.AddTransient, QueuePublisher>(); + // Register queue transmitters. + configurator.AddTransient, QueueTransmitter>(); + configurator.AddTransient, QueueTransmitter>(); - // Register topic publishers. - configurator.AddTransient, TopicPublisher>(); + // Register topic transmitters. + configurator.AddTransient, TopicTransmitter>(); - // Register request publishers. - configurator.AddTransient, RequestPublisher>(); + // Register request transmitters. + configurator.AddTransient, RequestTransmitter>(); } } } \ No newline at end of file diff --git a/example/RapidField.SolidInstruments.Example.Domain/ExampleDomainDependencyModule.cs b/example/RapidField.SolidInstruments.Example.Domain/ExampleDomainDependencyModule.cs index 31fbd04e..93ce0a27 100644 --- a/example/RapidField.SolidInstruments.Example.Domain/ExampleDomainDependencyModule.cs +++ b/example/RapidField.SolidInstruments.Example.Domain/ExampleDomainDependencyModule.cs @@ -5,7 +5,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using RapidField.SolidInstruments.Example.Contracts.Messages; -using RapidField.SolidInstruments.Example.Domain.MessageSubscribers; +using RapidField.SolidInstruments.Example.Domain.MessageListeners; using RapidField.SolidInstruments.InversionOfControl.DotNetNative; using RapidField.SolidInstruments.Messaging; using RapidField.SolidInstruments.Messaging.EventMessages; @@ -45,16 +45,16 @@ public ExampleDomainDependencyModule(IConfiguration applicationConfiguration) /// protected override void Configure(ServiceCollection configurator, IConfiguration applicationConfiguration) { - // Register queue subscribers. - configurator.AddTransient, ApplicationStartedEventMessageSubscriber>(); - configurator.AddTransient, ApplicationStoppedEventMessageSubscriber>(); - configurator.AddTransient, ExceptionRaisedEventMessageSubscriber>(); + // Register queue listeners. + configurator.AddTransient, ApplicationStartedEventMessageListener>(); + configurator.AddTransient, ApplicationStoppedEventMessageListener>(); + configurator.AddTransient, ExceptionRaisedEventMessageListener>(); - // Register topic subscribers. - configurator.AddTransient, HeartbeatMessageSubscriber>(); + // Register topic listeners. + configurator.AddTransient, HeartbeatMessageListener>(); - // Register request subscribers. - configurator.AddTransient, PingRequestMessageSubscriber>(); + // Register request listeners. + configurator.AddTransient, PingRequestMessageListener>(); } } } \ No newline at end of file diff --git a/example/RapidField.SolidInstruments.Example.Domain/MessageSubscribers/ApplicationStartedEventMessageSubscriber.cs b/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/ApplicationStartedEventMessageListener.cs similarity index 83% rename from example/RapidField.SolidInstruments.Example.Domain/MessageSubscribers/ApplicationStartedEventMessageSubscriber.cs rename to example/RapidField.SolidInstruments.Example.Domain/MessageListeners/ApplicationStartedEventMessageListener.cs index 7baff2aa..0cfb4f8c 100644 --- a/example/RapidField.SolidInstruments.Example.Domain/MessageSubscribers/ApplicationStartedEventMessageSubscriber.cs +++ b/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/ApplicationStartedEventMessageListener.cs @@ -8,15 +8,15 @@ using RapidField.SolidInstruments.Messaging.EventMessages; using System; -namespace RapidField.SolidInstruments.Example.Domain.MessageSubscribers +namespace RapidField.SolidInstruments.Example.Domain.MessageListeners { /// - /// Subscribes to and processes instances. + /// Listens for and processes instances. /// - public sealed class ApplicationStartedEventMessageSubscriber : QueueSubscriber + public sealed class ApplicationStartedEventMessageListener : QueueListener { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// A processing intermediary that is used to process sub-commands. @@ -24,14 +24,14 @@ public sealed class ApplicationStartedEventMessageSubscriber : QueueSubscriber /// is . /// - public ApplicationStartedEventMessageSubscriber(ICommandMediator mediator) + public ApplicationStartedEventMessageListener(ICommandMediator mediator) : base(mediator) { return; } /// - /// Releases all resources consumed by the current . + /// Releases all resources consumed by the current . /// /// /// A value indicating whether or not managed resources should be released. diff --git a/example/RapidField.SolidInstruments.Example.Domain/MessageSubscribers/ApplicationStoppedEventMessageSubscriber.cs b/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/ApplicationStoppedEventMessageListener.cs similarity index 83% rename from example/RapidField.SolidInstruments.Example.Domain/MessageSubscribers/ApplicationStoppedEventMessageSubscriber.cs rename to example/RapidField.SolidInstruments.Example.Domain/MessageListeners/ApplicationStoppedEventMessageListener.cs index 29fa752f..648c5457 100644 --- a/example/RapidField.SolidInstruments.Example.Domain/MessageSubscribers/ApplicationStoppedEventMessageSubscriber.cs +++ b/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/ApplicationStoppedEventMessageListener.cs @@ -8,15 +8,15 @@ using RapidField.SolidInstruments.Messaging.EventMessages; using System; -namespace RapidField.SolidInstruments.Example.Domain.MessageSubscribers +namespace RapidField.SolidInstruments.Example.Domain.MessageListeners { /// - /// Subscribes to and processes instances. + /// Listens for and processes instances. /// - public sealed class ApplicationStoppedEventMessageSubscriber : QueueSubscriber + public sealed class ApplicationStoppedEventMessageListener : QueueListener { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// A processing intermediary that is used to process sub-commands. @@ -24,14 +24,14 @@ public sealed class ApplicationStoppedEventMessageSubscriber : QueueSubscriber /// is . /// - public ApplicationStoppedEventMessageSubscriber(ICommandMediator mediator) + public ApplicationStoppedEventMessageListener(ICommandMediator mediator) : base(mediator) { return; } /// - /// Releases all resources consumed by the current . + /// Releases all resources consumed by the current . /// /// /// A value indicating whether or not managed resources should be released. diff --git a/example/RapidField.SolidInstruments.Example.Domain/MessageSubscribers/ExceptionRaisedEventMessageSubscriber.cs b/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/ExceptionRaisedEventMessageListener.cs similarity index 83% rename from example/RapidField.SolidInstruments.Example.Domain/MessageSubscribers/ExceptionRaisedEventMessageSubscriber.cs rename to example/RapidField.SolidInstruments.Example.Domain/MessageListeners/ExceptionRaisedEventMessageListener.cs index b8790af7..549d6e61 100644 --- a/example/RapidField.SolidInstruments.Example.Domain/MessageSubscribers/ExceptionRaisedEventMessageSubscriber.cs +++ b/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/ExceptionRaisedEventMessageListener.cs @@ -8,15 +8,15 @@ using RapidField.SolidInstruments.Messaging.EventMessages; using System; -namespace RapidField.SolidInstruments.Example.Domain.MessageSubscribers +namespace RapidField.SolidInstruments.Example.Domain.MessageListeners { /// - /// Subscribes to and processes instances. + /// Listens for and processes instances. /// - public sealed class ExceptionRaisedEventMessageSubscriber : QueueSubscriber + public sealed class ExceptionRaisedEventMessageListener : QueueListener { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// A processing intermediary that is used to process sub-commands. @@ -24,14 +24,14 @@ public sealed class ExceptionRaisedEventMessageSubscriber : QueueSubscriber /// is . /// - public ExceptionRaisedEventMessageSubscriber(ICommandMediator mediator) + public ExceptionRaisedEventMessageListener(ICommandMediator mediator) : base(mediator) { return; } /// - /// Releases all resources consumed by the current . + /// Releases all resources consumed by the current . /// /// /// A value indicating whether or not managed resources should be released. diff --git a/example/RapidField.SolidInstruments.Example.Domain/MessageSubscribers/HeartbeatMessageSubscriber.cs b/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/HeartbeatMessageListener.cs similarity index 88% rename from example/RapidField.SolidInstruments.Example.Domain/MessageSubscribers/HeartbeatMessageSubscriber.cs rename to example/RapidField.SolidInstruments.Example.Domain/MessageListeners/HeartbeatMessageListener.cs index b7699f6e..61ffd0cb 100644 --- a/example/RapidField.SolidInstruments.Example.Domain/MessageSubscribers/HeartbeatMessageSubscriber.cs +++ b/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/HeartbeatMessageListener.cs @@ -10,15 +10,15 @@ using System; using System.Diagnostics; -namespace RapidField.SolidInstruments.Example.Domain.MessageSubscribers +namespace RapidField.SolidInstruments.Example.Domain.MessageListeners { /// - /// Subscribes to and processes instances. + /// Listens for and processes instances. /// - public sealed class HeartbeatMessageSubscriber : TopicSubscriber + public sealed class HeartbeatMessageListener : TopicListener { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// A processing intermediary that is used to process sub-commands. @@ -26,14 +26,14 @@ public sealed class HeartbeatMessageSubscriber : TopicSubscriber /// is . /// - public HeartbeatMessageSubscriber(ICommandMediator mediator) + public HeartbeatMessageListener(ICommandMediator mediator) : base(mediator) { return; } /// - /// Releases all resources consumed by the current . + /// Releases all resources consumed by the current . /// /// /// A value indicating whether or not managed resources should be released. diff --git a/example/RapidField.SolidInstruments.Example.Domain/MessageSubscribers/PingRequestMessageSubscriber.cs b/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/PingRequestMessageListener.cs similarity index 85% rename from example/RapidField.SolidInstruments.Example.Domain/MessageSubscribers/PingRequestMessageSubscriber.cs rename to example/RapidField.SolidInstruments.Example.Domain/MessageListeners/PingRequestMessageListener.cs index a0a70e90..7f3e3bcd 100644 --- a/example/RapidField.SolidInstruments.Example.Domain/MessageSubscribers/PingRequestMessageSubscriber.cs +++ b/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/PingRequestMessageListener.cs @@ -8,15 +8,15 @@ using RapidField.SolidInstruments.Messaging; using System; -namespace RapidField.SolidInstruments.Example.Domain.MessageSubscribers +namespace RapidField.SolidInstruments.Example.Domain.MessageListeners { /// - /// Subscribes to and processes instances. + /// Listens for and processes instances. /// - public sealed class PingRequestMessageSubscriber : RequestSubscriber + public sealed class PingRequestMessageListener : RequestListener { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// A processing intermediary that is used to process sub-commands. @@ -24,14 +24,14 @@ public sealed class PingRequestMessageSubscriber : RequestSubscriber /// is . /// - public PingRequestMessageSubscriber(ICommandMediator mediator) + public PingRequestMessageListener(ICommandMediator mediator) : base(mediator) { return; } /// - /// Releases all resources consumed by the current . + /// Releases all resources consumed by the current . /// /// /// A value indicating whether or not managed resources should be released. diff --git a/example/RapidField.SolidInstruments.Example.ServiceApplication/ApplicationDependencyModule.cs b/example/RapidField.SolidInstruments.Example.ServiceApplication/ApplicationDependencyModule.cs index 96f060d6..52465efc 100644 --- a/example/RapidField.SolidInstruments.Example.ServiceApplication/ApplicationDependencyModule.cs +++ b/example/RapidField.SolidInstruments.Example.ServiceApplication/ApplicationDependencyModule.cs @@ -68,10 +68,10 @@ protected override void Configure(ServiceCollection configurator, IConfiguration configurator.AddScoped, AzureServiceBusMessageAdapter>((serviceProvider) => serviceProvider.GetService()); configurator.AddScoped(); configurator.AddScoped, AzureServiceBusClientFactory>((serviceProvider) => serviceProvider.GetService()); - configurator.AddScoped(); - configurator.AddScoped((serviceProvider) => serviceProvider.GetService()); - configurator.AddSingleton(); - configurator.AddSingleton((serviceProvider) => serviceProvider.GetService()); + configurator.AddScoped(); + configurator.AddScoped((serviceProvider) => serviceProvider.GetService()); + configurator.AddSingleton(); + configurator.AddSingleton((serviceProvider) => serviceProvider.GetService()); configurator.AddSingleton(); configurator.AddSingleton((serviceProvider) => serviceProvider.GetService()); } diff --git a/example/RapidField.SolidInstruments.Example.ServiceApplication/ExampleMessagingServiceExecutor.cs b/example/RapidField.SolidInstruments.Example.ServiceApplication/ExampleMessagingServiceExecutor.cs index c21c2b8d..c5a64d0f 100644 --- a/example/RapidField.SolidInstruments.Example.ServiceApplication/ExampleMessagingServiceExecutor.cs +++ b/example/RapidField.SolidInstruments.Example.ServiceApplication/ExampleMessagingServiceExecutor.cs @@ -42,20 +42,20 @@ public ExampleMessagingServiceExecutor() /// /// Configuration information for the service application. /// - protected override void AddSubscriptions(IMessageSubscriptionProfile subscriptionProfile, IConfiguration applicationConfiguration) + protected override void AddSubscriptions(IMessageListeningProfile subscriptionProfile, IConfiguration applicationConfiguration) { try { - // Add queue subscribers. - subscriptionProfile.AddQueueSubscriber(); - subscriptionProfile.AddQueueSubscriber(); - subscriptionProfile.AddQueueSubscriber(); + // Add queue listeners. + subscriptionProfile.AddQueueListener(); + subscriptionProfile.AddQueueListener(); + subscriptionProfile.AddQueueListener(); - // Add topic subscribers. - subscriptionProfile.AddTopicSubscriber(); + // Add topic listeners. + subscriptionProfile.AddTopicListener(); - // Add request subscribers. - subscriptionProfile.AddRequestSubscriber(); + // Add request listeners. + subscriptionProfile.AddRequestListener(); } finally { @@ -82,10 +82,10 @@ protected override void BuildConfiguration(IConfigurationBuilder configurationBu } /// - /// Configures the service to publish heartbeat messages. + /// Configures the service to transmit heartbeat messages. /// /// - /// An object that defines how the service publishes heartbeat messages. + /// An object that defines how the service transmits heartbeat messages. /// /// /// Configuration information for the service application. diff --git a/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusSubscribingFacade.cs b/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusListeningFacade.cs similarity index 80% rename from src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusSubscribingFacade.cs rename to src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusListeningFacade.cs index 8772435c..b24088c5 100644 --- a/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusSubscribingFacade.cs +++ b/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusListeningFacade.cs @@ -15,27 +15,27 @@ namespace RapidField.SolidInstruments.Messaging.AzureServiceBus { /// - /// Facilitates subscribing operations for Azure Service Bus queues. + /// Facilitates listening operations for Azure Service Bus queues. /// - public sealed class AzureServiceBusSubscribingFacade : MessageSubscribingFacade + public sealed class AzureServiceBusListeningFacade : MessageListeningFacade { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// - /// An implementation-specific messaging facade that is used to publish response messages. + /// + /// An implementation-specific messaging facade that is used to transmit response messages. /// /// - /// is . + /// is . /// - public AzureServiceBusSubscribingFacade(AzureServiceBusPublishingFacade publishingFacade) - : base(publishingFacade) + public AzureServiceBusListeningFacade(AzureServiceBusTransmittingFacade transmittingFacade) + : base(transmittingFacade) { return; } /// - /// Releases all resources consumed by the current . + /// Releases all resources consumed by the current . /// /// /// A value indicating whether or not managed resources should be released. @@ -66,11 +66,11 @@ protected sealed override void RegisterMessageHandler(Action /// - /// An exception was raised while trying to publish an . + /// An exception was raised while trying to transmit an . /// [DebuggerHidden] - private static Task HandleReceiverExceptionAsync(ExceptionReceivedEventArgs exceptionReceivedArguments) => Task.CompletedTask; + private static Task HandleReceiverExceptionAsync(ExceptionReceivedEventArgs exceptionReceivedArguments) => Task.CompletedTask; // TODO /// /// Gets options that specify how receive clients handle messages. diff --git a/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusRequestingFacade.cs b/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusRequestingFacade.cs index 99ac3236..dfbefcc3 100644 --- a/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusRequestingFacade.cs +++ b/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusRequestingFacade.cs @@ -11,19 +11,19 @@ namespace RapidField.SolidInstruments.Messaging.AzureServiceBus /// /// Facilitates requesting operations for Azure Service Bus. /// - public sealed class AzureServiceBusRequestingFacade : MessageRequestingFacade + public sealed class AzureServiceBusRequestingFacade : MessageRequestingFacade { /// /// Initializes a new instance of the class. /// - /// - /// An implementation-specific messaging facade that subscribes to request messages. + /// + /// An implementation-specific messaging facade that listens for request messages. /// /// - /// is . + /// is . /// - public AzureServiceBusRequestingFacade(AzureServiceBusSubscribingFacade subscribingFacade) - : base(subscribingFacade) + public AzureServiceBusRequestingFacade(AzureServiceBusListeningFacade listeningFacade) + : base(listeningFacade) { return; } diff --git a/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusPublishingFacade.cs b/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusTransmittingFacade.cs similarity index 74% rename from src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusPublishingFacade.cs rename to src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusTransmittingFacade.cs index 44898ee0..f97e5764 100644 --- a/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusPublishingFacade.cs +++ b/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusTransmittingFacade.cs @@ -11,12 +11,12 @@ namespace RapidField.SolidInstruments.Messaging.AzureServiceBus { /// - /// Facilitates publishing operations for Azure Service Bus queues. + /// Facilitates transmission operations for Azure Service Bus queues. /// - public sealed class AzureServiceBusPublishingFacade : MessagePublishingFacade + public sealed class AzureServiceBusTransmittingFacade : MessageTransmittingFacade { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// An appliance that creates manages implementation-specific messaging clients. @@ -28,14 +28,14 @@ public sealed class AzureServiceBusPublishingFacade : MessagePublishingFacade is -or- is /// . /// - public AzureServiceBusPublishingFacade(IMessagingClientFactory clientFactory, IMessageAdapter messageAdapter) + public AzureServiceBusTransmittingFacade(IMessagingClientFactory clientFactory, IMessageAdapter messageAdapter) : base(clientFactory, messageAdapter) { return; } /// - /// Releases all resources consumed by the current . + /// Releases all resources consumed by the current . /// /// /// A value indicating whether or not managed resources should be released. @@ -43,10 +43,10 @@ public AzureServiceBusPublishingFacade(IMessagingClientFactory base.Dispose(disposing); /// - /// Asynchronously publishes the specified message to a bus. + /// Asynchronously transmits the specified message to a bus. /// /// - /// The message to publish. + /// The message to transmit. /// /// /// An implementation-specific receive client. @@ -57,6 +57,6 @@ public AzureServiceBusPublishingFacade(IMessagingClientFactory /// A task representing the asynchronous operation. /// - protected sealed override Task PublishAsync(AzureServiceBusMessage message, ISenderClient sendClient, ConcurrencyControlToken controlToken) => sendClient.SendAsync(message); + protected sealed override Task TransmitAsync(AzureServiceBusMessage message, ISenderClient sendClient, ConcurrencyControlToken controlToken) => sendClient.SendAsync(message); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/IMessageSubscriber.cs b/src/RapidField.SolidInstruments.Messaging/IMessageListener.cs similarity index 64% rename from src/RapidField.SolidInstruments.Messaging/IMessageSubscriber.cs rename to src/RapidField.SolidInstruments.Messaging/IMessageListener.cs index b66450b6..c2e082f7 100644 --- a/src/RapidField.SolidInstruments.Messaging/IMessageSubscriber.cs +++ b/src/RapidField.SolidInstruments.Messaging/IMessageListener.cs @@ -8,12 +8,12 @@ namespace RapidField.SolidInstruments.Messaging { /// - /// Processes messages as a subscriber. + /// Processes messages as a listener. /// - public interface IMessageSubscriber + public interface IMessageListener { /// - /// Gets the type of the message that the current processes. + /// Gets the type of the message that the current processes. /// Type MessageType { @@ -22,26 +22,26 @@ Type MessageType } /// - /// Processes messages as a subscriber. + /// Processes messages as a listener. /// /// - /// The type of the message that is subscribed to. + /// The type of the message that is listened for. /// - public interface IMessageSubscriber : IMessageSubscriber, IMessageHandler, ICommandHandler + public interface IMessageListener : IMessageListener, IMessageHandler, ICommandHandler where TMessage : class, IMessage { } /// - /// Processes messages as a subscriber. + /// Processes messages as a listener. /// /// - /// The type of the request message that is processed by the subscriber. + /// The type of the request message that is processed by the listener. /// /// - /// The type of the response message that is published in response to the request. + /// The type of the response message that is transmitted in response to the request. /// - public interface IMessageSubscriber : IMessageSubscriber, IMessageHandler, ICommandHandler + public interface IMessageListener : IMessageListener, IMessageHandler, ICommandHandler where TRequestMessage : class, IRequestMessage where TResponseMessage : class, IResponseMessage { diff --git a/src/RapidField.SolidInstruments.Messaging/IMessageSubscribingFacade.cs b/src/RapidField.SolidInstruments.Messaging/IMessageListeningFacade.cs similarity index 85% rename from src/RapidField.SolidInstruments.Messaging/IMessageSubscribingFacade.cs rename to src/RapidField.SolidInstruments.Messaging/IMessageListeningFacade.cs index dedd752e..b5d981a2 100644 --- a/src/RapidField.SolidInstruments.Messaging/IMessageSubscribingFacade.cs +++ b/src/RapidField.SolidInstruments.Messaging/IMessageListeningFacade.cs @@ -8,7 +8,7 @@ namespace RapidField.SolidInstruments.Messaging { /// - /// Facilitates implementation-specific subscribing operations for a message bus. + /// Facilitates implementation-specific listening operations for a message bus. /// /// /// The type of the implementation-specific send client. @@ -19,17 +19,17 @@ namespace RapidField.SolidInstruments.Messaging /// /// The type of implementation-specific adapted messages. /// - /// - /// The type of the implementation-specific messaging facade that is used to publish request messages. + /// + /// The type of the implementation-specific messaging facade that is used to transmit request messages. /// - public interface IMessageSubscribingFacade : IMessagingFacade + public interface IMessageListeningFacade : IMessagingFacade where TAdaptedMessage : class - where TPublishingFacade : MessagePublishingFacade + where TTransmittingFacade : MessageTransmittingFacade { } /// - /// Facilitates implementation-specific subscribing operations for a message bus. + /// Facilitates implementation-specific listening operations for a message bus. /// /// /// The type of the implementation-specific send client. @@ -40,15 +40,15 @@ public interface IMessageSubscribingFacade /// The type of implementation-specific adapted messages. /// - public interface IMessageSubscribingFacade : IMessageSubscribingFacade, IMessagingFacade + public interface IMessageListeningFacade : IMessageListeningFacade, IMessagingFacade where TAdaptedMessage : class { } /// - /// Facilitates implementation-specific subscribing operations for a message bus. + /// Facilitates implementation-specific listening operations for a message bus. /// - public interface IMessageSubscribingFacade : IMessagingFacade + public interface IMessageListeningFacade : IMessagingFacade { /// /// Registers the specified queue message handler with the bus. @@ -62,7 +62,7 @@ public interface IMessageSubscribingFacade : IMessagingFacade /// /// is . /// - /// + /// /// An exception was raised while attempting to register . /// /// @@ -90,7 +90,7 @@ void RegisterQueueMessageHandler(Action messageHandler) /// /// is . /// - /// + /// /// An exception was raised while attempting to register . /// /// @@ -114,7 +114,7 @@ void RegisterQueueMessageHandler(Action messageHandler, IEnu /// /// is . /// - /// + /// /// An exception was raised while attempting to register . /// /// @@ -136,7 +136,7 @@ void RegisterRequestMessageHandler(Func /// is . /// - /// + /// /// An exception was raised while attempting to register . /// /// @@ -164,7 +164,7 @@ void RegisterTopicMessageHandler(Action messageHandler) /// /// is . /// - /// + /// /// An exception was raised while attempting to register . /// /// @@ -174,10 +174,10 @@ void RegisterTopicMessageHandler(Action messageHandler, IEnu where TMessage : class, IMessage; /// - /// Gets the collection of message types for which the current has one or more + /// Gets the collection of message types for which the current has one or more /// registered handlers. /// - IEnumerable SubscribedMessageTypes + IEnumerable ListenedMessageTypes { get; } diff --git a/src/RapidField.SolidInstruments.Messaging/IMessageRequestingFacade.cs b/src/RapidField.SolidInstruments.Messaging/IMessageRequestingFacade.cs index 1da819f3..d5bf772e 100644 --- a/src/RapidField.SolidInstruments.Messaging/IMessageRequestingFacade.cs +++ b/src/RapidField.SolidInstruments.Messaging/IMessageRequestingFacade.cs @@ -19,16 +19,16 @@ namespace RapidField.SolidInstruments.Messaging /// /// The type of implementation-specific adapted messages. /// - /// - /// The type of the implementation-specific messaging facade that is used to publish request messages. + /// + /// The type of the implementation-specific messaging facade that is used to transmit request messages. /// - /// - /// The type of the implementation-specific messaging facade that subscribes to request messages. + /// + /// The type of the implementation-specific messaging facade that listens for request messages. /// - public interface IMessageRequestingFacade : IMessageRequestingFacade + public interface IMessageRequestingFacade : IMessageRequestingFacade where TAdaptedMessage : class - where TPublishingFacade : MessagePublishingFacade - where TSubscribingFacade : MessageSubscribingFacade + where TTransmittingFacade : MessageTransmittingFacade + where TListeningFacade : MessageListeningFacade { } @@ -55,7 +55,7 @@ public interface IMessageRequestingFacade : public interface IMessageRequestingFacade : IMessagingFacade { /// - /// Asynchronously publishes the specified request message to a bus and waits for the correlated response message. + /// Asynchronously transmits the specified request message to a bus and waits for the correlated response message. /// /// /// The type of the request message. @@ -64,7 +64,7 @@ public interface IMessageRequestingFacade : IMessagingFacade /// The type of the response message. /// /// - /// The request message to publish. + /// The request message to transmit. /// /// /// A task representing the asynchronous operation and containing the correlated response message. diff --git a/src/RapidField.SolidInstruments.Messaging/IMessagePublisher.cs b/src/RapidField.SolidInstruments.Messaging/IMessageTransmitter.cs similarity index 62% rename from src/RapidField.SolidInstruments.Messaging/IMessagePublisher.cs rename to src/RapidField.SolidInstruments.Messaging/IMessageTransmitter.cs index 53e485e3..74dd85b1 100644 --- a/src/RapidField.SolidInstruments.Messaging/IMessagePublisher.cs +++ b/src/RapidField.SolidInstruments.Messaging/IMessageTransmitter.cs @@ -8,12 +8,12 @@ namespace RapidField.SolidInstruments.Messaging { /// - /// Publishes messages. + /// Transmits messages. /// - public interface IMessagePublisher + public interface IMessageTransmitter { /// - /// Gets the type of the message that the current publishes. + /// Gets the type of the message that the current transmits. /// Type MessageType { @@ -22,26 +22,26 @@ Type MessageType } /// - /// Publishes messages. + /// Transmits messages. /// /// - /// The type of the message that is published by the publisher. + /// The type of the message that is transmitted by the transmitter. /// - public interface IMessagePublisher : IMessagePublisher, IMessageHandler, ICommandHandler + public interface IMessageTransmitter : IMessageTransmitter, IMessageHandler, ICommandHandler where TMessage : class, IMessage { } /// - /// Publishes messages. + /// Transmits messages. /// /// - /// The type of the request message that is published by the publisher. + /// The type of the request message that is transmitted by the transmitter. /// /// - /// The type of the response message that is published in response to the request. + /// The type of the response message that is transmitted in response to the request. /// - public interface IMessagePublisher : IMessagePublisher, IMessageHandler, ICommandHandler + public interface IMessageTransmitter : IMessageTransmitter, IMessageHandler, ICommandHandler where TRequestMessage : class, IRequestMessage where TResponseMessage : class, IResponseMessage { diff --git a/src/RapidField.SolidInstruments.Messaging/IMessagePublishingFacade.cs b/src/RapidField.SolidInstruments.Messaging/IMessageTransmittingFacade.cs similarity index 69% rename from src/RapidField.SolidInstruments.Messaging/IMessagePublishingFacade.cs rename to src/RapidField.SolidInstruments.Messaging/IMessageTransmittingFacade.cs index 9d6fd5ca..85f75374 100644 --- a/src/RapidField.SolidInstruments.Messaging/IMessagePublishingFacade.cs +++ b/src/RapidField.SolidInstruments.Messaging/IMessageTransmittingFacade.cs @@ -9,7 +9,7 @@ namespace RapidField.SolidInstruments.Messaging { /// - /// Facilitates implementation-specific publishing operations for a message bus. + /// Facilitates implementation-specific transmission operations for a message bus. /// /// /// The type of the implementation-specific send client. @@ -20,124 +20,124 @@ namespace RapidField.SolidInstruments.Messaging /// /// The type of implementation-specific adapted messages. /// - public interface IMessagePublishingFacade : IMessagePublishingFacade, IMessagingFacade + public interface IMessageTransmittingFacade : IMessageTransmittingFacade, IMessagingFacade where TAdaptedMessage : class { } /// - /// Facilitates implementation-specific publishing operations for a message bus. + /// Facilitates implementation-specific transmission operations for a message bus. /// - public interface IMessagePublishingFacade : IMessagingFacade + public interface IMessageTransmittingFacade : IMessagingFacade { /// - /// Asynchronously publishes the specified message to a queue. + /// Asynchronously transmits the specified message to a queue. /// /// - /// The type of the message to publish. + /// The type of the message to transmit. /// /// - /// The message to publish. + /// The message to transmit. + /// + /// + /// An ordered collection of non-null, non-empty alphanumeric string tokens from which to construct the path, or + /// to omit path tokens. The default value is . /// /// /// A task representing the asynchronous operation. /// + /// + /// contains one or more null or empty tokens and/or tokens with non-alphanumeric characters. + /// /// /// is . /// - /// - /// An exception was raised while attempting to publish . + /// + /// An exception was raised while attempting to transmit . /// /// /// The object is disposed. /// - Task PublishToQueueAsync(TMessage message) + Task TransmitToQueueAsync(TMessage message, IEnumerable pathTokens) where TMessage : class, IMessageBase; /// - /// Asynchronously publishes the specified message to a queue. + /// Asynchronously transmits the specified message to a queue. /// /// - /// The type of the message to publish. + /// The type of the message to transmit. /// /// - /// The message to publish. - /// - /// - /// An ordered collection of non-null, non-empty alphanumeric string tokens from which to construct the path, or - /// to omit path tokens. The default value is . + /// The message to transmit. /// /// /// A task representing the asynchronous operation. /// - /// - /// contains one or more null or empty tokens and/or tokens with non-alphanumeric characters. - /// /// /// is . /// - /// - /// An exception was raised while attempting to publish . + /// + /// An exception was raised while attempting to transmit . /// /// /// The object is disposed. /// - Task PublishToQueueAsync(TMessage message, IEnumerable pathTokens) + Task TransmitToQueueAsync(TMessage message) where TMessage : class, IMessageBase; /// - /// Asynchronously publishes the specified message to a topic. + /// Asynchronously transmits the specified message to a topic. /// /// - /// The type of the message to publish. + /// The type of the message to transmit. /// /// - /// The message to publish. + /// The message to transmit. + /// + /// + /// An ordered collection of non-null, non-empty alphanumeric string tokens from which to construct the path, or + /// to omit path tokens. The default value is . /// /// /// A task representing the asynchronous operation. /// + /// + /// contains one or more null or empty tokens and/or tokens with non-alphanumeric characters. + /// /// /// is . /// - /// - /// An exception was raised while attempting to publish . + /// + /// An exception was raised while attempting to transmit . /// /// /// The object is disposed. /// - Task PublishToTopicAsync(TMessage message) + Task TransmitToTopicAsync(TMessage message, IEnumerable pathTokens) where TMessage : class, IMessageBase; /// - /// Asynchronously publishes the specified message to a topic. + /// Asynchronously transmits the specified message to a topic. /// /// - /// The type of the message to publish. + /// The type of the message to transmit. /// /// - /// The message to publish. - /// - /// - /// An ordered collection of non-null, non-empty alphanumeric string tokens from which to construct the path, or - /// to omit path tokens. The default value is . + /// The message to transmit. /// /// /// A task representing the asynchronous operation. /// - /// - /// contains one or more null or empty tokens and/or tokens with non-alphanumeric characters. - /// /// /// is . /// - /// - /// An exception was raised while attempting to publish . + /// + /// An exception was raised while attempting to transmit . /// /// /// The object is disposed. /// - Task PublishToTopicAsync(TMessage message, IEnumerable pathTokens) + Task TransmitToTopicAsync(TMessage message) where TMessage : class, IMessageBase; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/IMessagingClientFactory.cs b/src/RapidField.SolidInstruments.Messaging/IMessagingClientFactory.cs index f391de33..a05afb8e 100644 --- a/src/RapidField.SolidInstruments.Messaging/IMessagingClientFactory.cs +++ b/src/RapidField.SolidInstruments.Messaging/IMessagingClientFactory.cs @@ -32,7 +32,7 @@ public interface IMessagingClientFactory /// /// The managed, implementation-specific message receiver. /// - /// + /// /// An exception was raised while creating the client. /// /// @@ -57,7 +57,7 @@ TReceiver GetQueueReceiver() /// /// contains one or more null or empty tokens and/or tokens with non-alphanumeric characters. /// - /// + /// /// An exception was raised while creating the client. /// /// @@ -75,7 +75,7 @@ TReceiver GetQueueReceiver(IEnumerable pathTokens) /// /// The managed, implementation-specific message sender. /// - /// + /// /// An exception was raised while creating the client. /// /// @@ -100,7 +100,7 @@ TSender GetQueueSender() /// /// contains one or more null or empty tokens and/or tokens with non-alphanumeric characters. /// - /// + /// /// An exception was raised while creating the client. /// /// @@ -127,7 +127,7 @@ TSender GetQueueSender(IEnumerable pathTokens) /// /// is . /// - /// + /// /// An exception was raised while creating the client. /// /// @@ -161,7 +161,7 @@ TReceiver GetTopicReceiver(String receiverIdentifier) /// /// is . /// - /// + /// /// An exception was raised while creating the client. /// /// @@ -179,7 +179,7 @@ TReceiver GetTopicReceiver(String receiverIdentifier, IEnumerable /// The managed, implementation-specific message sender. /// - /// + /// /// An exception was raised while creating the client. /// /// @@ -204,7 +204,7 @@ TSender GetTopicSender() /// /// contains one or more null or empty tokens and/or tokens with non-alphanumeric characters. /// - /// + /// /// An exception was raised while creating the client. /// /// diff --git a/src/RapidField.SolidInstruments.Messaging/Message.cs b/src/RapidField.SolidInstruments.Messaging/Message.cs index cb03b8c0..64123503 100644 --- a/src/RapidField.SolidInstruments.Messaging/Message.cs +++ b/src/RapidField.SolidInstruments.Messaging/Message.cs @@ -203,13 +203,13 @@ public MessageProcessingInformation ProcessingInformation public virtual Type ResultType => Nix.Type; /// - /// Represents the entity type that is used for publishing and subscribing to request messages. + /// Represents the entity type that is used for transmitting and listening for request messages. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal static MessagingEntityType RequestEntityType = MessagingEntityType.Queue; /// - /// Represents the entity type that is used for publishing and subscribing to response messages. + /// Represents the entity type that is used for transmitting and listening for response messages. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal static MessagingEntityType ResponseEntityType = MessagingEntityType.Topic; diff --git a/src/RapidField.SolidInstruments.Messaging/MessageHandlerRole.cs b/src/RapidField.SolidInstruments.Messaging/MessageHandlerRole.cs index 4dc3fc32..c11b6ce6 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessageHandlerRole.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessageHandlerRole.cs @@ -17,13 +17,13 @@ public enum MessageHandlerRole : Int32 Unspecified = 0, /// - /// The handler is a publisher. + /// The handler is a transmitter. /// - Publisher = 1, + Transmitter = 1, /// - /// The handler is a subscriber. + /// The handler is a listener. /// - Subscriber = 2 + Listener = 2 } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/MessageSubscriber.cs b/src/RapidField.SolidInstruments.Messaging/MessageListener.cs similarity index 68% rename from src/RapidField.SolidInstruments.Messaging/MessageSubscriber.cs rename to src/RapidField.SolidInstruments.Messaging/MessageListener.cs index 65e4faad..79ea734d 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessageSubscriber.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessageListener.cs @@ -8,19 +8,19 @@ namespace RapidField.SolidInstruments.Messaging { /// - /// Processes messages as a subscriber. + /// Processes messages as a listener. /// /// - /// is the default implementation of . + /// is the default implementation of . /// /// - /// The type of the message that is subscribed to. + /// The type of the message that is listened for. /// - public abstract class MessageSubscriber : MessageHandler, IMessageSubscriber + public abstract class MessageListener : MessageHandler, IMessageListener where TMessage : class, IMessage { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// A processing intermediary that is used to process sub-commands. @@ -34,14 +34,14 @@ public abstract class MessageSubscriber : MessageHandler, IM /// /// is equal to . /// - protected MessageSubscriber(ICommandMediator mediator, MessagingEntityType entityType) - : base(mediator, MessageHandlerRole.Subscriber, entityType) + protected MessageListener(ICommandMediator mediator, MessagingEntityType entityType) + : base(mediator, MessageHandlerRole.Listener, entityType) { return; } /// - /// Releases all resources consumed by the current . + /// Releases all resources consumed by the current . /// /// /// A value indicating whether or not managed resources should be released. @@ -49,30 +49,30 @@ protected MessageSubscriber(ICommandMediator mediator, MessagingEntityType entit protected override void Dispose(Boolean disposing) => base.Dispose(disposing); /// - /// Gets the type of the message that the current processes. + /// Gets the type of the message that the current processes. /// public Type MessageType => typeof(TMessage); } /// - /// Processes messages as a subscriber. + /// Processes messages as a listener. /// /// - /// is the default implementation of - /// . + /// is the default implementation of + /// . /// /// - /// The type of the request message that is processed by the subscriber. + /// The type of the request message that is processed by the listener. /// /// - /// The type of the response message that is published in response to the request. + /// The type of the response message that is transmitted in response to the request. /// - public abstract class MessageSubscriber : MessageHandler, IMessageSubscriber + public abstract class MessageListener : MessageHandler, IMessageListener where TRequestMessage : class, IRequestMessage where TResponseMessage : class, IResponseMessage { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// A processing intermediary that is used to process sub-commands. @@ -80,14 +80,14 @@ public abstract class MessageSubscriber : Mes /// /// is . /// - protected MessageSubscriber(ICommandMediator mediator) - : base(mediator, MessageHandlerRole.Subscriber, Message.RequestEntityType) + protected MessageListener(ICommandMediator mediator) + : base(mediator, MessageHandlerRole.Listener, Message.RequestEntityType) { return; } /// - /// Releases all resources consumed by the current . + /// Releases all resources consumed by the current . /// /// /// A value indicating whether or not managed resources should be released. @@ -95,7 +95,7 @@ protected MessageSubscriber(ICommandMediator mediator) protected override void Dispose(Boolean disposing) => base.Dispose(disposing); /// - /// Gets the type of the message that the current + /// Gets the type of the message that the current /// processes. /// public Type MessageType => typeof(TRequestMessage); diff --git a/src/RapidField.SolidInstruments.Messaging/MessageSubscribingException.cs b/src/RapidField.SolidInstruments.Messaging/MessageListeningException.cs similarity index 78% rename from src/RapidField.SolidInstruments.Messaging/MessageSubscribingException.cs rename to src/RapidField.SolidInstruments.Messaging/MessageListeningException.cs index e433626a..e032c345 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessageSubscribingException.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessageListeningException.cs @@ -7,45 +7,45 @@ namespace RapidField.SolidInstruments.Messaging { /// - /// Represents an exception that is raised when a message subscribing operation fails. + /// Represents an exception that is raised when a message listening operation fails. /// - public class MessageSubscribingException : MessagingException + public class MessageListeningException : MessagingException { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public MessageSubscribingException() + public MessageListeningException() : base() { return; } /// - /// Initializes a new instance of the + /// Initializes a new instance of the /// /// /// The type of the message that was being processed when the exception was raised. /// - public MessageSubscribingException(Type messageType) + public MessageListeningException(Type messageType) : base(messageType) { return; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// The error message that explains the reason for the exception. /// - public MessageSubscribingException(String message) + public MessageListeningException(String message) : base(message) { return; } /// - /// Initializes a new instance of the + /// Initializes a new instance of the /// /// /// The type of the message that was being processed when the exception was raised. @@ -53,14 +53,14 @@ public MessageSubscribingException(String message) /// /// The exception that is the cause of the current exception. /// - public MessageSubscribingException(Type messageType, Exception innerException) + public MessageListeningException(Type messageType, Exception innerException) : base(messageType, innerException) { return; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// The error message that explains the reason for the exception. @@ -68,7 +68,7 @@ public MessageSubscribingException(Type messageType, Exception innerException) /// /// The exception that is the cause of the current exception. /// - public MessageSubscribingException(String message, Exception innerException) + public MessageListeningException(String message, Exception innerException) : base(message, innerException) { return; diff --git a/src/RapidField.SolidInstruments.Messaging/MessageSubscribingFacade.cs b/src/RapidField.SolidInstruments.Messaging/MessageListeningFacade.cs similarity index 79% rename from src/RapidField.SolidInstruments.Messaging/MessageSubscribingFacade.cs rename to src/RapidField.SolidInstruments.Messaging/MessageListeningFacade.cs index f20ff669..3c987271 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessageSubscribingFacade.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessageListeningFacade.cs @@ -18,7 +18,7 @@ namespace RapidField.SolidInstruments.Messaging { /// - /// Facilitates implementation-specific subscribing operations for a message bus. + /// Facilitates implementation-specific listening operations for a message bus. /// /// /// The type of the implementation-specific send client. @@ -29,31 +29,31 @@ namespace RapidField.SolidInstruments.Messaging /// /// The type of implementation-specific adapted messages. /// - /// - /// The type of the implementation-specific messaging facade that is used to publish response messages. + /// + /// The type of the implementation-specific messaging facade that is used to transmit response messages. /// /// - /// is the default - /// implementation of . + /// is the default + /// implementation of . /// - public abstract class MessageSubscribingFacade : MessageSubscribingFacade + public abstract class MessageListeningFacade : MessageListeningFacade where TAdaptedMessage : class - where TPublishingFacade : MessagePublishingFacade + where TTransmittingFacade : MessageTransmittingFacade { /// /// Initializes a new instance of the - /// class. + /// class. /// - /// - /// An implementation-specific messaging facade that is used to publish response messages. + /// + /// An implementation-specific messaging facade that is used to transmit response messages. /// /// - /// is . + /// is . /// - protected MessageSubscribingFacade(TPublishingFacade publishingFacade) - : base(publishingFacade.RejectIf().IsNull(nameof(publishingFacade)).TargetArgument.ClientFactory, publishingFacade.MessageAdapter) + protected MessageListeningFacade(TTransmittingFacade transmittingFacade) + : base(transmittingFacade.RejectIf().IsNull(nameof(transmittingFacade)).TargetArgument.ClientFactory, transmittingFacade.MessageAdapter) { - PublishingFacade = publishingFacade; + TransmittingFacade = transmittingFacade; } /// @@ -71,7 +71,7 @@ protected MessageSubscribingFacade(TPublishingFacade publishingFacade) /// /// is . /// - /// + /// /// An exception was raised while attempting to register . /// /// @@ -87,7 +87,7 @@ public sealed override void RegisterRequestMessageHandler(controlToken) == false) + if (TryAddListenedMessageType(controlToken) == false) { // Disallow registration of duplicate request handlers. return; @@ -120,19 +120,19 @@ public sealed override void RegisterRequestMessageHandler { var responseMessage = requestMessageHandler(message); - PublishingFacade.PublishAsync(responseMessage, null, Message.ResponseEntityType).Wait(); + TransmittingFacade.TransmitAsync(responseMessage, null, Message.ResponseEntityType).Wait(); }, requestMessage).Wait(); } catch (Exception exception) { - throw new MessageSubscribingException(typeof(TRequestMessage), exception); + throw new MessageListeningException(typeof(TRequestMessage), exception); } }); RegisterMessageHandler(messageHandler, requestReceiveClient, controlToken); } } - catch (MessageSubscribingException) + catch (MessageListeningException) { throw; } @@ -142,13 +142,13 @@ public sealed override void RegisterRequestMessageHandler /// Releases all resources consumed by the current - /// . + /// . /// /// /// A value indicating whether or not managed resources should be released. @@ -156,28 +156,28 @@ public sealed override void RegisterRequestMessageHandler base.Dispose(disposing); /// - /// Asynchronously publishes the specified instance. + /// Asynchronously transmits the specified instance. /// /// - /// The message to publish. + /// The message to transmit. /// /// - /// The messaging type to use when publishing the message. + /// The messaging type to use when transmitting the message. /// /// /// A task representing the asynchronous operation. /// - protected sealed override Task PublishReceiverExceptionAsync(ExceptionRaisedEventMessage exceptionRaisedMessage, MessagingEntityType messagingEntityType) => PublishingFacade.PublishAsync(exceptionRaisedMessage, null, messagingEntityType); + protected sealed override Task TransmitReceiverExceptionAsync(ExceptionRaisedEventMessage exceptionRaisedMessage, MessagingEntityType messagingEntityType) => TransmittingFacade.TransmitAsync(exceptionRaisedMessage, null, messagingEntityType); /// - /// Represents an implementation-specific messaging facade that is used to publish response messages. + /// Represents an implementation-specific messaging facade that is used to transmit response messages. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - internal readonly TPublishingFacade PublishingFacade; + internal readonly TTransmittingFacade TransmittingFacade; } /// - /// Facilitates implementation-specific subscribing operations for a message bus. + /// Facilitates implementation-specific listening operations for a message bus. /// /// /// The type of the implementation-specific send client. @@ -189,14 +189,14 @@ public sealed override void RegisterRequestMessageHandler /// - /// is the default implementation of - /// . + /// is the default implementation of + /// . /// - public abstract class MessageSubscribingFacade : MessagingFacade, IMessageSubscribingFacade + public abstract class MessageListeningFacade : MessagingFacade, IMessageListeningFacade where TAdaptedMessage : class { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// An appliance that creates manages implementation-specific messaging clients. @@ -208,7 +208,7 @@ public abstract class MessageSubscribingFacade is -or- is /// . /// - protected MessageSubscribingFacade(IMessagingClientFactory clientFactory, IMessageAdapter messageAdapter) + protected MessageListeningFacade(IMessagingClientFactory clientFactory, IMessageAdapter messageAdapter) : base(clientFactory, messageAdapter) { return; @@ -226,7 +226,7 @@ protected MessageSubscribingFacade(IMessagingClientFactory /// is . /// - /// + /// /// An exception was raised while attempting to register . /// /// @@ -254,7 +254,7 @@ public void RegisterQueueMessageHandler(Action messageHandle /// /// is . /// - /// + /// /// An exception was raised while attempting to register . /// /// @@ -278,7 +278,7 @@ public void RegisterQueueMessageHandler(Action messageHandle /// /// is . /// - /// + /// /// An exception was raised while attempting to register . /// /// @@ -300,7 +300,7 @@ public abstract void RegisterRequestMessageHandler /// is . /// - /// + /// /// An exception was raised while attempting to register . /// /// @@ -328,7 +328,7 @@ public void RegisterTopicMessageHandler(Action messageHandle /// /// is . /// - /// + /// /// An exception was raised while attempting to register . /// /// @@ -396,7 +396,7 @@ internal Task HandleMessageAsync(Action messageHandler, TMes /// /// is . /// - /// + /// /// An exception was raised while attempting to register . /// /// @@ -412,7 +412,7 @@ internal void RegisterMessageHandler(Action messageHandler, { RejectIfDisposed(); - if (TryAddSubscribedMessageType(controlToken) == false) + if (TryAddListenedMessageType(controlToken) == false) { if (ResponseMessageInterfaceType.IsAssignableFrom(typeof(TMessage))) { @@ -448,7 +448,7 @@ internal void RegisterMessageHandler(Action messageHandler, } catch (Exception exception) { - throw new MessageSubscribingException(typeof(TMessage), exception); + throw new MessageListeningException(typeof(TMessage), exception); } }); @@ -456,7 +456,7 @@ internal void RegisterMessageHandler(Action messageHandler, { RegisterMessageHandler(adaptedMessageHandler, receiveClient, controlToken); } - catch (MessageSubscribingException) + catch (MessageListeningException) { throw; } @@ -466,16 +466,16 @@ internal void RegisterMessageHandler(Action messageHandler, } catch (Exception exception) { - throw new MessageSubscribingException(typeof(TMessage), exception); + throw new MessageListeningException(typeof(TMessage), exception); } } } /// - /// Attempts to add the specified message type to the collection of subscribed message types. + /// Attempts to add the specified message type to the collection of listened message types. /// /// - /// The type of the subscribed message. + /// The type of the listened message. /// /// /// A token that represents and manages contextual thread safety. @@ -484,43 +484,29 @@ internal void RegisterMessageHandler(Action messageHandler, /// if the specified message type was added; if it was already present. /// [DebuggerHidden] - internal Boolean TryAddSubscribedMessageType(ConcurrencyControlToken controlToken) + internal Boolean TryAddListenedMessageType(ConcurrencyControlToken controlToken) where TMessage : IMessageBase { var messageType = typeof(TMessage); - if (SubscribedMessageTypeList.Contains(messageType)) + if (ListenedMessageTypeList.Contains(messageType)) { return false; } - SubscribedMessageTypeList.Add(messageType); + ListenedMessageTypeList.Add(messageType); return true; } /// /// Releases all resources consumed by the current - /// . + /// . /// /// /// A value indicating whether or not managed resources should be released. /// protected override void Dispose(Boolean disposing) => base.Dispose(disposing); - /// - /// Asynchronously publishes the specified instance. - /// - /// - /// The message to publish. - /// - /// - /// The messaging type to use when publishing the message. - /// - /// - /// A task representing the asynchronous operation. - /// - protected abstract Task PublishReceiverExceptionAsync(ExceptionRaisedEventMessage exceptionRaisedMessage, MessagingEntityType messagingEntityType); - /// /// Registers the specified message handler with the bus. /// @@ -535,6 +521,20 @@ internal Boolean TryAddSubscribedMessageType(ConcurrencyControlToken c /// protected abstract void RegisterMessageHandler(Action adaptedMessageHandler, TReceiver receiveClient, ConcurrencyControlToken controlToken); + /// + /// Asynchronously transmits the specified instance. + /// + /// + /// The message to transmit. + /// + /// + /// The messaging type to use when transmitting the message. + /// + /// + /// A task representing the asynchronous operation. + /// + protected abstract Task TransmitReceiverExceptionAsync(ExceptionRaisedEventMessage exceptionRaisedMessage, MessagingEntityType messagingEntityType); + /// /// Asynchronously executes the failure policy prescribed by the specified message. /// @@ -569,9 +569,9 @@ private Task ExecuteFailurePolicyAsync(Action messageHandler var retryPolicy = failurePolicy?.RetryPolicy ?? DefaultFailurePolicy.RetryPolicy; var failurePolicyTasks = new List(); - if (failurePolicy.PublishExceptionRaisedEventMessage) + if (failurePolicy.TransmitExceptionRaisedEventMessage) { - failurePolicyTasks.Add(PublishReceiverExceptionAsync(raisedException, message.CorrelationIdentifier)); + failurePolicyTasks.Add(TransmitReceiverExceptionAsync(raisedException, message.CorrelationIdentifier)); } if (retryPolicy.RetryCount >= attemptCount) @@ -579,8 +579,8 @@ private Task ExecuteFailurePolicyAsync(Action messageHandler var baseDelayDurationInSeconds = retryPolicy.BaseDelayDurationInSeconds.AbsoluteValue(); var calculatedDelayDurationInSeconds = retryPolicy.DurationScale switch { - MessageSubscribingRetryDurationScale.Fibonacci => (Int32)new FibonacciSequence(0, baseDelayDurationInSeconds).ToArray(attemptCount, 1).First(), - MessageSubscribingRetryDurationScale.Linear => baseDelayDurationInSeconds * processingInformation.AttemptCount, + MessageListeningRetryDurationScale.Fibonacci => (Int32)new FibonacciSequence(0, baseDelayDurationInSeconds).ToArray(attemptCount, 1).First(), + MessageListeningRetryDurationScale.Linear => baseDelayDurationInSeconds * processingInformation.AttemptCount, _ => throw new UnsupportedSpecificationException($"The specified duration scale, {retryPolicy.DurationScale}, is not supported.") }; @@ -591,8 +591,8 @@ private Task ExecuteFailurePolicyAsync(Action messageHandler { throw failurePolicy.SecondaryFailureBehavior switch { - MessageSubscribingSecondaryFailureBehavior.Discard => new UnsupportedSpecificationException($"The specified secondary failure behavior, {failurePolicy.SecondaryFailureBehavior}, is not supported."), // TODO - MessageSubscribingSecondaryFailureBehavior.RouteToDeadLetterQueue => new UnsupportedSpecificationException($"The specified secondary failure behavior, {failurePolicy.SecondaryFailureBehavior}, is not supported."), // TODO + MessageListeningSecondaryFailureBehavior.Discard => new UnsupportedSpecificationException($"The specified secondary failure behavior, {failurePolicy.SecondaryFailureBehavior}, is not supported."), // TODO + MessageListeningSecondaryFailureBehavior.RouteToDeadLetterQueue => new UnsupportedSpecificationException($"The specified secondary failure behavior, {failurePolicy.SecondaryFailureBehavior}, is not supported."), // TODO _ => new UnsupportedSpecificationException($"The specified secondary failure behavior, {failurePolicy.SecondaryFailureBehavior}, is not supported.") }; } @@ -606,10 +606,10 @@ private Task ExecuteFailurePolicyAsync(Action messageHandler } /// - /// Asynchronously publishes a new instance for the specified exception. + /// Asynchronously transmits a new instance for the specified exception. /// /// - /// An exception for which to publish a new message. + /// An exception for which to transmit a new message. /// /// /// A correlation identifier for the message that could not be processed. @@ -618,43 +618,44 @@ private Task ExecuteFailurePolicyAsync(Action messageHandler /// A task representing the asynchronous operation. /// [DebuggerHidden] - private Task PublishReceiverExceptionAsync(Exception raisedException, Guid correlationIdentifier) + private Task TransmitReceiverExceptionAsync(Exception raisedException, Guid correlationIdentifier) { var exceptionRaisedEvent = new ExceptionRaisedEvent(ApplicationIdentity, raisedException, ExceptionRaisedMessageEventVerbosity); var exceptionRaisedEventMessage = new ExceptionRaisedEventMessage(exceptionRaisedEvent, correlationIdentifier); - return PublishReceiverExceptionAsync(exceptionRaisedEventMessage, ExceptionRaisedMessageEntityType); + return TransmitReceiverExceptionAsync(exceptionRaisedEventMessage, ExceptionRaisedMessageEntityType); } /// /// Gets the collection of message types for which the current - /// has one or more registered handlers. + /// has one or more registered handlers. /// - public IEnumerable SubscribedMessageTypes + public IEnumerable ListenedMessageTypes { get { - foreach (var subscribedMessageType in SubscribedMessageTypeList) + foreach (var listenedMessageType in ListenedMessageTypeList) { - yield return subscribedMessageType; + yield return listenedMessageType; } } } /// - /// Gets the entity type that is used to publish new instances of . + /// Gets the entity type that is used to transmit new instances of . /// protected virtual MessagingEntityType ExceptionRaisedMessageEntityType => MessagingEntityType.Topic; /// - /// Gets the event verbosity level that is used when publishing new instances of . + /// Gets the event verbosity level that is used when transmitting new instances of + /// . /// protected virtual EventVerbosity ExceptionRaisedMessageEventVerbosity => EventVerbosity.Normal; /// - /// Represents the default instructions that guide failure behavior for the subscriber. + /// Represents the default instructions that guide failure behavior for the listener. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - internal static readonly MessageSubscribingFailurePolicy DefaultFailurePolicy = MessageSubscribingFailurePolicy.Default; + internal static readonly MessageListeningFailurePolicy DefaultFailurePolicy = MessageListeningFailurePolicy.Default; /// /// Represents the type. @@ -664,9 +665,9 @@ public IEnumerable SubscribedMessageTypes /// /// Represents the collection of message types for which the current - /// has one or more registered handlers. + /// has one or more registered handlers. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly IList SubscribedMessageTypeList = new List(); + private readonly IList ListenedMessageTypeList = new List(); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/MessageSubscribingFailurePolicy.cs b/src/RapidField.SolidInstruments.Messaging/MessageListeningFailurePolicy.cs similarity index 63% rename from src/RapidField.SolidInstruments.Messaging/MessageSubscribingFailurePolicy.cs rename to src/RapidField.SolidInstruments.Messaging/MessageListeningFailurePolicy.cs index 29935b36..40f59bb3 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessageSubscribingFailurePolicy.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessageListeningFailurePolicy.cs @@ -11,115 +11,115 @@ namespace RapidField.SolidInstruments.Messaging { /// - /// Represents instructions that guide failure behavior for the subscriber when processing an . + /// Represents instructions that guide failure behavior for the listener when processing an . /// [DataContract] - public sealed class MessageSubscribingFailurePolicy + public sealed class MessageListeningFailurePolicy { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public MessageSubscribingFailurePolicy() - : this(MessageSubscribingRetryPolicy.Default) + public MessageListeningFailurePolicy() + : this(MessageListeningRetryPolicy.Default) { return; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// Information that defines retry behavior that is employed before employing secondary failure behavior. The default value - /// is . + /// is . /// /// /// is . /// - public MessageSubscribingFailurePolicy(MessageSubscribingRetryPolicy retryPolicy) + public MessageListeningFailurePolicy(MessageListeningRetryPolicy retryPolicy) : this(retryPolicy, DefaultSecondaryFailureBehavior) { return; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// Information that defines retry behavior that is employed before employing secondary failure behavior. The default value - /// is . + /// is . /// /// /// The failure behavior that is employed after the retry policy is exhausted. The default value is - /// . + /// . /// /// /// is . /// /// /// is equal to - /// . + /// . /// - public MessageSubscribingFailurePolicy(MessageSubscribingRetryPolicy retryPolicy, MessageSubscribingSecondaryFailureBehavior secondaryFailureBehavior) - : this(retryPolicy, secondaryFailureBehavior, DefaultPublishExceptionRaisedEventMessage) + public MessageListeningFailurePolicy(MessageListeningRetryPolicy retryPolicy, MessageListeningSecondaryFailureBehavior secondaryFailureBehavior) + : this(retryPolicy, secondaryFailureBehavior, DefaultTransmitExceptionRaisedEventMessage) { return; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// Information that defines retry behavior that is employed before employing secondary failure behavior. The default value - /// is . + /// is . /// /// /// The failure behavior that is employed after the retry policy is exhausted. The default value is - /// . + /// . /// - /// - /// A value indicating whether or not subscribers should publish instances when - /// an exception is raised during message processing. The default value is . + /// + /// A value indicating whether or not listeners should transmit instances when an + /// exception is raised during message processing. The default value is . /// /// /// is . /// /// /// is equal to - /// . + /// . /// - public MessageSubscribingFailurePolicy(MessageSubscribingRetryPolicy retryPolicy, MessageSubscribingSecondaryFailureBehavior secondaryFailureBehavior, Boolean publishExceptionRaisedEventMessage) + public MessageListeningFailurePolicy(MessageListeningRetryPolicy retryPolicy, MessageListeningSecondaryFailureBehavior secondaryFailureBehavior, Boolean transmitExceptionRaisedEventMessage) { - PublishExceptionRaisedEventMessage = publishExceptionRaisedEventMessage; RetryPolicy = retryPolicy.RejectIf().IsNull(nameof(retryPolicy)); - SecondaryFailureBehavior = secondaryFailureBehavior.RejectIf().IsEqualToValue(MessageSubscribingSecondaryFailureBehavior.Unspecified, nameof(secondaryFailureBehavior)); + SecondaryFailureBehavior = secondaryFailureBehavior.RejectIf().IsEqualToValue(MessageListeningSecondaryFailureBehavior.Unspecified, nameof(secondaryFailureBehavior)); + TransmitExceptionRaisedEventMessage = transmitExceptionRaisedEventMessage; } /// - /// Gets or sets a value indicating whether or not subscribers should publish - /// instances when an exception is raised during message processing. + /// Gets or sets information that defines retry behavior that is employed before employing secondary failure behavior. /// [DataMember] - public Boolean PublishExceptionRaisedEventMessage + public MessageListeningRetryPolicy RetryPolicy { get; set; } /// - /// Gets or sets information that defines retry behavior that is employed before employing secondary failure behavior. + /// Gets or sets the failure behavior that is employed after the retry policy is exhausted. /// [DataMember] - public MessageSubscribingRetryPolicy RetryPolicy + public MessageListeningSecondaryFailureBehavior SecondaryFailureBehavior { get; set; } /// - /// Gets or sets the failure behavior that is employed after the retry policy is exhausted. + /// Gets or sets a value indicating whether or not listeners should transmit + /// instances when an exception is raised during message processing. /// [DataMember] - public MessageSubscribingSecondaryFailureBehavior SecondaryFailureBehavior + public Boolean TransmitExceptionRaisedEventMessage { get; set; @@ -128,19 +128,19 @@ public MessageSubscribingSecondaryFailureBehavior SecondaryFailureBehavior /// /// Represents the default failure behavior. /// - public static readonly MessageSubscribingFailurePolicy Default = new MessageSubscribingFailurePolicy(); + public static readonly MessageListeningFailurePolicy Default = new MessageListeningFailurePolicy(); /// - /// Represents the default value indicating whether or not subscribers should publish - /// instances when an exception is raised during message processing. + /// Represents the default failure behavior that is employed after the retry policy is exhausted. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const Boolean DefaultPublishExceptionRaisedEventMessage = false; + private const MessageListeningSecondaryFailureBehavior DefaultSecondaryFailureBehavior = MessageListeningSecondaryFailureBehavior.RouteToDeadLetterQueue; /// - /// Represents the default failure behavior that is employed after the retry policy is exhausted. + /// Represents the default value indicating whether or not listeners should transmit + /// instances when an exception is raised during message processing. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const MessageSubscribingSecondaryFailureBehavior DefaultSecondaryFailureBehavior = MessageSubscribingSecondaryFailureBehavior.RouteToDeadLetterQueue; + private const Boolean DefaultTransmitExceptionRaisedEventMessage = false; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/MessageSubscribingRetryDurationScale.cs b/src/RapidField.SolidInstruments.Messaging/MessageListeningRetryDurationScale.cs similarity index 91% rename from src/RapidField.SolidInstruments.Messaging/MessageSubscribingRetryDurationScale.cs rename to src/RapidField.SolidInstruments.Messaging/MessageListeningRetryDurationScale.cs index 8c349615..ee50ea11 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessageSubscribingRetryDurationScale.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessageListeningRetryDurationScale.cs @@ -8,10 +8,10 @@ namespace RapidField.SolidInstruments.Messaging { /// - /// Represents the retry duration scaling behavior employed by a subscriber in response to message processing failure. + /// Represents the retry duration scaling behavior employed by a listener in response to message processing failure. /// [DataContract] - public enum MessageSubscribingRetryDurationScale : Int32 + public enum MessageListeningRetryDurationScale : Int32 { /// /// The retry duration scaling behavior is not specified. diff --git a/src/RapidField.SolidInstruments.Messaging/MessageSubscribingRetryPolicy.cs b/src/RapidField.SolidInstruments.Messaging/MessageListeningRetryPolicy.cs similarity index 62% rename from src/RapidField.SolidInstruments.Messaging/MessageSubscribingRetryPolicy.cs rename to src/RapidField.SolidInstruments.Messaging/MessageListeningRetryPolicy.cs index 1a737f5a..8f9bb3e6 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessageSubscribingRetryPolicy.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessageListeningRetryPolicy.cs @@ -10,42 +10,42 @@ namespace RapidField.SolidInstruments.Messaging { /// - /// Represents instructions that guide retry behavior for the subscriber when processing an . + /// Represents instructions that guide retry behavior for the listener when processing an . /// [DataContract] - public sealed class MessageSubscribingRetryPolicy + public sealed class MessageListeningRetryPolicy { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public MessageSubscribingRetryPolicy() + public MessageListeningRetryPolicy() : this(DefaultRetryCount) { return; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// - /// The number of times that subscribers should try to process a failed message before employing secondary failure behavior, - /// or zero to employ secondary behavior upon first failure. The default value is three. + /// The number of times that listener should try to process a failed message before employing secondary failure behavior, or + /// zero to employ secondary behavior upon first failure. The default value is three. /// /// /// is less than zero. /// - public MessageSubscribingRetryPolicy(Int32 retryCount) + public MessageListeningRetryPolicy(Int32 retryCount) : this(retryCount, DefaultBaseDelayDurationInSeconds) { return; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// - /// The number of times that subscribers should try to process a failed message before employing secondary failure behavior, - /// or zero to employ secondary behavior upon first failure. The default value is three. + /// The number of times that listener should try to process a failed message before employing secondary failure behavior, or + /// zero to employ secondary behavior upon first failure. The default value is three. /// /// /// The number of seconds to wait between retries, or the duration, in seconds, from which to scale non-linearly. The @@ -54,35 +54,35 @@ public MessageSubscribingRetryPolicy(Int32 retryCount) /// /// is less than zero -or- is less than zero. /// - public MessageSubscribingRetryPolicy(Int32 retryCount, Int32 baseDelayDurationInSeconds) + public MessageListeningRetryPolicy(Int32 retryCount, Int32 baseDelayDurationInSeconds) : this(retryCount, baseDelayDurationInSeconds, DefaultDurationScale) { return; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// - /// The number of times that subscribers should try to process a failed message before employing secondary failure behavior, - /// or zero to employ secondary behavior upon first failure. The default value is three. + /// The number of times that listener should try to process a failed message before employing secondary failure behavior, or + /// zero to employ secondary behavior upon first failure. The default value is three. /// /// /// The number of seconds to wait between retries, or the duration, in seconds, from which to scale non-linearly. The /// default value is three. /// /// - /// The retry duration scaling behavior employed by subscribers in response to message processing failure. The default value - /// is . + /// The retry duration scaling behavior employed by listener in response to message processing failure. The default value is + /// . /// /// /// is less than zero -or- is equal to - /// -or- is less than zero. + /// -or- is less than zero. /// - public MessageSubscribingRetryPolicy(Int32 retryCount, Int32 baseDelayDurationInSeconds, MessageSubscribingRetryDurationScale durationScale) + public MessageListeningRetryPolicy(Int32 retryCount, Int32 baseDelayDurationInSeconds, MessageListeningRetryDurationScale durationScale) { BaseDelayDurationInSeconds = baseDelayDurationInSeconds.RejectIf().IsLessThan(0, nameof(baseDelayDurationInSeconds)); - DurationScale = durationScale.RejectIf().IsEqualToValue(MessageSubscribingRetryDurationScale.Unspecified, nameof(durationScale)); + DurationScale = durationScale.RejectIf().IsEqualToValue(MessageListeningRetryDurationScale.Unspecified, nameof(durationScale)); RetryCount = retryCount.RejectIf().IsLessThan(0, nameof(retryCount)); } @@ -98,18 +98,18 @@ public Int32 BaseDelayDurationInSeconds } /// - /// Gets or sets the retry duration scaling behavior employed by subscribers in response to message processing failure. + /// Gets or sets the retry duration scaling behavior employed by listener in response to message processing failure. /// [DataMember] - public MessageSubscribingRetryDurationScale DurationScale + public MessageListeningRetryDurationScale DurationScale { get; set; } /// - /// Gets or sets the number of times that subscribers should try to process a failed message before employing secondary - /// failure behavior, or zero to employ secondary behavior upon first failure. + /// Gets or sets the number of times that listener should try to process a failed message before employing secondary failure + /// behavior, or zero to employ secondary behavior upon first failure. /// [DataMember] public Int32 RetryCount @@ -121,12 +121,12 @@ public Int32 RetryCount /// /// Represents the default retry behavior. /// - public static readonly MessageSubscribingRetryPolicy Default = new MessageSubscribingRetryPolicy(); + public static readonly MessageListeningRetryPolicy Default = new MessageListeningRetryPolicy(); /// - /// Represents retry behavior that instructs the subscriber not to retry processing for the message. + /// Represents retry behavior that instructs the listener not to retry processing for the message. /// - public static readonly MessageSubscribingRetryPolicy DoNotRetry = new MessageSubscribingRetryPolicy(0); + public static readonly MessageListeningRetryPolicy DoNotRetry = new MessageListeningRetryPolicy(0); /// /// Represents the default number of seconds to wait between retries, or the duration, in seconds, from which to scale @@ -136,15 +136,14 @@ public Int32 RetryCount private const Int32 DefaultBaseDelayDurationInSeconds = 3; /// - /// Represents the default retry duration scaling behavior employed by subscribers in response to message processing - /// failure. + /// Represents the default retry duration scaling behavior employed by listener in response to message processing failure. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const MessageSubscribingRetryDurationScale DefaultDurationScale = MessageSubscribingRetryDurationScale.Fibonacci; + private const MessageListeningRetryDurationScale DefaultDurationScale = MessageListeningRetryDurationScale.Fibonacci; /// - /// Represents the default number of times that subscribers should try to process a failed message before employing - /// secondary failure behavior, or zero to employ secondary behavior upon first failure. + /// Represents the default number of times that listener should try to process a failed message before employing secondary + /// failure behavior, or zero to employ secondary behavior upon first failure. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] private const Int32 DefaultRetryCount = 3; diff --git a/src/RapidField.SolidInstruments.Messaging/MessageSubscribingSecondaryFailureBehavior.cs b/src/RapidField.SolidInstruments.Messaging/MessageListeningSecondaryFailureBehavior.cs similarity index 85% rename from src/RapidField.SolidInstruments.Messaging/MessageSubscribingSecondaryFailureBehavior.cs rename to src/RapidField.SolidInstruments.Messaging/MessageListeningSecondaryFailureBehavior.cs index f96205bd..efb58bc4 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessageSubscribingSecondaryFailureBehavior.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessageListeningSecondaryFailureBehavior.cs @@ -8,10 +8,10 @@ namespace RapidField.SolidInstruments.Messaging { /// - /// Represents behavior that is employed by a message subscriber after the associated retry policy is exhausted. + /// Represents behavior that is employed by a message listener after the associated retry policy is exhausted. /// [DataContract] - public enum MessageSubscribingSecondaryFailureBehavior : Int32 + public enum MessageListeningSecondaryFailureBehavior : Int32 { /// /// The secondary failure behavior is not specified. diff --git a/src/RapidField.SolidInstruments.Messaging/MessageProcessingAttemptResult.cs b/src/RapidField.SolidInstruments.Messaging/MessageProcessingAttemptResult.cs index e49eeef5..09f94f72 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessageProcessingAttemptResult.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessageProcessingAttemptResult.cs @@ -11,7 +11,7 @@ namespace RapidField.SolidInstruments.Messaging { /// - /// Represents information about the result of a message processing attempt by a subscriber. + /// Represents information about the result of a message processing attempt by a listener. /// [DataContract] public sealed class MessageProcessingAttemptResult diff --git a/src/RapidField.SolidInstruments.Messaging/MessageProcessingInformation.cs b/src/RapidField.SolidInstruments.Messaging/MessageProcessingInformation.cs index ee9aa810..d1edf37b 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessageProcessingInformation.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessageProcessingInformation.cs @@ -20,7 +20,7 @@ public sealed class MessageProcessingInformation /// Initializes a new instance of the class. /// public MessageProcessingInformation() - : this(MessageSubscribingFailurePolicy.Default) + : this(MessageListeningFailurePolicy.Default) { return; } @@ -29,13 +29,13 @@ public MessageProcessingInformation() /// Initializes a new instance of the class. /// /// - /// Instructions that guide failure behavior for the subscriber. The default value is - /// . + /// Instructions that guide failure behavior for the listener. The default value is + /// . /// /// /// is . /// - public MessageProcessingInformation(MessageSubscribingFailurePolicy failurePolicy) + public MessageProcessingInformation(MessageListeningFailurePolicy failurePolicy) { AttemptResults = new Collection(); FailurePolicy = failurePolicy.RejectIf().IsNull(nameof(failurePolicy)); @@ -60,10 +60,10 @@ public Collection AttemptResults } /// - /// Gets or sets instructions that guide failure behavior for the subscriber. + /// Gets or sets instructions that guide failure behavior for the listener. /// [DataMember] - public MessageSubscribingFailurePolicy FailurePolicy + public MessageListeningFailurePolicy FailurePolicy { get; set; diff --git a/src/RapidField.SolidInstruments.Messaging/MessageRequestingFacade.cs b/src/RapidField.SolidInstruments.Messaging/MessageRequestingFacade.cs index 9e8f1e3a..16f0f4f9 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessageRequestingFacade.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessageRequestingFacade.cs @@ -25,41 +25,41 @@ namespace RapidField.SolidInstruments.Messaging /// /// The type of implementation-specific adapted messages. /// - /// - /// The type of the implementation-specific messaging facade that is used to publish request and response messages. + /// + /// The type of the implementation-specific messaging facade that is used to transmit request and response messages. /// - /// - /// The type of the implementation-specific messaging facade that subscribes to request messages. + /// + /// The type of the implementation-specific messaging facade that listens for request messages. /// /// - /// is the + /// is the /// default implementation of - /// . + /// . /// - public abstract class MessageRequestingFacade : MessageRequestingFacade, IMessageRequestingFacade + public abstract class MessageRequestingFacade : MessageRequestingFacade, IMessageRequestingFacade where TAdaptedMessage : class - where TPublishingFacade : MessagePublishingFacade - where TSubscribingFacade : MessageSubscribingFacade + where TTransmittingFacade : MessageTransmittingFacade + where TListeningFacade : MessageListeningFacade { /// /// Initializes a new instance of the class. /// - /// - /// An implementation-specific messaging facade that subscribes to request messages. + /// + /// An implementation-specific messaging facade that listens for request messages. /// /// - /// is . + /// is . /// - protected MessageRequestingFacade(TSubscribingFacade subscribingFacade) - : base(subscribingFacade.RejectIf().IsNull(nameof(subscribingFacade)).TargetArgument.ClientFactory, subscribingFacade.MessageAdapter) + protected MessageRequestingFacade(TListeningFacade listeningFacade) + : base(listeningFacade.RejectIf().IsNull(nameof(listeningFacade)).TargetArgument.ClientFactory, listeningFacade.MessageAdapter) { - PublishingFacade = subscribingFacade.PublishingFacade; - SubscribingFacade = subscribingFacade; + TransmittingFacade = listeningFacade.TransmittingFacade; + ListeningFacade = listeningFacade; } /// /// Releases all resources consumed by the current - /// . + /// . /// /// /// A value indicating whether or not managed resources should be released. @@ -67,47 +67,47 @@ protected MessageRequestingFacade(TSubscribingFacade subscribingFacade) protected override void Dispose(Boolean disposing) => base.Dispose(disposing); /// - /// Asynchronously publishes the specified request message to a bus. + /// Registers the specified response handler with a bus. /// - /// - /// The type of the request message. - /// /// /// The type of the response message. /// - /// - /// The request message to publish. + /// + /// An action that handles the response. /// - /// - /// A task representing the asynchronous operation. - /// - protected sealed override Task PublishRequestMessageAsync(TRequestMessage requestMessage) => PublishingFacade.PublishAsync(requestMessage, null, Message.RequestEntityType); + /// + /// A token that represents and manages contextual thread safety. + /// + protected sealed override void RegisterResponseHandler(Action responseHandler, ConcurrencyControlToken controlToken) => ListeningFacade.RegisterTopicMessageHandler(responseHandler); /// - /// Registers the specified response handler with a bus. + /// Asynchronously transmits the specified request message to a bus. /// + /// + /// The type of the request message. + /// /// /// The type of the response message. /// - /// - /// An action that handles the response. - /// - /// - /// A token that represents and manages contextual thread safety. + /// + /// The request message to transmit. /// - protected sealed override void RegisterResponseHandler(Action responseHandler, ConcurrencyControlToken controlToken) => SubscribingFacade.RegisterTopicMessageHandler(responseHandler); + /// + /// A task representing the asynchronous operation. + /// + protected sealed override Task TransmitRequestMessageAsync(TRequestMessage requestMessage) => TransmittingFacade.TransmitAsync(requestMessage, null, Message.RequestEntityType); /// - /// Represents an implementation-specific messaging facade that is used to publish request and response messages. + /// Represents an implementation-specific messaging facade that listens for request messages. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly TPublishingFacade PublishingFacade; + private readonly TListeningFacade ListeningFacade; /// - /// Represents an implementation-specific messaging facade that subscribes to request messages. + /// Represents an implementation-specific messaging facade that is used to transmit request and response messages. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly TSubscribingFacade SubscribingFacade; + private readonly TTransmittingFacade TransmittingFacade; } /// @@ -149,7 +149,7 @@ protected MessageRequestingFacade(IMessagingClientFactory - /// Asynchronously publishes the specified request message to a bus and waits for the correlated response message. + /// Asynchronously transmits the specified request message to a bus and waits for the correlated response message. /// /// /// The type of the request message. @@ -158,7 +158,7 @@ protected MessageRequestingFacade(IMessagingClientFactory /// - /// The request message to publish. + /// The request message to transmit. /// /// /// A task representing the asynchronous operation and containing the correlated response message. @@ -195,7 +195,7 @@ public Task RequestAsync(TR } } - return PublishRequestMessageAsync(requestMessage).ContinueWith((publishTask) => + return TransmitRequestMessageAsync(requestMessage).ContinueWith((transmitTask) => { RejectIfDisposed(); @@ -203,7 +203,7 @@ public Task RequestAsync(TR { if (!(WaitForResponse(requestMessageIdentifier) is TResponseMessage responseMessage)) { - throw new MessageSubscribingException($"The response message is not a valid {typeof(TResponseMessage).FullName}."); + throw new MessageListeningException($"The response message is not a valid {typeof(TResponseMessage).FullName}."); } return responseMessage; @@ -229,37 +229,37 @@ public Task RequestAsync(TR protected override void Dispose(Boolean disposing) => base.Dispose(disposing); /// - /// Asynchronously publishes the specified request message to a bus. + /// Registers the specified response handler with a bus. /// - /// - /// The type of the request message. - /// /// /// The type of the response message. /// - /// - /// The request message to publish. + /// + /// An action that handles the response. /// - /// - /// A task representing the asynchronous operation. - /// - protected abstract Task PublishRequestMessageAsync(TRequestMessage requestMessage) - where TRequestMessage : class, IRequestMessage + /// + /// A token that represents and manages contextual thread safety. + /// + protected abstract void RegisterResponseHandler(Action responseHandler, ConcurrencyControlToken controlToken) where TResponseMessage : class, IResponseMessage; /// - /// Registers the specified response handler with a bus. + /// Asynchronously transmits the specified request message to a bus. /// + /// + /// The type of the request message. + /// /// /// The type of the response message. /// - /// - /// An action that handles the response. - /// - /// - /// A token that represents and manages contextual thread safety. + /// + /// The request message to transmit. /// - protected abstract void RegisterResponseHandler(Action responseHandler, ConcurrencyControlToken controlToken) + /// + /// A task representing the asynchronous operation. + /// + protected abstract Task TransmitRequestMessageAsync(TRequestMessage requestMessage) + where TRequestMessage : class, IRequestMessage where TResponseMessage : class, IResponseMessage; /// diff --git a/src/RapidField.SolidInstruments.Messaging/MessagePublishingException.cs b/src/RapidField.SolidInstruments.Messaging/MessageTransmissionException.cs similarity index 78% rename from src/RapidField.SolidInstruments.Messaging/MessagePublishingException.cs rename to src/RapidField.SolidInstruments.Messaging/MessageTransmissionException.cs index f52adf17..41912847 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessagePublishingException.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessageTransmissionException.cs @@ -7,45 +7,45 @@ namespace RapidField.SolidInstruments.Messaging { /// - /// Represents an exception that is raised when a message publishing operation fails. + /// Represents an exception that is raised when a message transmission operation fails. /// - public class MessagePublishingException : MessagingException + public class MessageTransmissionException : MessagingException { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public MessagePublishingException() + public MessageTransmissionException() : base() { return; } /// - /// Initializes a new instance of the + /// Initializes a new instance of the /// /// /// The type of the message that was being processed when the exception was raised. /// - public MessagePublishingException(Type messageType) + public MessageTransmissionException(Type messageType) : base(messageType) { return; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// The error message that explains the reason for the exception. /// - public MessagePublishingException(String message) + public MessageTransmissionException(String message) : base(message) { return; } /// - /// Initializes a new instance of the + /// Initializes a new instance of the /// /// /// The type of the message that was being processed when the exception was raised. @@ -53,14 +53,14 @@ public MessagePublishingException(String message) /// /// The exception that is the cause of the current exception. /// - public MessagePublishingException(Type messageType, Exception innerException) + public MessageTransmissionException(Type messageType, Exception innerException) : base(messageType, innerException) { return; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// The error message that explains the reason for the exception. @@ -68,7 +68,7 @@ public MessagePublishingException(Type messageType, Exception innerException) /// /// The exception that is the cause of the current exception. /// - public MessagePublishingException(String message, Exception innerException) + public MessageTransmissionException(String message, Exception innerException) : base(message, innerException) { return; diff --git a/src/RapidField.SolidInstruments.Messaging/MessagePublisher.cs b/src/RapidField.SolidInstruments.Messaging/MessageTransmitter.cs similarity index 75% rename from src/RapidField.SolidInstruments.Messaging/MessagePublisher.cs rename to src/RapidField.SolidInstruments.Messaging/MessageTransmitter.cs index 13b6ffab..5c31c674 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessagePublisher.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessageTransmitter.cs @@ -12,25 +12,25 @@ namespace RapidField.SolidInstruments.Messaging { /// - /// Publishes messages. + /// Transmits messages. /// /// - /// is the default implementation of . + /// is the default implementation of . /// /// - /// The type of the message that is published by the publisher. + /// The type of the message that is transmitted by the transmitter. /// - public abstract class MessagePublisher : MessageHandler, IMessagePublisher + public abstract class MessageTransmitter : MessageHandler, IMessageTransmitter where TMessage : class, IMessage { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// A processing intermediary that is used to process sub-commands. /// /// - /// An appliance that facilitates implementation-specific message publishing operations. + /// An appliance that facilitates implementation-specific message transmission operations. /// /// /// The targeted entity type. @@ -41,14 +41,14 @@ public abstract class MessagePublisher : MessageHandler, IMe /// /// is equal to . /// - protected MessagePublisher(ICommandMediator mediator, IMessagePublishingFacade facade, MessagingEntityType entityType) - : base(mediator, MessageHandlerRole.Publisher, entityType) + protected MessageTransmitter(ICommandMediator mediator, IMessageTransmittingFacade facade, MessagingEntityType entityType) + : base(mediator, MessageHandlerRole.Transmitter, entityType) { Facade = facade.RejectIf().IsNull(nameof(facade)).TargetArgument; } /// - /// Releases all resources consumed by the current . + /// Releases all resources consumed by the current . /// /// /// A value indicating whether or not managed resources should be released. @@ -74,12 +74,12 @@ protected override void Process(TMessage command, ICommandMediator mediator, Con { case MessagingEntityType.Queue: - controlToken.AttachTask(Facade.PublishToQueueAsync(command)); + controlToken.AttachTask(Facade.TransmitToQueueAsync(command)); break; case MessagingEntityType.Topic: - controlToken.AttachTask(Facade.PublishToTopicAsync(command)); + controlToken.AttachTask(Facade.TransmitToTopicAsync(command)); break; default: @@ -89,54 +89,54 @@ protected override void Process(TMessage command, ICommandMediator mediator, Con } /// - /// Gets the type of the message that the current publishes. + /// Gets the type of the message that the current transmits. /// public Type MessageType => typeof(TMessage); /// - /// Represents an appliance that facilitates implementation-specific message publishing operations. + /// Represents an appliance that facilitates implementation-specific message transmission operations. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly IMessagePublishingFacade Facade; + private readonly IMessageTransmittingFacade Facade; } /// - /// Publishes request messages. + /// Transmits request messages. /// /// - /// is the default implementation of - /// . + /// is the default implementation of + /// . /// /// - /// The type of the request message that is published by the publisher. + /// The type of the request message that is transmitted by the transmitter. /// /// - /// The type of the response message that is published in response to the request. + /// The type of the response message that is transmitted in response to the request. /// - public abstract class MessagePublisher : MessageHandler, IMessagePublisher + public abstract class MessageTransmitter : MessageHandler, IMessageTransmitter where TRequestMessage : class, IRequestMessage where TResponseMessage : class, IResponseMessage { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// A processing intermediary that is used to process sub-commands. /// /// - /// An appliance that facilitates implementation-specific request message publishing operations. + /// An appliance that facilitates implementation-specific request message transmission operations. /// /// /// is -or- is . /// - protected MessagePublisher(ICommandMediator mediator, IMessageRequestingFacade facade) - : base(mediator, MessageHandlerRole.Publisher, Message.RequestEntityType) + protected MessageTransmitter(ICommandMediator mediator, IMessageRequestingFacade facade) + : base(mediator, MessageHandlerRole.Transmitter, Message.RequestEntityType) { Facade = facade.RejectIf().IsNull(nameof(facade)).TargetArgument; } /// - /// Releases all resources consumed by the current . + /// Releases all resources consumed by the current . /// /// /// A value indicating whether or not managed resources should be released. @@ -169,13 +169,13 @@ protected override TResponseMessage Process(TRequestMessage command, ICommandMed } /// - /// Gets the type of the message that the current - /// publishes. + /// Gets the type of the message that the current + /// transmits. /// public Type MessageType => typeof(TRequestMessage); /// - /// Represents an appliance that facilitates implementation-specific message publishing operations. + /// Represents an appliance that facilitates implementation-specific message transmission operations. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly IMessageRequestingFacade Facade; diff --git a/src/RapidField.SolidInstruments.Messaging/MessagePublishingFacade.cs b/src/RapidField.SolidInstruments.Messaging/MessageTransmittingFacade.cs similarity index 70% rename from src/RapidField.SolidInstruments.Messaging/MessagePublishingFacade.cs rename to src/RapidField.SolidInstruments.Messaging/MessageTransmittingFacade.cs index e8116e07..85423c9e 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessagePublishingFacade.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessageTransmittingFacade.cs @@ -13,7 +13,7 @@ namespace RapidField.SolidInstruments.Messaging { /// - /// Facilitates implementation-specific publishing operations for a message bus. + /// Facilitates implementation-specific transmission operations for a message bus. /// /// /// The type of the implementation-specific send client. @@ -25,14 +25,14 @@ namespace RapidField.SolidInstruments.Messaging /// The type of implementation-specific adapted messages. /// /// - /// is the default implementation of - /// . + /// is the default implementation of + /// . /// - public abstract class MessagePublishingFacade : MessagingFacade, IMessagePublishingFacade + public abstract class MessageTransmittingFacade : MessagingFacade, IMessageTransmittingFacade where TAdaptedMessage : class { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// An appliance that creates manages implementation-specific messaging clients. @@ -44,20 +44,20 @@ public abstract class MessagePublishingFacade is -or- is /// . /// - protected MessagePublishingFacade(IMessagingClientFactory clientFactory, IMessageAdapter messageAdapter) + protected MessageTransmittingFacade(IMessagingClientFactory clientFactory, IMessageAdapter messageAdapter) : base(clientFactory, messageAdapter) { return; } /// - /// Asynchronously publishes the specified message to a queue. + /// Asynchronously transmits the specified message to a queue. /// /// - /// The type of the message to publish. + /// The type of the message to transmit. /// /// - /// The message to publish. + /// The message to transmit. /// /// /// A task representing the asynchronous operation. @@ -65,23 +65,23 @@ protected MessagePublishingFacade(IMessagingClientFactory /// is . /// - /// - /// An exception was raised while attempting to publish . + /// + /// An exception was raised while attempting to transmit . /// /// /// The object is disposed. /// - public Task PublishToQueueAsync(TMessage message) - where TMessage : class, IMessageBase => PublishToQueueAsync(message, null); + public Task TransmitToQueueAsync(TMessage message) + where TMessage : class, IMessageBase => TransmitToQueueAsync(message, null); /// - /// Asynchronously publishes the specified message to a queue. + /// Asynchronously transmits the specified message to a queue. /// /// - /// The type of the message to publish. + /// The type of the message to transmit. /// /// - /// The message to publish. + /// The message to transmit. /// /// /// An ordered collection of non-null, non-empty alphanumeric string tokens from which to construct the path, or @@ -96,23 +96,23 @@ public Task PublishToQueueAsync(TMessage message) /// /// is . /// - /// - /// An exception was raised while attempting to publish . + /// + /// An exception was raised while attempting to transmit . /// /// /// The object is disposed. /// - public Task PublishToQueueAsync(TMessage message, IEnumerable pathTokens) - where TMessage : class, IMessageBase => PublishAsync(message, pathTokens, MessagingEntityType.Queue); + public Task TransmitToQueueAsync(TMessage message, IEnumerable pathTokens) + where TMessage : class, IMessageBase => TransmitAsync(message, pathTokens, MessagingEntityType.Queue); /// - /// Asynchronously publishes the specified message to a topic. + /// Asynchronously transmits the specified message to a topic. /// /// - /// The type of the message to publish. + /// The type of the message to transmit. /// /// - /// The message to publish. + /// The message to transmit. /// /// /// A task representing the asynchronous operation. @@ -120,23 +120,23 @@ public Task PublishToQueueAsync(TMessage message, IEnumerable /// /// is . /// - /// - /// An exception was raised while attempting to publish . + /// + /// An exception was raised while attempting to transmit . /// /// /// The object is disposed. /// - public Task PublishToTopicAsync(TMessage message) - where TMessage : class, IMessageBase => PublishToTopicAsync(message, null); + public Task TransmitToTopicAsync(TMessage message) + where TMessage : class, IMessageBase => TransmitToTopicAsync(message, null); /// - /// Asynchronously publishes the specified message to a topic. + /// Asynchronously transmits the specified message to a topic. /// /// - /// The type of the message to publish. + /// The type of the message to transmit. /// /// - /// The message to publish. + /// The message to transmit. /// /// /// An ordered collection of non-null, non-empty alphanumeric string tokens from which to construct the path, or @@ -151,23 +151,23 @@ public Task PublishToTopicAsync(TMessage message) /// /// is . /// - /// - /// An exception was raised while attempting to publish . + /// + /// An exception was raised while attempting to transmit . /// /// /// The object is disposed. /// - public Task PublishToTopicAsync(TMessage message, IEnumerable pathTokens) - where TMessage : class, IMessageBase => PublishAsync(message, pathTokens, MessagingEntityType.Topic); + public Task TransmitToTopicAsync(TMessage message, IEnumerable pathTokens) + where TMessage : class, IMessageBase => TransmitAsync(message, pathTokens, MessagingEntityType.Topic); /// - /// Asynchronously publishes the specified message to a bus. + /// Asynchronously transmits the specified message to a bus. /// /// - /// The type of the message to publish. + /// The type of the message to transmit. /// /// - /// The message to publish. + /// The message to transmit. /// /// /// An ordered collection of non-null, non-empty alphanumeric string tokens from which to construct the path, or @@ -188,14 +188,14 @@ public Task PublishToTopicAsync(TMessage message, IEnumerable /// /// is equal to . /// - /// - /// An exception was raised while attempting to publish . + /// + /// An exception was raised while attempting to transmit . /// /// /// The object is disposed. /// [DebuggerHidden] - internal Task PublishAsync(TMessage message, IEnumerable pathTokens, MessagingEntityType entityType) + internal Task TransmitAsync(TMessage message, IEnumerable pathTokens, MessagingEntityType entityType) where TMessage : class, IMessageBase { message = message.RejectIf().IsNull(nameof(message)).TargetArgument; @@ -216,9 +216,9 @@ internal Task PublishAsync(TMessage message, IEnumerable pathT try { var adaptedMessage = MessageAdapter.ConvertForward(message) as TAdaptedMessage; - return PublishAsync(adaptedMessage, sendClient, controlToken); + return TransmitAsync(adaptedMessage, sendClient, controlToken); } - catch (MessagePublishingException) + catch (MessageTransmissionException) { throw; } @@ -228,14 +228,14 @@ internal Task PublishAsync(TMessage message, IEnumerable pathT } catch (Exception exception) { - throw new MessagePublishingException(typeof(TMessage), exception); + throw new MessageTransmissionException(typeof(TMessage), exception); } } } /// /// Releases all resources consumed by the current - /// . + /// . /// /// /// A value indicating whether or not managed resources should be released. @@ -243,10 +243,10 @@ internal Task PublishAsync(TMessage message, IEnumerable pathT protected override void Dispose(Boolean disposing) => base.Dispose(disposing); /// - /// Asynchronously publishes the specified message to a bus. + /// Asynchronously transmits the specified message to a bus. /// /// - /// The message to publish. + /// The message to transmit. /// /// /// An implementation-specific receive client. @@ -257,6 +257,6 @@ internal Task PublishAsync(TMessage message, IEnumerable pathT /// /// A task representing the asynchronous operation. /// - protected abstract Task PublishAsync(TAdaptedMessage message, TSender sendClient, ConcurrencyControlToken controlToken); + protected abstract Task TransmitAsync(TAdaptedMessage message, TSender sendClient, ConcurrencyControlToken controlToken); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/MessagingClientFactory.cs b/src/RapidField.SolidInstruments.Messaging/MessagingClientFactory.cs index 5953ece7..d553c3ad 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessagingClientFactory.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessagingClientFactory.cs @@ -60,7 +60,7 @@ protected MessagingClientFactory(TConnection connection) /// /// The managed, implementation-specific message receiver. /// - /// + /// /// An exception was raised while creating the client. /// /// @@ -85,7 +85,7 @@ public TReceiver GetQueueReceiver() /// /// contains one or more null or empty tokens and/or tokens with non-alphanumeric characters. /// - /// + /// /// An exception was raised while creating the client. /// /// @@ -103,7 +103,7 @@ public TReceiver GetQueueReceiver(IEnumerable pathTokens) /// /// The managed, implementation-specific message sender. /// - /// + /// /// An exception was raised while creating the client. /// /// @@ -128,7 +128,7 @@ public TSender GetQueueSender() /// /// contains one or more null or empty tokens and/or tokens with non-alphanumeric characters. /// - /// + /// /// An exception was raised while creating the client. /// /// @@ -155,7 +155,7 @@ public TSender GetQueueSender(IEnumerable pathTokens) /// /// is . /// - /// + /// /// An exception was raised while creating the client. /// /// @@ -189,7 +189,7 @@ public TReceiver GetTopicReceiver(String receiverIdentifier) /// /// is . /// - /// + /// /// An exception was raised while creating the client. /// /// @@ -207,7 +207,7 @@ public TReceiver GetTopicReceiver(String receiverIdentifier, IEnumerab /// /// The managed, implementation-specific message sender. /// - /// + /// /// An exception was raised while creating the client. /// /// @@ -232,7 +232,7 @@ public TSender GetTopicSender() /// /// contains one or more null or empty tokens and/or tokens with non-alphanumeric characters. /// - /// + /// /// An exception was raised while creating the client. /// /// @@ -396,7 +396,7 @@ private String GetEntityPath(String pathPrefix, Type messageType, IEnumerable /// contains one or more null or empty tokens and/or tokens with non-alphanumeric characters. /// - /// + /// /// An exception was raised while creating the client. /// /// @@ -425,7 +425,7 @@ private TReceiver GetMessageReceiver(MessagingEntityType entityType, S } catch (Exception exception) { - throw new MessageSubscribingException(typeof(TMessage), exception); + throw new MessageListeningException(typeof(TMessage), exception); } MessageReceivers.Add(entityPath, receiver); @@ -452,7 +452,7 @@ private TReceiver GetMessageReceiver(MessagingEntityType entityType, S /// /// contains one or more null or empty tokens and/or tokens with non-alphanumeric characters. /// - /// + /// /// An exception was raised while creating the client. /// /// @@ -479,7 +479,7 @@ private TSender GetMessageSender(MessagingEntityType entityType, IEnum } catch (Exception exception) { - throw new MessagePublishingException(typeof(TMessage), exception); + throw new MessageTransmissionException(typeof(TMessage), exception); } MessageSenders.Add(entityPath, sender); diff --git a/src/RapidField.SolidInstruments.Messaging/MessagingEntityType.cs b/src/RapidField.SolidInstruments.Messaging/MessagingEntityType.cs index 4de50fff..baca7ec3 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessagingEntityType.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessagingEntityType.cs @@ -7,7 +7,7 @@ namespace RapidField.SolidInstruments.Messaging { /// - /// Defines the targeted entity type for a message publishing or subscription operation. + /// Defines the targeted entity type for a message transmission or listening operation. /// public enum MessagingEntityType : Int32 { diff --git a/src/RapidField.SolidInstruments.Messaging/QueueSubscriber.cs b/src/RapidField.SolidInstruments.Messaging/QueueListener.cs similarity index 78% rename from src/RapidField.SolidInstruments.Messaging/QueueSubscriber.cs rename to src/RapidField.SolidInstruments.Messaging/QueueListener.cs index 8e707f72..f609472c 100644 --- a/src/RapidField.SolidInstruments.Messaging/QueueSubscriber.cs +++ b/src/RapidField.SolidInstruments.Messaging/QueueListener.cs @@ -8,16 +8,16 @@ namespace RapidField.SolidInstruments.Messaging { /// - /// Processes queue messages as a subscriber. + /// Processes queue messages as a listener. /// /// - /// The type of the message that is subscribed to. + /// The type of the message that is listened for. /// - public abstract class QueueSubscriber : MessageSubscriber + public abstract class QueueListener : MessageListener where TMessage : class, IMessage { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// A processing intermediary that is used to process sub-commands. @@ -25,14 +25,14 @@ public abstract class QueueSubscriber : MessageSubscriber /// /// is . /// - protected QueueSubscriber(ICommandMediator mediator) + protected QueueListener(ICommandMediator mediator) : base(mediator, MessagingEntityType.Queue) { return; } /// - /// Releases all resources consumed by the current . + /// Releases all resources consumed by the current . /// /// /// A value indicating whether or not managed resources should be released. diff --git a/src/RapidField.SolidInstruments.Messaging/QueuePublisher.cs b/src/RapidField.SolidInstruments.Messaging/QueueTransmitter.cs similarity index 84% rename from src/RapidField.SolidInstruments.Messaging/QueuePublisher.cs rename to src/RapidField.SolidInstruments.Messaging/QueueTransmitter.cs index 0b7b5131..7d352ac6 100644 --- a/src/RapidField.SolidInstruments.Messaging/QueuePublisher.cs +++ b/src/RapidField.SolidInstruments.Messaging/QueueTransmitter.cs @@ -9,34 +9,34 @@ namespace RapidField.SolidInstruments.Messaging { /// - /// Publishes messages to a queue. + /// Transmits messages to a queue. /// /// - /// The type of the message that is published by the publisher. + /// The type of the message that is transmitted by the transmitter. /// - public class QueuePublisher : MessagePublisher + public class QueueTransmitter : MessageTransmitter where TMessage : class, IMessage { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// A processing intermediary that is used to process sub-commands. /// /// - /// An appliance that facilitates implementation-specific message publishing operations. + /// An appliance that facilitates implementation-specific message transmission operations. /// /// /// is -or- is . /// - public QueuePublisher(ICommandMediator mediator, IMessagePublishingFacade facade) + public QueueTransmitter(ICommandMediator mediator, IMessageTransmittingFacade facade) : base(mediator, facade, MessagingEntityType.Queue) { return; } /// - /// Releases all resources consumed by the current . + /// Releases all resources consumed by the current . /// /// /// A value indicating whether or not managed resources should be released. diff --git a/src/RapidField.SolidInstruments.Messaging/RequestSubscriber.cs b/src/RapidField.SolidInstruments.Messaging/RequestListener.cs similarity index 78% rename from src/RapidField.SolidInstruments.Messaging/RequestSubscriber.cs rename to src/RapidField.SolidInstruments.Messaging/RequestListener.cs index bf85c870..933ad4df 100644 --- a/src/RapidField.SolidInstruments.Messaging/RequestSubscriber.cs +++ b/src/RapidField.SolidInstruments.Messaging/RequestListener.cs @@ -8,20 +8,20 @@ namespace RapidField.SolidInstruments.Messaging { /// - /// Processes request messages as a subscriber. + /// Processes request messages as a listener. /// /// - /// The type of the request message that is processed by the subscriber. + /// The type of the request message that is processed by the listener. /// /// - /// The type of the response message that is published in response to the request. + /// The type of the response message that is transmitted in response to the request. /// - public abstract class RequestSubscriber : MessageSubscriber + public abstract class RequestListener : MessageListener where TRequestMessage : class, IRequestMessage where TResponseMessage : class, IResponseMessage { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// A processing intermediary that is used to process sub-commands. @@ -29,14 +29,14 @@ public abstract class RequestSubscriber : Mes /// /// is . /// - protected RequestSubscriber(ICommandMediator mediator) + protected RequestListener(ICommandMediator mediator) : base(mediator) { return; } /// - /// Releases all resources consumed by the current . + /// Releases all resources consumed by the current . /// /// /// A value indicating whether or not managed resources should be released. diff --git a/src/RapidField.SolidInstruments.Messaging/RequestPublisher.cs b/src/RapidField.SolidInstruments.Messaging/RequestTransmitter.cs similarity index 82% rename from src/RapidField.SolidInstruments.Messaging/RequestPublisher.cs rename to src/RapidField.SolidInstruments.Messaging/RequestTransmitter.cs index ac1a23c8..7ac61831 100644 --- a/src/RapidField.SolidInstruments.Messaging/RequestPublisher.cs +++ b/src/RapidField.SolidInstruments.Messaging/RequestTransmitter.cs @@ -9,38 +9,38 @@ namespace RapidField.SolidInstruments.Messaging { /// - /// Publishes request messages. + /// Transmits request messages. /// /// - /// The type of the request message that is published by the publisher. + /// The type of the request message that is transmitted by the transmitter. /// /// - /// The type of the response message that is published in response to the request. + /// The type of the response message that is transmitted in response to the request. /// - public class RequestPublisher : MessagePublisher + public class RequestTransmitter : MessageTransmitter where TRequestMessage : class, IRequestMessage where TResponseMessage : class, IResponseMessage { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// A processing intermediary that is used to process sub-commands. /// /// - /// An appliance that facilitates implementation-specific request message publishing operations. + /// An appliance that facilitates implementation-specific request message transmission operations. /// /// /// is -or- is . /// - public RequestPublisher(ICommandMediator mediator, IMessageRequestingFacade facade) + public RequestTransmitter(ICommandMediator mediator, IMessageRequestingFacade facade) : base(mediator, facade) { return; } /// - /// Releases all resources consumed by the current . + /// Releases all resources consumed by the current . /// /// /// A value indicating whether or not managed resources should be released. diff --git a/src/RapidField.SolidInstruments.Messaging/Service/HeartbeatMessage.cs b/src/RapidField.SolidInstruments.Messaging/Service/HeartbeatMessage.cs index 3ea90fb7..105a2b08 100644 --- a/src/RapidField.SolidInstruments.Messaging/Service/HeartbeatMessage.cs +++ b/src/RapidField.SolidInstruments.Messaging/Service/HeartbeatMessage.cs @@ -9,7 +9,7 @@ namespace RapidField.SolidInstruments.Messaging.Service { /// - /// Represents a message that is published at a regular interval. + /// Represents a message that is transmitted at a regular interval. /// [DataContract] public class HeartbeatMessage : Message @@ -27,7 +27,7 @@ public HeartbeatMessage() /// Initializes a new instance of the class. /// /// - /// The regular interval, in seconds, at which the message is published. + /// The regular interval, in seconds, at which the message is transmitted. /// /// /// is less than or equal to zero. @@ -42,7 +42,7 @@ public HeartbeatMessage(Int32 intervalInSeconds) /// Initializes a new instance of the class. /// /// - /// The regular interval, in seconds, at which the message is published. + /// The regular interval, in seconds, at which the message is transmitted. /// /// /// The label, if any, that is associated with the message. This argument can be null. @@ -58,7 +58,7 @@ public HeartbeatMessage(Int32 intervalInSeconds, String label) } /// - /// Gets or sets the regular interval, in seconds, at which the message is published. + /// Gets or sets the regular interval, in seconds, at which the message is transmitted. /// [DataMember] public Int32 IntervalInSeconds diff --git a/src/RapidField.SolidInstruments.Messaging/Service/HeartbeatSchedule.cs b/src/RapidField.SolidInstruments.Messaging/Service/HeartbeatSchedule.cs index d0db5ca8..b1837e6e 100644 --- a/src/RapidField.SolidInstruments.Messaging/Service/HeartbeatSchedule.cs +++ b/src/RapidField.SolidInstruments.Messaging/Service/HeartbeatSchedule.cs @@ -13,7 +13,7 @@ namespace RapidField.SolidInstruments.Messaging.Service { /// /// Represents a collection of definitions that a - /// uses to publish + /// uses to transmit /// heartbeat messages. /// public sealed class HeartbeatSchedule : IEnumerable @@ -30,7 +30,7 @@ public HeartbeatSchedule() /// Adds the specified heartbeat schedule item to the schedule. /// /// - /// The regular interval, in seconds, at which the message is published. + /// The regular interval, in seconds, at which the message is transmitted. /// /// /// is less than or equal to zero. @@ -47,7 +47,7 @@ public HeartbeatSchedule() /// The type of the associated heartbeat message. /// /// - /// The regular interval, in seconds, at which the message is published. + /// The regular interval, in seconds, at which the message is transmitted. /// /// /// is less than or equal to zero. @@ -62,10 +62,10 @@ public void AddItem(Int32 intervalInSeconds) /// Adds the specified heartbeat schedule item to the schedule. /// /// - /// The regular interval, in seconds, at which the message is published. + /// The regular interval, in seconds, at which the message is transmitted. /// /// - /// The messaging entity type that is used when publishing the message. The default value is + /// The messaging entity type that is used when transmitting the message. The default value is /// . /// /// @@ -84,10 +84,10 @@ public void AddItem(Int32 intervalInSeconds) /// The type of the associated heartbeat message. /// /// - /// The regular interval, in seconds, at which the message is published. + /// The regular interval, in seconds, at which the message is transmitted. /// /// - /// The messaging entity type that is used when publishing the message. The default value is + /// The messaging entity type that is used when transmitting the message. The default value is /// . /// /// @@ -104,10 +104,10 @@ public void AddItem(Int32 intervalInSeconds, MessagingEntityType entit /// Adds the specified heartbeat schedule item to the schedule. /// /// - /// The regular interval, in seconds, at which the message is published. + /// The regular interval, in seconds, at which the message is transmitted. /// /// - /// The messaging entity type that is used when publishing the message. The default value is + /// The messaging entity type that is used when transmitting the message. The default value is /// . /// /// @@ -129,10 +129,10 @@ public void AddItem(Int32 intervalInSeconds, MessagingEntityType entit /// The type of the associated heartbeat message. /// /// - /// The regular interval, in seconds, at which the message is published. + /// The regular interval, in seconds, at which the message is transmitted. /// /// - /// The messaging entity type that is used when publishing the message. The default value is + /// The messaging entity type that is used when transmitting the message. The default value is /// . /// /// @@ -168,7 +168,7 @@ public void AddItem(Int32 intervalInSeconds, MessagingEntityType entit /// Adds the specified heartbeat schedule item to the schedule. /// /// - /// An item that specifies how a heartbeat message is published. + /// An item that specifies how a heartbeat message is transmitted. /// /// /// is . diff --git a/src/RapidField.SolidInstruments.Messaging/Service/HeartbeatScheduleItem.cs b/src/RapidField.SolidInstruments.Messaging/Service/HeartbeatScheduleItem.cs index e9f3d2cd..f469aaf4 100644 --- a/src/RapidField.SolidInstruments.Messaging/Service/HeartbeatScheduleItem.cs +++ b/src/RapidField.SolidInstruments.Messaging/Service/HeartbeatScheduleItem.cs @@ -11,7 +11,7 @@ namespace RapidField.SolidInstruments.Messaging.Service { /// - /// Specifies a message type, entity type, interval and label for a regularly-published heartbeat message. + /// Specifies a message type, entity type, interval and label for a regularly-transmitted heartbeat message. /// /// /// is the default implementation of . @@ -26,7 +26,7 @@ public sealed class HeartbeatScheduleItem : IHeartbeatScheduleItem /// Initializes a new instance of the class. /// /// - /// The regular interval, in seconds, at which the message is published. + /// The regular interval, in seconds, at which the message is transmitted. /// /// /// is less than or equal to zero. @@ -42,10 +42,10 @@ internal HeartbeatScheduleItem(Int32 intervalInSeconds) /// Initializes a new instance of the class. /// /// - /// The regular interval, in seconds, at which the message is published. + /// The regular interval, in seconds, at which the message is transmitted. /// /// - /// The messaging entity type that is used when publishing the message. The default value is + /// The messaging entity type that is used when transmitting the message. The default value is /// . /// /// @@ -63,10 +63,10 @@ internal HeartbeatScheduleItem(Int32 intervalInSeconds, MessagingEntityType enti /// Initializes a new instance of the class. /// /// - /// The regular interval, in seconds, at which the message is published. + /// The regular interval, in seconds, at which the message is transmitted. /// /// - /// The messaging entity type that is used when publishing the message. The default value is + /// The messaging entity type that is used when transmitting the message. The default value is /// . /// /// @@ -268,24 +268,24 @@ public override Boolean Equals(Object obj) public override Int32 GetHashCode() => ((IntervalInSeconds ^ (Int32)EntityType) ^ ((Label is null ? 0 : Label.GetHashCode()) ^ MessageType.FullName.GetHashCode())); /// - /// Asynchronously publishes a single heartbeat message with characteristics defined by the current + /// Asynchronously transmits a single heartbeat message with characteristics defined by the current /// . /// - /// - /// An appliance that facilitates message publishing operations. + /// + /// An appliance that facilitates message transmitting operations. /// /// /// A task representing the asynchronous operation. /// /// - /// is null. + /// is null. /// - /// - /// An exception was raised while attempting to publish the heartbeat message. + /// + /// An exception was raised while attempting to transmit the heartbeat message. /// - public Task PublishHeartbeatMessageAsync(IMessagePublishingFacade messagePublishingFacade) + public Task TransmitHeartbeatMessageAsync(IMessageTransmittingFacade messageTransmittingFacade) { - messagePublishingFacade = messagePublishingFacade.RejectIf().IsNull(nameof(messagePublishingFacade)).TargetArgument; + messageTransmittingFacade = messageTransmittingFacade.RejectIf().IsNull(nameof(messageTransmittingFacade)).TargetArgument; try { @@ -297,23 +297,23 @@ public Task PublishHeartbeatMessageAsync(IMessagePublishingFacade messagePublish return EntityType switch { - MessagingEntityType.Queue => messagePublishingFacade.PublishToQueueAsync(message), - MessagingEntityType.Topic => messagePublishingFacade.PublishToTopicAsync(message), + MessagingEntityType.Queue => messageTransmittingFacade.TransmitToQueueAsync(message), + MessagingEntityType.Topic => messageTransmittingFacade.TransmitToTopicAsync(message), _ => throw new UnsupportedSpecificationException($"The specified messaging entity type, {EntityType}, is not supported.") }; } - catch (MessagePublishingException) + catch (MessageTransmissionException) { throw; } catch (Exception exception) { - throw new MessagePublishingException(typeof(HeartbeatMessage), exception); + throw new MessageTransmissionException(typeof(HeartbeatMessage), exception); } } /// - /// Gets the messaging entity type that is used when publishing the message. + /// Gets the messaging entity type that is used when transmitting the message. /// public MessagingEntityType EntityType { @@ -321,7 +321,7 @@ public MessagingEntityType EntityType } /// - /// Gets the regular interval, in seconds, at which the message is published. + /// Gets the regular interval, in seconds, at which the message is transmitted. /// public Int32 IntervalInSeconds { @@ -342,7 +342,7 @@ public String Label public Type MessageType => typeof(TMessage); /// - /// Represents the default messaging entity type that is used when publishing messages. + /// Represents the default messaging entity type that is used when transmitting messages. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] private const MessagingEntityType DefaultEntityType = MessagingEntityType.Topic; diff --git a/src/RapidField.SolidInstruments.Messaging/Service/IHeartbeatScheduleItem.cs b/src/RapidField.SolidInstruments.Messaging/Service/IHeartbeatScheduleItem.cs index d0165b1d..3fbe9926 100644 --- a/src/RapidField.SolidInstruments.Messaging/Service/IHeartbeatScheduleItem.cs +++ b/src/RapidField.SolidInstruments.Messaging/Service/IHeartbeatScheduleItem.cs @@ -8,30 +8,30 @@ namespace RapidField.SolidInstruments.Messaging.Service { /// - /// Specifies a message type, entity type, interval and label for a regularly-published heartbeat message. + /// Specifies a message type, entity type, interval and label for a regularly-transmitted heartbeat message. /// public interface IHeartbeatScheduleItem : IComparable, IEquatable { /// - /// Asynchronously publishes a single heartbeat message with characteristics defined by the current + /// Asynchronously transmits a single heartbeat message with characteristics defined by the current /// . /// - /// - /// An appliance that facilitates message publishing operations. + /// + /// An appliance that facilitates message transmitting operations. /// /// /// A task representing the asynchronous operation. /// /// - /// is null. + /// is null. /// - /// - /// An exception was raised while attempting to publish the heartbeat message. + /// + /// An exception was raised while attempting to transmit the heartbeat message. /// - Task PublishHeartbeatMessageAsync(IMessagePublishingFacade messagePublishingFacade); + Task TransmitHeartbeatMessageAsync(IMessageTransmittingFacade messageTransmittingFacade); /// - /// Gets the messaging entity type that is used when publishing the message. + /// Gets the messaging entity type that is used when transmitting the message. /// MessagingEntityType EntityType { @@ -39,7 +39,7 @@ MessagingEntityType EntityType } /// - /// Gets the regular interval, in seconds, at which the message is published. + /// Gets the regular interval, in seconds, at which the message is transmitted. /// Int32 IntervalInSeconds { diff --git a/src/RapidField.SolidInstruments.Messaging/Service/IMessageSubscriptionProfile.cs b/src/RapidField.SolidInstruments.Messaging/Service/IMessageListeningProfile.cs similarity index 90% rename from src/RapidField.SolidInstruments.Messaging/Service/IMessageSubscriptionProfile.cs rename to src/RapidField.SolidInstruments.Messaging/Service/IMessageListeningProfile.cs index feeecc53..0ac8a114 100644 --- a/src/RapidField.SolidInstruments.Messaging/Service/IMessageSubscriptionProfile.cs +++ b/src/RapidField.SolidInstruments.Messaging/Service/IMessageListeningProfile.cs @@ -8,10 +8,10 @@ namespace RapidField.SolidInstruments.Messaging.Service { /// - /// Manages the subscriber types that are supported by a + /// Manages the listener types that are supported by a /// . /// - public interface IMessageSubscriptionProfile + public interface IMessageListeningProfile { /// /// Adds support for the specified queue message type. @@ -22,7 +22,7 @@ public interface IMessageSubscriptionProfile /// /// was already added. /// - void AddQueueSubscriber() + void AddQueueListener() where TMessage : class, IMessage; /// @@ -37,7 +37,7 @@ void AddQueueSubscriber() /// /// was already added. /// - void AddRequestSubscriber() + void AddRequestListener() where TRequestMessage : class, IRequestMessage where TResponseMessage : class, IResponseMessage; @@ -50,7 +50,7 @@ void AddRequestSubscriber() /// /// was already added. /// - void AddTopicSubscriber() + void AddTopicListener() where TMessage : class, IMessage; /// diff --git a/src/RapidField.SolidInstruments.Messaging/Service/MessageSubscriptionProfile.cs b/src/RapidField.SolidInstruments.Messaging/Service/MessageListeningProfile.cs similarity index 80% rename from src/RapidField.SolidInstruments.Messaging/Service/MessageSubscriptionProfile.cs rename to src/RapidField.SolidInstruments.Messaging/Service/MessageListeningProfile.cs index 1f0e8fb6..a0273f02 100644 --- a/src/RapidField.SolidInstruments.Messaging/Service/MessageSubscriptionProfile.cs +++ b/src/RapidField.SolidInstruments.Messaging/Service/MessageListeningProfile.cs @@ -12,16 +12,16 @@ namespace RapidField.SolidInstruments.Messaging.Service { /// - /// Manages the subscriber types that are supported by an + /// Manages the listener types that are supported by an /// . /// /// - /// is the default implementation of . + /// is the default implementation of . /// - public sealed class MessageSubscriptionProfile : IMessageSubscriptionProfile + public sealed class MessageListeningProfile : IMessageListeningProfile { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// A dependency scope that spans the full lifetime of execution for the associated @@ -31,7 +31,7 @@ public sealed class MessageSubscriptionProfile : IMessageSubscriptionProfile /// is . /// [DebuggerHidden] - internal MessageSubscriptionProfile(IDependencyScope rootDependencyScope) + internal MessageListeningProfile(IDependencyScope rootDependencyScope) : base() { RootDependencyScope = rootDependencyScope.RejectIf().IsNull(nameof(rootDependencyScope)).TargetArgument; @@ -46,8 +46,8 @@ internal MessageSubscriptionProfile(IDependencyScope rootDependencyScope) /// /// was already added. /// - public void AddQueueSubscriber() - where TMessage : class, IMessage => AddSubscriber(MessagingEntityType.Queue); + public void AddQueueListener() + where TMessage : class, IMessage => AddListener(MessagingEntityType.Queue); /// /// Adds support for the specified request message type. @@ -61,7 +61,7 @@ public void AddQueueSubscriber() /// /// was already added. /// - public void AddRequestSubscriber() + public void AddRequestListener() where TRequestMessage : class, IRequestMessage where TResponseMessage : class, IResponseMessage { @@ -73,7 +73,7 @@ public void AddRequestSubscriber() } SupportedMessageTypesReference.Add(requestMessageType); - RootDependencyScope.Resolve().RegisterRequestMessageHandler((requestMessage) => HandleRequestMessage(requestMessage)); + RootDependencyScope.Resolve().RegisterRequestMessageHandler((requestMessage) => HandleRequestMessage(requestMessage)); } /// @@ -85,8 +85,8 @@ public void AddRequestSubscriber() /// /// was already added. /// - public void AddTopicSubscriber() - where TMessage : class, IMessage => AddSubscriber(MessagingEntityType.Topic); + public void AddTopicListener() + where TMessage : class, IMessage => AddListener(MessagingEntityType.Topic); /// /// Adds support for the specified message type. @@ -104,7 +104,7 @@ public void AddTopicSubscriber() /// was already added. /// [DebuggerHidden] - private void AddSubscriber(MessagingEntityType entityType) + private void AddListener(MessagingEntityType entityType) where TMessage : class, IMessage { var messageType = typeof(TMessage); @@ -120,12 +120,12 @@ private void AddSubscriber(MessagingEntityType entityType) { case MessagingEntityType.Queue: - RootDependencyScope.Resolve().RegisterQueueMessageHandler((message) => HandleMessage(message)); + RootDependencyScope.Resolve().RegisterQueueMessageHandler((message) => HandleMessage(message)); break; case MessagingEntityType.Topic: - RootDependencyScope.Resolve().RegisterTopicMessageHandler((message) => HandleMessage(message)); + RootDependencyScope.Resolve().RegisterTopicMessageHandler((message) => HandleMessage(message)); break; default: @@ -151,19 +151,19 @@ private void HandleMessage(TMessage message) { using (var dependencyScope = RootDependencyScope.CreateChildScope()) { - using (var subscriber = dependencyScope.Resolve>()) + using (var listener = dependencyScope.Resolve>()) { - subscriber.Process(message); + listener.Process(message); } } } - catch (MessageSubscribingException) + catch (MessageListeningException) { throw; } catch (Exception exception) { - throw new MessageSubscribingException(typeof(TMessage), exception); + throw new MessageListeningException(typeof(TMessage), exception); } } @@ -188,19 +188,19 @@ private TResponseMessage HandleRequestMessage { using (var dependencyScope = RootDependencyScope.CreateChildScope()) { - using (var subscriber = dependencyScope.Resolve>()) + using (var listener = dependencyScope.Resolve>()) { - return subscriber.Process(requestMessage); + return listener.Process(requestMessage); } } } - catch (MessageSubscribingException) + catch (MessageListeningException) { throw; } catch (Exception exception) { - throw new MessageSubscribingException(typeof(TRequestMessage), exception); + throw new MessageListeningException(typeof(TRequestMessage), exception); } } diff --git a/src/RapidField.SolidInstruments.Messaging/Service/MessagingServiceExecutor.cs b/src/RapidField.SolidInstruments.Messaging/Service/MessagingServiceExecutor.cs index 14512b89..b168edcc 100644 --- a/src/RapidField.SolidInstruments.Messaging/Service/MessagingServiceExecutor.cs +++ b/src/RapidField.SolidInstruments.Messaging/Service/MessagingServiceExecutor.cs @@ -49,28 +49,28 @@ protected MessagingServiceExecutor(String serviceName) { LazyHeartbeatSchedule = new Lazy(CreateHeartbeatSchedule, LazyThreadSafetyMode.ExecutionAndPublication); LazyHeartbeatTimers = new Lazy>(() => new List(), LazyThreadSafetyMode.ExecutionAndPublication); - LazySubscriptionProfile = new Lazy(CreateSubscriptionProfile, LazyThreadSafetyMode.ExecutionAndPublication); + LazyListeningProfile = new Lazy(CreateListeningProfile, LazyThreadSafetyMode.ExecutionAndPublication); } /// /// Adds message subscriptions to the service. /// - /// + /// /// An object that is used to add subscriptions. /// /// /// Configuration information for the service application. /// - protected virtual void AddSubscriptions(IMessageSubscriptionProfile subscriptionProfile, IConfiguration applicationConfiguration) + protected virtual void AddSubscriptions(IMessageListeningProfile listeningProfile, IConfiguration applicationConfiguration) { return; } /// - /// Configures the service to publish heartbeat messages. + /// Configures the service to transmit heartbeat messages. /// /// - /// An object that defines how the service publishes heartbeat messages. + /// An object that defines how the service transmits heartbeat messages. /// /// /// Configuration information for the service application. @@ -112,7 +112,7 @@ protected sealed override void Execute(IDependencyScope dependencyScope, IConfig try { - AddSubscriptions(SubscriptionProfile, ApplicationConfiguration); + AddSubscriptions(ListeningProfile, ApplicationConfiguration); StartHeartbeats(); executionLifetime.KeepAlive(); } @@ -178,65 +178,21 @@ private HeartbeatSchedule CreateHeartbeatSchedule() } /// - /// Creates the subscription profile for the service. + /// Creates the listening profile for the service. /// /// - /// The subscription profile for the service. + /// The listening profile for the service. /// [DebuggerHidden] - private IMessageSubscriptionProfile CreateSubscriptionProfile() + private IMessageListeningProfile CreateListeningProfile() { var dependencyScope = CreateDependencyScope(); ReferenceManager.AddObject(dependencyScope); - return new MessageSubscriptionProfile(dependencyScope); + return new MessageListeningProfile(dependencyScope); } /// - /// Asynchronously publishes a heartbeat message using the specified schedule item. - /// - /// - /// A schedule item that defines characteristics of the message. - /// - /// - /// A task representing the asynchronous operation. - /// - /// - /// An exception was raised while attempting to publish the heartbeat message. - /// - [DebuggerHidden] - private Task PublishHeartbeatMessageAsync(IHeartbeatScheduleItem scheduleItem) - { - try - { - var dependencyScope = CreateDependencyScope(); - - try - { - var messagePublishingFacade = dependencyScope.Resolve(); - - return scheduleItem.PublishHeartbeatMessageAsync(messagePublishingFacade).ContinueWith(publishHeartbeatMessageTask => - { - dependencyScope.Dispose(); - }); - } - catch - { - dependencyScope.Dispose(); - throw; - } - } - catch (MessagePublishingException) - { - throw; - } - catch (Exception exception) - { - throw new MessagePublishingException(typeof(HeartbeatMessage), exception); - } - } - - /// - /// Creates a new timer that publishes messages according to the supplied specifications. + /// Creates a new timer that transmits messages according to the supplied specifications. /// /// /// Specifications for the heartbeat. @@ -244,7 +200,7 @@ private Task PublishHeartbeatMessageAsync(IHeartbeatScheduleItem scheduleItem) [DebuggerHidden] private void ScheduleHeartbeat(IHeartbeatScheduleItem heartbeatScheduleItem) { - var timerCallback = new TimerCallback((state) => PublishHeartbeatMessageAsync(state as IHeartbeatScheduleItem).Wait()); + var timerCallback = new TimerCallback((state) => TransmitHeartbeatMessageAsync(state as IHeartbeatScheduleItem).Wait()); var timer = new Timer(timerCallback, heartbeatScheduleItem, TimeSpan.Zero, TimeSpan.FromSeconds(heartbeatScheduleItem.IntervalInSeconds)); HeartbeatTimers.Add(timer); ReferenceManager.AddObject(timer); @@ -275,6 +231,50 @@ private void StopHeartbeats() } } + /// + /// Asynchronously transmits a heartbeat message using the specified schedule item. + /// + /// + /// A schedule item that defines characteristics of the message. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// An exception was raised while attempting to transmit the heartbeat message. + /// + [DebuggerHidden] + private Task TransmitHeartbeatMessageAsync(IHeartbeatScheduleItem scheduleItem) + { + try + { + var dependencyScope = CreateDependencyScope(); + + try + { + var messageTransmittingFacade = dependencyScope.Resolve(); + + return scheduleItem.TransmitHeartbeatMessageAsync(messageTransmittingFacade).ContinueWith(transmitHeartbeatMessageTask => + { + dependencyScope.Dispose(); + }); + } + catch + { + dependencyScope.Dispose(); + throw; + } + } + catch (MessageTransmissionException) + { + throw; + } + catch (Exception exception) + { + throw new MessageTransmissionException(typeof(HeartbeatMessage), exception); + } + } + /// /// Gets the heartbeat message schedule for the service. /// @@ -282,16 +282,16 @@ private void StopHeartbeats() private HeartbeatSchedule HeartbeatSchedule => LazyHeartbeatSchedule.Value; /// - /// Gets a collection of timers that control heartbeat publishing. + /// Gets a collection of timers that control heartbeat transmission. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] private IList HeartbeatTimers => LazyHeartbeatTimers.Value; /// - /// Gets the subscription profile for the service. + /// Gets the listening profile for the service. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private IMessageSubscriptionProfile SubscriptionProfile => LazySubscriptionProfile.Value; + private IMessageListeningProfile ListeningProfile => LazyListeningProfile.Value; /// /// Represents the lazily-initialized heartbeat message schedule for the service. @@ -300,15 +300,15 @@ private void StopHeartbeats() private readonly Lazy LazyHeartbeatSchedule; /// - /// Represents a lazily-initialized collection of timers that control heartbeat publishing. + /// Represents a lazily-initialized collection of timers that control heartbeat transmission. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Lazy> LazyHeartbeatTimers; /// - /// Represents the lazily-initialized + /// Represents the lazily-initialized listening profile for the service. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly Lazy LazySubscriptionProfile; + private readonly Lazy LazyListeningProfile; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TopicSubscriber.cs b/src/RapidField.SolidInstruments.Messaging/TopicListener.cs similarity index 78% rename from src/RapidField.SolidInstruments.Messaging/TopicSubscriber.cs rename to src/RapidField.SolidInstruments.Messaging/TopicListener.cs index 2244e799..75f2807b 100644 --- a/src/RapidField.SolidInstruments.Messaging/TopicSubscriber.cs +++ b/src/RapidField.SolidInstruments.Messaging/TopicListener.cs @@ -8,16 +8,16 @@ namespace RapidField.SolidInstruments.Messaging { /// - /// Processes topic messages as a subscriber. + /// Processes topic messages as a listener. /// /// - /// The type of the message that is subscribed to. + /// The type of the message that is listened for. /// - public abstract class TopicSubscriber : MessageSubscriber + public abstract class TopicListener : MessageListener where TMessage : class, IMessage { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// A processing intermediary that is used to process sub-commands. @@ -25,14 +25,14 @@ public abstract class TopicSubscriber : MessageSubscriber /// /// is . /// - protected TopicSubscriber(ICommandMediator mediator) + protected TopicListener(ICommandMediator mediator) : base(mediator, MessagingEntityType.Topic) { return; } /// - /// Releases all resources consumed by the current . + /// Releases all resources consumed by the current . /// /// /// A value indicating whether or not managed resources should be released. diff --git a/src/RapidField.SolidInstruments.Messaging/TopicPublisher.cs b/src/RapidField.SolidInstruments.Messaging/TopicTransmitter.cs similarity index 84% rename from src/RapidField.SolidInstruments.Messaging/TopicPublisher.cs rename to src/RapidField.SolidInstruments.Messaging/TopicTransmitter.cs index f1fe5b7a..443800e4 100644 --- a/src/RapidField.SolidInstruments.Messaging/TopicPublisher.cs +++ b/src/RapidField.SolidInstruments.Messaging/TopicTransmitter.cs @@ -9,34 +9,34 @@ namespace RapidField.SolidInstruments.Messaging { /// - /// Publishes messages to a topic. + /// Transmits messages to a topic. /// /// - /// The type of the message that is published by the publisher. + /// The type of the message that is transmitted by the transmitter. /// - public class TopicPublisher : MessagePublisher + public class TopicTransmitter : MessageTransmitter where TMessage : class, IMessage { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// A processing intermediary that is used to process sub-commands. /// /// - /// An appliance that facilitates implementation-specific message publishing operations. + /// An appliance that facilitates implementation-specific message transmission operations. /// /// /// is -or- is . /// - public TopicPublisher(ICommandMediator mediator, IMessagePublishingFacade facade) + public TopicTransmitter(ICommandMediator mediator, IMessageTransmittingFacade facade) : base(mediator, facade, MessagingEntityType.Topic) { return; } /// - /// Releases all resources consumed by the current . + /// Releases all resources consumed by the current . /// /// /// A value indicating whether or not managed resources should be released. diff --git a/test/RapidField.SolidInstruments.Messaging.UnitTests/MessageProcessingInformationTests.cs b/test/RapidField.SolidInstruments.Messaging.UnitTests/MessageProcessingInformationTests.cs index 4cb28c9d..e15b5e79 100644 --- a/test/RapidField.SolidInstruments.Messaging.UnitTests/MessageProcessingInformationTests.cs +++ b/test/RapidField.SolidInstruments.Messaging.UnitTests/MessageProcessingInformationTests.cs @@ -68,11 +68,11 @@ private static void ShouldBeSerializable(SerializationFormat format) // Arrange. var retryCount = 3; var baseDelayDurationInSeconds = 1; - var durationScale = MessageSubscribingRetryDurationScale.Fibonacci; - var retryPolicy = new MessageSubscribingRetryPolicy(retryCount, baseDelayDurationInSeconds, durationScale); - var secondaryFailureBehavior = MessageSubscribingSecondaryFailureBehavior.Discard; - var publishExceptionRaisedMessage = true; - var failurePolicy = new MessageSubscribingFailurePolicy(retryPolicy, secondaryFailureBehavior, publishExceptionRaisedMessage); + var durationScale = MessageListeningRetryDurationScale.Fibonacci; + var retryPolicy = new MessageListeningRetryPolicy(retryCount, baseDelayDurationInSeconds, durationScale); + var secondaryFailureBehavior = MessageListeningSecondaryFailureBehavior.Discard; + var transmitExceptionRaisedMessage = true; + var failurePolicy = new MessageListeningFailurePolicy(retryPolicy, secondaryFailureBehavior, transmitExceptionRaisedMessage); var target = new MessageProcessingInformation(failurePolicy); var attemptEndTimeStamp = DateTime.UtcNow; var attemptStartTimeStamp = (DateTime?)null; @@ -94,7 +94,7 @@ private static void ShouldBeSerializable(SerializationFormat format) deserializedResult.AttemptResults.Single().AttemptStartTimeStamp.Should().Be(attemptStartTimeStamp); deserializedResult.AttemptResults.Single().ExceptionStackTrace.Should().Be(exceptionStackTrace); deserializedResult.FailurePolicy.Should().NotBeNull(); - deserializedResult.FailurePolicy.PublishExceptionRaisedEventMessage.Should().Be(publishExceptionRaisedMessage); + deserializedResult.FailurePolicy.TransmitExceptionRaisedEventMessage.Should().Be(transmitExceptionRaisedMessage); deserializedResult.FailurePolicy.RetryPolicy.Should().NotBeNull(); deserializedResult.FailurePolicy.RetryPolicy.BaseDelayDurationInSeconds.Should().Be(baseDelayDurationInSeconds); deserializedResult.FailurePolicy.RetryPolicy.DurationScale.Should().Be(durationScale); From 3fbf2ecc98ef577fd8afdf97944f51ecdf81a5c8 Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Sun, 16 Feb 2020 13:09:02 -0600 Subject: [PATCH 04/55] Minor housekeeping. --- .../IDurableMessageQueuePersistenceProxy.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageQueuePersistenceProxy.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageQueuePersistenceProxy.cs index 95d37cea..49723ab8 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageQueuePersistenceProxy.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageQueuePersistenceProxy.cs @@ -52,15 +52,15 @@ Task PersistOperationRecordAsync(TOperation operation) Task PersistSnapshotAsync(DurableMessageQueueSnapshot snapshot); /// - /// Asynchronously persists a thread-safe snapshot of the associated queue and, optionally, flattens the persistent operation - /// records preceding it. + /// Asynchronously persists a thread-safe snapshot of the associated queue and, optionally, flattens the persistent + /// operation records preceding it. /// /// /// The snapshot to persist. /// /// - /// A value indicating whether or not all persisted operation records preceding the snapshot state are destroyed. The default - /// value is . + /// A value indicating whether or not all persisted operation records preceding the snapshot state are destroyed. The + /// default value is . /// /// /// A task representing the asynchronous operation. From 2ab4b386b0fabf323a8e0534a794526b376156e1 Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Sun, 16 Feb 2020 13:39:27 -0600 Subject: [PATCH 05/55] Housekeeping. --- CodeMaid.config | 3 + .../custom/partials/_breadcrumb.liquid | 12 ++-- doc/templates/custom/partials/_footer.liquid | 26 +++---- doc/templates/custom/partials/_head.liquid | 70 +++++++++---------- doc/templates/custom/partials/_logo.liquid | 4 +- doc/templates/custom/partials/_navbar.liquid | 34 ++++----- doc/templates/custom/partials/_scripts.liquid | 2 +- doc/templates/custom/partials/_toc.liquid | 10 +-- .../Symmetric/ICascadingSymmetricKey.cs | 4 +- .../EntityFrameworkRepository.cs | 1 - .../IFactoryProducedInstanceGroup.cs | 1 - 11 files changed, 84 insertions(+), 83 deletions(-) diff --git a/CodeMaid.config b/CodeMaid.config index 5b6638d0..abc23feb 100644 --- a/CodeMaid.config +++ b/CodeMaid.config @@ -173,6 +173,9 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in # ================================================================================================================================= + + \.Designer\.cs$||\.Designer\.vb$||\.resx$||\.min\.css$||\.min\.js$||\.ico$||\.jpg$||\.png$||\.vsspell$||\\wwwroot\\ + \ No newline at end of file diff --git a/doc/templates/custom/partials/_breadcrumb.liquid b/doc/templates/custom/partials/_breadcrumb.liquid index 3bf0d72a..53f655ff 100644 --- a/doc/templates/custom/partials/_breadcrumb.liquid +++ b/doc/templates/custom/partials/_breadcrumb.liquid @@ -1,8 +1,8 @@ {% comment -%}Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.{% endcomment -%} + + \ No newline at end of file diff --git a/doc/templates/custom/partials/_footer.liquid b/doc/templates/custom/partials/_footer.liquid index ddc2672b..1496cc34 100644 --- a/doc/templates/custom/partials/_footer.liquid +++ b/doc/templates/custom/partials/_footer.liquid @@ -1,16 +1,16 @@ {% comment -%}Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.{% endcomment -%} + \ No newline at end of file diff --git a/doc/templates/custom/partials/_head.liquid b/doc/templates/custom/partials/_head.liquid index 2af71a64..fb7c9eed 100644 --- a/doc/templates/custom/partials/_head.liquid +++ b/doc/templates/custom/partials/_head.liquid @@ -1,38 +1,38 @@ -{% comment -%}Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.{% endcomment -%} +{% comment -%}Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.{% endcomment -%} - - - {%- if title and _appTitle -%} - {{title}} | {{appTitle}} - - {%- else -%} + + + {%- if title and _appTitle -%} + {{title}} | {{appTitle}} + + {%- else -%} {%- if title or _appTitle -%} - {{title}}{{appTitle}} - + {{title}}{{appTitle}} + {%- endif -%} - {%- endif -%} - - - {%- if _description -%} - - {%- endif -%} - {%- if _appFaviconPath -%} - - {%- else -%} - - {%- endif -%} - - - - - - {%- if _noindex -%} - - {%- endif -%} - {%- if _enableSearch -%} - - {%- endif -%} - {%- if _enableNewTab -%} - - {%- endif -%} - + {%- endif -%} + + + {%- if _description -%} + + {%- endif -%} + {%- if _appFaviconPath -%} + + {%- else -%} + + {%- endif -%} + + + + + + {%- if _noindex -%} + + {%- endif -%} + {%- if _enableSearch -%} + + {%- endif -%} + {%- if _enableNewTab -%} + + {%- endif -%} + \ No newline at end of file diff --git a/doc/templates/custom/partials/_logo.liquid b/doc/templates/custom/partials/_logo.liquid index 6139b770..4e887546 100644 --- a/doc/templates/custom/partials/_logo.liquid +++ b/doc/templates/custom/partials/_logo.liquid @@ -1,8 +1,8 @@ {% comment -%}Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.{% endcomment -%} {%- if _appLogoPath -%} - + {%- else -%} - + {%- endif -%} \ No newline at end of file diff --git a/doc/templates/custom/partials/_navbar.liquid b/doc/templates/custom/partials/_navbar.liquid index bcfde283..73f775fd 100644 --- a/doc/templates/custom/partials/_navbar.liquid +++ b/doc/templates/custom/partials/_navbar.liquid @@ -1,21 +1,21 @@ {% comment -%}Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.{% endcomment -%} + \ No newline at end of file diff --git a/doc/templates/custom/partials/_scripts.liquid b/doc/templates/custom/partials/_scripts.liquid index 6640a85c..4e7ede1f 100644 --- a/doc/templates/custom/partials/_scripts.liquid +++ b/doc/templates/custom/partials/_scripts.liquid @@ -1,4 +1,4 @@ {% comment -%}Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.{% endcomment -%} - + \ No newline at end of file diff --git a/doc/templates/custom/partials/_toc.liquid b/doc/templates/custom/partials/_toc.liquid index ef44c453..5d35a42a 100644 --- a/doc/templates/custom/partials/_toc.liquid +++ b/doc/templates/custom/partials/_toc.liquid @@ -1,7 +1,7 @@ {% comment -%}Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.{% endcomment -%} + +
+
+
+ \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/ICascadingSymmetricKey.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/ICascadingSymmetricKey.cs index b224b524..8a94bf30 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Symmetric/ICascadingSymmetricKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Symmetric/ICascadingSymmetricKey.cs @@ -8,8 +8,8 @@ namespace RapidField.SolidInstruments.Cryptography.Symmetric { /// - /// Represents a series of instances that constitute instructions for applying cascading - /// encryption and decryption. + /// Represents a series of instances that constitute instructions for applying cascading encryption + /// and decryption. /// public interface ICascadingSymmetricKey : IDisposable { diff --git a/src/RapidField.SolidInstruments.DataAccess.EntityFramework/EntityFrameworkRepository.cs b/src/RapidField.SolidInstruments.DataAccess.EntityFramework/EntityFrameworkRepository.cs index 3c761734..874990dd 100644 --- a/src/RapidField.SolidInstruments.DataAccess.EntityFramework/EntityFrameworkRepository.cs +++ b/src/RapidField.SolidInstruments.DataAccess.EntityFramework/EntityFrameworkRepository.cs @@ -3,7 +3,6 @@ // ================================================================================================================================= using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Internal; using RapidField.SolidInstruments.Core.ArgumentValidation; using RapidField.SolidInstruments.Core.Concurrency; using System; diff --git a/src/RapidField.SolidInstruments.ObjectComposition/IFactoryProducedInstanceGroup.cs b/src/RapidField.SolidInstruments.ObjectComposition/IFactoryProducedInstanceGroup.cs index 7c8a53ea..589dfd55 100644 --- a/src/RapidField.SolidInstruments.ObjectComposition/IFactoryProducedInstanceGroup.cs +++ b/src/RapidField.SolidInstruments.ObjectComposition/IFactoryProducedInstanceGroup.cs @@ -3,7 +3,6 @@ // ================================================================================================================================= using System; -using System.Collections.Generic; namespace RapidField.SolidInstruments.ObjectComposition { From f5913bbc826377df2d9deca068bfa50cdf50d115 Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Sun, 16 Feb 2020 20:11:09 -0600 Subject: [PATCH 06/55] Enhancing Instrument. --- .../ICircularBuffer.cs | 2 +- .../IReadOnlyPinnedBuffer.cs | 2 +- .../ICommandHandler.cs | 2 +- .../ICommandMediator.cs | 2 +- .../Concurrency/ConcurrencyControl.cs | 48 ++++++++++++++++--- .../ConcurrencyControlConsumptionState.cs | 34 +++++++++++++ .../Concurrency/IConcurrencyControl.cs | 10 +++- .../Concurrency/SemaphoreControl.cs | 23 +++++++-- .../SingleThreadSpinLockControl.cs | 40 ++++++++++++---- .../Concurrency/UnconstrainedControl.cs | 25 ++++++---- .../IInstrument.cs | 27 +++++++++++ .../IObjectBuilder.cs | 2 +- .../IReferenceManager.cs | 2 +- .../Instrument.cs | 24 +++++++++- .../Hashing/IHashTree.cs | 2 +- .../ISecureBuffer.cs | 2 +- .../Secrets/IReadOnlySecret.cs | 2 +- .../Secrets/ISecretVault.cs | 2 +- .../Symmetric/ICascadingSymmetricKey.cs | 2 +- .../Symmetric/ISymmetricKey.cs | 2 +- .../IDataAccessRepository.cs | 2 +- .../IDataAccessTransaction.cs | 3 +- .../IDependencyContainer.cs | 2 +- .../IDependencyEngine.cs | 2 +- .../IDependencyScope.cs | 2 +- .../IMessagingFacade.cs | 3 +- .../IDurableMessageQueue.cs | 2 +- .../IDurableMessageQueuePersistenceProxy.cs | 2 +- .../IDurableMessageTransport.cs | 2 +- .../IObjectContainer.cs | 3 +- .../IObjectFactory.cs | 2 +- .../IServiceExecutionLifetime.cs | 2 +- .../IServiceExecutor.cs | 2 +- .../IChannel.cs | 3 +- .../InstrumentTests.cs | 15 +++--- .../SimulatedInstrument.cs | 2 +- 36 files changed, 239 insertions(+), 65 deletions(-) create mode 100644 src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControlConsumptionState.cs create mode 100644 src/RapidField.SolidInstruments.Core/IInstrument.cs diff --git a/src/RapidField.SolidInstruments.Collections/ICircularBuffer.cs b/src/RapidField.SolidInstruments.Collections/ICircularBuffer.cs index 08bbd2aa..0aafcffc 100644 --- a/src/RapidField.SolidInstruments.Collections/ICircularBuffer.cs +++ b/src/RapidField.SolidInstruments.Collections/ICircularBuffer.cs @@ -14,7 +14,7 @@ namespace RapidField.SolidInstruments.Collections /// /// The element type of the collection. /// - public interface ICircularBuffer : IDisposable, IEnumerable, IEnumerable + public interface ICircularBuffer : IAsyncDisposable, IDisposable, IEnumerable, IEnumerable { /// /// Gets the element at the specified index. diff --git a/src/RapidField.SolidInstruments.Collections/IReadOnlyPinnedBuffer.cs b/src/RapidField.SolidInstruments.Collections/IReadOnlyPinnedBuffer.cs index b79660a1..f70c0471 100644 --- a/src/RapidField.SolidInstruments.Collections/IReadOnlyPinnedBuffer.cs +++ b/src/RapidField.SolidInstruments.Collections/IReadOnlyPinnedBuffer.cs @@ -48,7 +48,7 @@ ReadOnlySpan ReadOnlySpan /// /// Represents a read-only, fixed-length bit field that is pinned in memory. /// - public interface IReadOnlyPinnedBuffer : IDisposable + public interface IReadOnlyPinnedBuffer : IAsyncDisposable, IDisposable { /// /// Gets a value indicating whether or not the buffer is empty. diff --git a/src/RapidField.SolidInstruments.Command/ICommandHandler.cs b/src/RapidField.SolidInstruments.Command/ICommandHandler.cs index 533368fe..e39e320e 100644 --- a/src/RapidField.SolidInstruments.Command/ICommandHandler.cs +++ b/src/RapidField.SolidInstruments.Command/ICommandHandler.cs @@ -50,7 +50,7 @@ public interface ICommandHandler : ICommandHandler /// The type of the command that is processed by the handler. /// - public interface ICommandHandler : IDisposable + public interface ICommandHandler : IAsyncDisposable, IDisposable where TCommand : class, ICommandBase { } diff --git a/src/RapidField.SolidInstruments.Command/ICommandMediator.cs b/src/RapidField.SolidInstruments.Command/ICommandMediator.cs index 48fa9f97..ead30b9f 100644 --- a/src/RapidField.SolidInstruments.Command/ICommandMediator.cs +++ b/src/RapidField.SolidInstruments.Command/ICommandMediator.cs @@ -10,7 +10,7 @@ namespace RapidField.SolidInstruments.Command /// /// Serves as a dependency resolver and processing intermediary for commands. /// - public interface ICommandMediator : IDisposable + public interface ICommandMediator : IAsyncDisposable, IDisposable { /// /// Processes the specified . diff --git a/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControl.cs b/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControl.cs index 70f67400..feb2362c 100644 --- a/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControl.cs +++ b/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControl.cs @@ -8,6 +8,7 @@ using System.Diagnostics; using System.Linq; using System.Threading; +using System.Threading.Tasks; namespace RapidField.SolidInstruments.Core.Concurrency { @@ -41,6 +42,7 @@ protected ConcurrencyControl(TimeSpan blockTimeoutThreshold) BlockTimeoutThreshold = blockTimeoutThreshold.RejectIf().IsLessThanOrEqualTo(TimeSpan.Zero, nameof(blockTimeoutThreshold)); BlockTimeoutThresholdIsInfinite = false; + ConsumptionState = ConcurrencyControlConsumptionState.Unclaimed; } /// @@ -95,6 +97,14 @@ public void Dispose() GC.SuppressFinalize(this); } + /// + /// Asynchronously releases all resources consumed by the current . + /// + /// + /// A task representing the asynchronous operation. + /// + public ValueTask DisposeAsync() => new ValueTask(Task.Factory.StartNew(Dispose)); + /// /// Informs the control that a thread is entering a block of code or that it is beginning to consuming a resource. /// @@ -112,12 +122,12 @@ public ConcurrencyControlToken Enter() { if (BlockTimeoutThresholdIsInfinite) { - EnterWithoutTimeout(); + ConsumptionState = EnterWithoutTimeout(); return GetNextToken(SynchronizationContext.Current, Thread.CurrentThread, Timeout.InfiniteTimeSpan, null); } var expirationStopwatch = Stopwatch.StartNew(); - EnterWithTimeout(BlockTimeoutThreshold); + ConsumptionState = EnterWithTimeout(BlockTimeoutThreshold); return GetNextToken(SynchronizationContext.Current, Thread.CurrentThread, BlockTimeoutThreshold, expirationStopwatch); } catch (ConcurrencyControlOperationException) @@ -147,13 +157,13 @@ public void Exit(ConcurrencyControlToken token) { RejectIfDisposed(); - if (Tokens.TryRemove(token.Identifier, out var releasedToken)) + if (Tokens.TryRemove(token.Identifier, out _)) { var exitedSuccessfully = false; try { - Exit(ref exitedSuccessfully); + ConsumptionState = Exit(ref exitedSuccessfully); } catch (ConcurrencyControlOperationException) { @@ -197,7 +207,10 @@ protected virtual void Dispose(Boolean disposing) /// /// Informs the control that a thread is entering a block of code or that it is beginning to consuming a resource. /// - protected abstract void EnterWithoutTimeout(); + /// + /// The resulting consumption state of the current . + /// + protected abstract ConcurrencyControlConsumptionState EnterWithoutTimeout(); /// /// Informs the control that a thread is entering a block of code or that it is beginning to consuming a resource and @@ -206,7 +219,10 @@ protected virtual void Dispose(Boolean disposing) /// /// The maximum length of time to block a thread before raising an exception. /// - protected abstract void EnterWithTimeout(TimeSpan blockTimeoutThreshold); + /// + /// The resulting consumption state of the current . + /// + protected abstract ConcurrencyControlConsumptionState EnterWithTimeout(TimeSpan blockTimeoutThreshold); /// /// Informs the control that a thread is exiting a block of code or has finished consuming a resource. @@ -214,7 +230,10 @@ protected virtual void Dispose(Boolean disposing) /// /// A value indicating whether or not the exit operation was successful. The initial value is . /// - protected abstract void Exit(ref Boolean exitedSuccessfully); + /// + /// The resulting consumption state of the current . + /// + protected abstract ConcurrencyControlConsumptionState Exit(ref Boolean exitedSuccessfully); /// /// Creates a new, uniquely-identified and adds it to in a @@ -271,6 +290,15 @@ private void RejectIfDisposed() } } + /// + /// Gets the consumption state of the current . + /// + public ConcurrencyControlConsumptionState ConsumptionState + { + get => (ConcurrencyControlConsumptionState)Convert.ToInt32(Interlocked.Read(ref ConsumptionStateValue)); + private set => Interlocked.Exchange(ref ConsumptionStateValue, Convert.ToInt64((Int32)value)); + } + /// /// Represents the highest assignable token identifier. /// @@ -296,6 +324,12 @@ private void RejectIfDisposed() [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly ConcurrentDictionary Tokens = new ConcurrentDictionary(); + /// + /// Represents the consumption state of the current . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private Int64 ConsumptionStateValue; + /// /// Represents a value indicating whether or not the current has been disposed. /// diff --git a/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControlConsumptionState.cs b/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControlConsumptionState.cs new file mode 100644 index 00000000..b08e858f --- /dev/null +++ b/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControlConsumptionState.cs @@ -0,0 +1,34 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.Core.Concurrency +{ + /// + /// Specifies the consumption state of an . + /// + public enum ConcurrencyControlConsumptionState : Int32 + { + /// + /// The concurrency control consumption state is not specified. + /// + Unspecified = 0, + + /// + /// No threads are currently consuming the associated control. + /// + Unclaimed = 1, + + /// + /// One or more threads are currently consuming the associated control, but additional threads may still enter. + /// + PartiallyClaimed = 2, + + /// + /// One or more threads are currently consuming the associated control, and no additional threads may enter. + /// + FullyClaimed = 3 + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Core/Concurrency/IConcurrencyControl.cs b/src/RapidField.SolidInstruments.Core/Concurrency/IConcurrencyControl.cs index a64a3541..feb32376 100644 --- a/src/RapidField.SolidInstruments.Core/Concurrency/IConcurrencyControl.cs +++ b/src/RapidField.SolidInstruments.Core/Concurrency/IConcurrencyControl.cs @@ -9,7 +9,7 @@ namespace RapidField.SolidInstruments.Core.Concurrency /// /// Represents a concurrency control mechanism. /// - public interface IConcurrencyControl : IDisposable + public interface IConcurrencyControl : IAsyncDisposable, IDisposable { /// /// Informs the control that a thread is entering a block of code or that it is beginning to consume a resource. @@ -36,5 +36,13 @@ public interface IConcurrencyControl : IDisposable /// The object is disposed. /// void Exit(ConcurrencyControlToken token); + + /// + /// Gets the consumption state of the current . + /// + ConcurrencyControlConsumptionState ConsumptionState + { + get; + } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Core/Concurrency/SemaphoreControl.cs b/src/RapidField.SolidInstruments.Core/Concurrency/SemaphoreControl.cs index 80e9ff96..9ec187e7 100644 --- a/src/RapidField.SolidInstruments.Core/Concurrency/SemaphoreControl.cs +++ b/src/RapidField.SolidInstruments.Core/Concurrency/SemaphoreControl.cs @@ -60,7 +60,14 @@ protected override void Dispose(Boolean disposing) /// /// Informs the control that a thread is entering a block of code or that it is beginning to consuming a resource. /// - protected sealed override void EnterWithoutTimeout() => Semaphore.Wait(); + /// + /// The resulting consumption state of the current . + /// + protected sealed override ConcurrencyControlConsumptionState EnterWithoutTimeout() + { + Semaphore.Wait(); + return Semaphore.CurrentCount == 0 ? ConcurrencyControlConsumptionState.FullyClaimed : ConcurrencyControlConsumptionState.PartiallyClaimed; + } /// /// Informs the control that a thread is entering a block of code or that it is beginning to consuming a resource and @@ -69,14 +76,17 @@ protected override void Dispose(Boolean disposing) /// /// The maximum length of time to block a thread before raising an exception. /// + /// + /// The resulting consumption state of the current . + /// /// /// The operation timed out. /// - protected sealed override void EnterWithTimeout(TimeSpan blockTimeoutThreshold) + protected sealed override ConcurrencyControlConsumptionState EnterWithTimeout(TimeSpan blockTimeoutThreshold) { if (Semaphore.Wait(blockTimeoutThreshold)) { - return; + return Semaphore.CurrentCount == 0 ? ConcurrencyControlConsumptionState.FullyClaimed : ConcurrencyControlConsumptionState.PartiallyClaimed; } throw new TimeoutException($"The operation failed to enter the semaphore after {blockTimeoutThreshold.ToSerializedString()}."); @@ -88,13 +98,16 @@ protected sealed override void EnterWithTimeout(TimeSpan blockTimeoutThreshold) /// /// A value indicating whether or not the exit operation was successful. The initial value is . /// + /// + /// The resulting consumption state of the current . + /// /// /// The semaphore has already reached its maximum size. /// - protected sealed override void Exit(ref Boolean exitedSuccessfully) + protected sealed override ConcurrencyControlConsumptionState Exit(ref Boolean exitedSuccessfully) { Semaphore.Release(); - exitedSuccessfully = true; + return Semaphore.CurrentCount == MaximumConcurrencyLimit ? ConcurrencyControlConsumptionState.Unclaimed : ConcurrencyControlConsumptionState.PartiallyClaimed; } /// diff --git a/src/RapidField.SolidInstruments.Core/Concurrency/SingleThreadSpinLockControl.cs b/src/RapidField.SolidInstruments.Core/Concurrency/SingleThreadSpinLockControl.cs index 63ccd49d..9ecfd714 100644 --- a/src/RapidField.SolidInstruments.Core/Concurrency/SingleThreadSpinLockControl.cs +++ b/src/RapidField.SolidInstruments.Core/Concurrency/SingleThreadSpinLockControl.cs @@ -43,21 +43,26 @@ internal SingleThreadSpinLockControl(TimeSpan blockTimeoutThreshold) /// /// Informs the control that a thread is entering a block of code or that it is beginning to consuming a resource. /// + /// + /// The resulting consumption state of the current . + /// /// /// The lock was not acquired. /// /// /// The current thread already owns the lock. /// - protected sealed override void EnterWithoutTimeout() + protected sealed override ConcurrencyControlConsumptionState EnterWithoutTimeout() { var lockTaken = false; Spin.TryEnter(ref lockTaken); - if (lockTaken == false) + if (lockTaken) { - throw new ConcurrencyControlOperationException("The operation failed to acquire a spin lock."); + return ConcurrencyControlConsumptionState.FullyClaimed; } + + throw new ConcurrencyControlOperationException("The operation failed to acquire a spin lock."); } /// @@ -67,21 +72,26 @@ protected sealed override void EnterWithoutTimeout() /// /// The maximum length of time to block a thread before raising an exception. /// + /// + /// The resulting consumption state of the current . + /// /// /// The current thread already owns the lock. /// /// /// The operation timed out. /// - protected sealed override void EnterWithTimeout(TimeSpan blockTimeoutThreshold) + protected sealed override ConcurrencyControlConsumptionState EnterWithTimeout(TimeSpan blockTimeoutThreshold) { var lockTaken = false; Spin.TryEnter(blockTimeoutThreshold, ref lockTaken); - if (lockTaken == false) + if (lockTaken) { - throw new TimeoutException($"The operation failed to acquire a spin lock after {blockTimeoutThreshold.ToSerializedString()}."); + return ConcurrencyControlConsumptionState.FullyClaimed; } + + throw new TimeoutException($"The operation failed to acquire a spin lock after {blockTimeoutThreshold.ToSerializedString()}."); } /// @@ -90,13 +100,25 @@ protected sealed override void EnterWithTimeout(TimeSpan blockTimeoutThreshold) /// /// A value indicating whether or not the exit operation was successful. The initial value is . /// + /// + /// The resulting consumption state of the current . + /// /// /// The current thread does not own the lock. /// - protected sealed override void Exit(ref Boolean exitedSuccessfully) + protected sealed override ConcurrencyControlConsumptionState Exit(ref Boolean exitedSuccessfully) { - Spin.Exit(); - exitedSuccessfully = true; + try + { + Spin.Exit(); + exitedSuccessfully = true; + return ConcurrencyControlConsumptionState.Unclaimed; + } + catch + { + exitedSuccessfully = false; + throw; + } } /// diff --git a/src/RapidField.SolidInstruments.Core/Concurrency/UnconstrainedControl.cs b/src/RapidField.SolidInstruments.Core/Concurrency/UnconstrainedControl.cs index aa456fd0..c7adc4ee 100644 --- a/src/RapidField.SolidInstruments.Core/Concurrency/UnconstrainedControl.cs +++ b/src/RapidField.SolidInstruments.Core/Concurrency/UnconstrainedControl.cs @@ -34,10 +34,10 @@ internal UnconstrainedControl() /// /// Informs the control that a thread is entering a block of code or that it is beginning to consuming a resource. /// - protected sealed override void EnterWithoutTimeout() - { - return; - } + /// + /// The resulting consumption state of the current . + /// + protected sealed override ConcurrencyControlConsumptionState EnterWithoutTimeout() => ConcurrencyControlConsumptionState.Unclaimed; /// /// Informs the control that a thread is entering a block of code or that it is beginning to consuming a resource and @@ -46,10 +46,10 @@ protected sealed override void EnterWithoutTimeout() /// /// The maximum length of time to block a thread before raising an exception. /// - protected sealed override void EnterWithTimeout(TimeSpan blockTimeoutThreshold) - { - return; - } + /// + /// The resulting consumption state of the current . + /// + protected sealed override ConcurrencyControlConsumptionState EnterWithTimeout(TimeSpan blockTimeoutThreshold) => ConcurrencyControlConsumptionState.Unclaimed; /// /// Informs the control that a thread is exiting a block of code or has finished consuming a resource. @@ -57,6 +57,13 @@ protected sealed override void EnterWithTimeout(TimeSpan blockTimeoutThreshold) /// /// A value indicating whether or not the exit operation was successful. The initial value is . /// - protected sealed override void Exit(ref Boolean exitedSuccessfully) => exitedSuccessfully = true; + /// + /// The resulting consumption state of the current . + /// + protected sealed override ConcurrencyControlConsumptionState Exit(ref Boolean exitedSuccessfully) + { + exitedSuccessfully = true; + return ConcurrencyControlConsumptionState.Unclaimed; + } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Core/IInstrument.cs b/src/RapidField.SolidInstruments.Core/IInstrument.cs new file mode 100644 index 00000000..3f931938 --- /dev/null +++ b/src/RapidField.SolidInstruments.Core/IInstrument.cs @@ -0,0 +1,27 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.Core +{ + /// + /// Represents a utility with disposable resources and exposes a lazily-loaded concurrency control mechanism. + /// + public interface IInstrument : IAsyncDisposable, IDisposable + { + /// + /// Gets a value indicating whether or not the current is fully occupied, as measured by thread + /// saturation for state-controlling operations. + /// + /// + /// Interrogate this property to determine if the instrument is immediately available to perform an operation that reserves + /// state control. This is useful for cases in which another resource may be utilized to perform the same operation. + /// + Boolean IsBusy + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Core/IObjectBuilder.cs b/src/RapidField.SolidInstruments.Core/IObjectBuilder.cs index 30eaad25..b4e4ee30 100644 --- a/src/RapidField.SolidInstruments.Core/IObjectBuilder.cs +++ b/src/RapidField.SolidInstruments.Core/IObjectBuilder.cs @@ -12,7 +12,7 @@ namespace RapidField.SolidInstruments.Core /// /// The output type that results from the invocation of . /// - public interface IObjectBuilder : IDisposable + public interface IObjectBuilder : IAsyncDisposable, IDisposable where TResult : class { /// diff --git a/src/RapidField.SolidInstruments.Core/IReferenceManager.cs b/src/RapidField.SolidInstruments.Core/IReferenceManager.cs index 35f48053..be131708 100644 --- a/src/RapidField.SolidInstruments.Core/IReferenceManager.cs +++ b/src/RapidField.SolidInstruments.Core/IReferenceManager.cs @@ -9,7 +9,7 @@ namespace RapidField.SolidInstruments.Core /// /// Tracks a collection of related object references and manages disposal of them. /// - public interface IReferenceManager : IDisposable + public interface IReferenceManager : IAsyncDisposable, IDisposable { /// /// Instructs the current to manage the specified object. diff --git a/src/RapidField.SolidInstruments.Core/Instrument.cs b/src/RapidField.SolidInstruments.Core/Instrument.cs index 6aef5ccf..45660ed9 100644 --- a/src/RapidField.SolidInstruments.Core/Instrument.cs +++ b/src/RapidField.SolidInstruments.Core/Instrument.cs @@ -8,13 +8,17 @@ using System; using System.Diagnostics; using System.Threading; +using System.Threading.Tasks; namespace RapidField.SolidInstruments.Core { /// /// Represents a utility with disposable resources and exposes a lazily-loaded concurrency control mechanism. /// - public abstract class Instrument : IDisposable + /// + /// is the default implementation of . + /// + public abstract class Instrument : IInstrument { /// /// Initializes a new instance of the class. @@ -94,6 +98,14 @@ public void Dispose() #pragma warning restore CA1063 + /// + /// Asynchronously releases all resources consumed by the current . + /// + /// + /// A task representing the asynchronous operation. + /// + public ValueTask DisposeAsync() => new ValueTask(Task.Factory.StartNew(Dispose)); + /// /// Initializes a concurrency control mechanism that is used to manage state for the current . /// @@ -132,6 +144,16 @@ protected void RejectIfDisposed() } } + /// + /// Gets a value indicating whether or not the current is fully occupied, as measured by thread + /// saturation for state-controlling operations. + /// + /// + /// Interrogate this property to determine if the instrument is immediately available to perform an operation that reserves + /// state control. This is useful for cases in which another resource may be utilized to perform the same operation. + /// + public Boolean IsBusy => StateControl.ConsumptionState == ConcurrencyControlConsumptionState.FullyClaimed; + /// /// Gets a concurrency control mechanism that is used to manage state for the current . /// diff --git a/src/RapidField.SolidInstruments.Cryptography/Hashing/IHashTree.cs b/src/RapidField.SolidInstruments.Cryptography/Hashing/IHashTree.cs index 67181cde..4c408c9b 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Hashing/IHashTree.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Hashing/IHashTree.cs @@ -16,7 +16,7 @@ namespace RapidField.SolidInstruments.Cryptography.Hashing /// /// The type of the data block objects underlying the hash tree. /// - public interface IHashTree : IDisposable + public interface IHashTree : IAsyncDisposable, IDisposable where TBlock : class { /// diff --git a/src/RapidField.SolidInstruments.Cryptography/ISecureBuffer.cs b/src/RapidField.SolidInstruments.Cryptography/ISecureBuffer.cs index a4216ed2..4118811c 100644 --- a/src/RapidField.SolidInstruments.Cryptography/ISecureBuffer.cs +++ b/src/RapidField.SolidInstruments.Cryptography/ISecureBuffer.cs @@ -10,7 +10,7 @@ namespace RapidField.SolidInstruments.Cryptography /// /// Represents a fixed-length bit field that is pinned in memory and encrypted at rest. /// - public interface ISecureBuffer : IDisposable + public interface ISecureBuffer : IAsyncDisposable, IDisposable { /// /// Decrypts the buffer, performs the specified operation against the pinned plaintext and encrypts the buffer as a diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/IReadOnlySecret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/IReadOnlySecret.cs index 7b820320..2d4dc4f8 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/IReadOnlySecret.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/IReadOnlySecret.cs @@ -41,7 +41,7 @@ public interface IReadOnlySecret : IReadOnlySecret /// /// Represents a named read-only secret value that is pinned in memory and encrypted at rest. /// - public interface IReadOnlySecret : IDisposable + public interface IReadOnlySecret : IAsyncDisposable, IDisposable { /// /// Decrypts the secret value, pins it in memory and performs the specified read operation against the resulting bytes as a diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretVault.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretVault.cs index 05d7e6e5..b0aff1ef 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretVault.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretVault.cs @@ -15,7 +15,7 @@ namespace RapidField.SolidInstruments.Cryptography.Secrets /// /// Represents a secure container for named secret values which are encrypted and pinned in memory at rest. /// - public interface ISecretVault : IDisposable + public interface ISecretVault : IAsyncDisposable, IDisposable { /// /// Adds the specified secret using the specified name to the current , or updates it if a secret diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/ICascadingSymmetricKey.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/ICascadingSymmetricKey.cs index 8a94bf30..a199b6de 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Symmetric/ICascadingSymmetricKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Symmetric/ICascadingSymmetricKey.cs @@ -11,7 +11,7 @@ namespace RapidField.SolidInstruments.Cryptography.Symmetric /// Represents a series of instances that constitute instructions for applying cascading encryption /// and decryption. /// - public interface ICascadingSymmetricKey : IDisposable + public interface ICascadingSymmetricKey : IAsyncDisposable, IDisposable { /// /// Converts the value of the current to its equivalent binary representation. diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/ISymmetricKey.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/ISymmetricKey.cs index b31f9230..77d2c70f 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Symmetric/ISymmetricKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Symmetric/ISymmetricKey.cs @@ -10,7 +10,7 @@ namespace RapidField.SolidInstruments.Cryptography.Symmetric /// Represents a symmetric-key algorithm and source bits for a derived key, encapsulates key derivation operations and secures /// key bits in memory. /// - public interface ISymmetricKey : IDisposable + public interface ISymmetricKey : IAsyncDisposable, IDisposable { /// /// Converts the value of the current to its equivalent binary representation. diff --git a/src/RapidField.SolidInstruments.DataAccess/IDataAccessRepository.cs b/src/RapidField.SolidInstruments.DataAccess/IDataAccessRepository.cs index c9571e78..9c07defc 100644 --- a/src/RapidField.SolidInstruments.DataAccess/IDataAccessRepository.cs +++ b/src/RapidField.SolidInstruments.DataAccess/IDataAccessRepository.cs @@ -245,7 +245,7 @@ public interface IDataAccessRepository : IDataAccessRepository /// /// Performs data access operations for a specified entity type. /// - public interface IDataAccessRepository : IDisposable + public interface IDataAccessRepository : IAsyncDisposable, IDisposable { /// /// Determines whether or not any entities exist in the current . diff --git a/src/RapidField.SolidInstruments.DataAccess/IDataAccessTransaction.cs b/src/RapidField.SolidInstruments.DataAccess/IDataAccessTransaction.cs index e7871a3e..7e4b8dbc 100644 --- a/src/RapidField.SolidInstruments.DataAccess/IDataAccessTransaction.cs +++ b/src/RapidField.SolidInstruments.DataAccess/IDataAccessTransaction.cs @@ -2,6 +2,7 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= +using RapidField.SolidInstruments.Core; using System; using System.Threading.Tasks; @@ -10,7 +11,7 @@ namespace RapidField.SolidInstruments.DataAccess /// /// Fulfills the unit of work pattern for data access operations. /// - public interface IDataAccessTransaction : IDisposable + public interface IDataAccessTransaction : IInstrument { /// /// Initiates the current . diff --git a/src/RapidField.SolidInstruments.InversionOfControl/IDependencyContainer.cs b/src/RapidField.SolidInstruments.InversionOfControl/IDependencyContainer.cs index ef0e792e..0bf55fb3 100644 --- a/src/RapidField.SolidInstruments.InversionOfControl/IDependencyContainer.cs +++ b/src/RapidField.SolidInstruments.InversionOfControl/IDependencyContainer.cs @@ -9,7 +9,7 @@ namespace RapidField.SolidInstruments.InversionOfControl /// /// Represents an abstraction for a utility that creates, destroys, and manages scoping for dependencies. /// - public interface IDependencyContainer : IDisposable + public interface IDependencyContainer : IAsyncDisposable, IDisposable { /// /// Creates a new initialization and disposal scope for the current . diff --git a/src/RapidField.SolidInstruments.InversionOfControl/IDependencyEngine.cs b/src/RapidField.SolidInstruments.InversionOfControl/IDependencyEngine.cs index 2388da04..5ffd3fb0 100644 --- a/src/RapidField.SolidInstruments.InversionOfControl/IDependencyEngine.cs +++ b/src/RapidField.SolidInstruments.InversionOfControl/IDependencyEngine.cs @@ -9,7 +9,7 @@ namespace RapidField.SolidInstruments.InversionOfControl /// /// Represents a configurable dependency resolution system. /// - public interface IDependencyEngine : IDisposable + public interface IDependencyEngine : IAsyncDisposable, IDisposable { /// /// Gets the engine's dependency container. diff --git a/src/RapidField.SolidInstruments.InversionOfControl/IDependencyScope.cs b/src/RapidField.SolidInstruments.InversionOfControl/IDependencyScope.cs index dbc5a0db..6fc3e7f4 100644 --- a/src/RapidField.SolidInstruments.InversionOfControl/IDependencyScope.cs +++ b/src/RapidField.SolidInstruments.InversionOfControl/IDependencyScope.cs @@ -9,7 +9,7 @@ namespace RapidField.SolidInstruments.InversionOfControl /// /// Represents a shared initialization and disposal scope for container-resolved objects. /// - public interface IDependencyScope : IDisposable + public interface IDependencyScope : IAsyncDisposable, IDisposable { /// /// Creates a new child initialization and disposal scope for the current . diff --git a/src/RapidField.SolidInstruments.Messaging/IMessagingFacade.cs b/src/RapidField.SolidInstruments.Messaging/IMessagingFacade.cs index 22a10902..b623308d 100644 --- a/src/RapidField.SolidInstruments.Messaging/IMessagingFacade.cs +++ b/src/RapidField.SolidInstruments.Messaging/IMessagingFacade.cs @@ -2,6 +2,7 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= +using RapidField.SolidInstruments.Core; using System; namespace RapidField.SolidInstruments.Messaging @@ -37,7 +38,7 @@ public interface IMessagingFacade : IMessagingFacade /// /// Facilitates implementation-specific operations for a message bus. /// - public interface IMessagingFacade : IDisposable + public interface IMessagingFacade : IInstrument { /// /// Gets the unique textual identifier for the current . diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageQueue.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageQueue.cs index 90976b36..ef58f502 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageQueue.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageQueue.cs @@ -12,7 +12,7 @@ namespace RapidField.SolidInstruments.Messaging.TransportPrimitives /// /// Represents a durable message queue. /// - public interface IDurableMessageQueue : IDisposable + public interface IDurableMessageQueue : IAsyncDisposable, IDisposable { /// /// Asynchronously notifies the queue that a locked message was not processed and can be made available for processing by diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageQueuePersistenceProxy.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageQueuePersistenceProxy.cs index 49723ab8..be4cc1a2 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageQueuePersistenceProxy.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageQueuePersistenceProxy.cs @@ -10,7 +10,7 @@ namespace RapidField.SolidInstruments.Messaging.TransportPrimitives /// /// Manages persistent state for instances. /// - public interface IDurableMessageQueuePersistenceProxy : IDisposable + public interface IDurableMessageQueuePersistenceProxy : IAsyncDisposable, IDisposable { /// /// Asynchronously persists the specified operation record. diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageTransport.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageTransport.cs index 1dd38907..d5e46825 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageTransport.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageTransport.cs @@ -10,7 +10,7 @@ namespace RapidField.SolidInstruments.Messaging.TransportPrimitives /// /// Supports message exchange for a collection of queues and topics. /// - public interface IDurableMessageTransport : IDisposable + public interface IDurableMessageTransport : IAsyncDisposable, IDisposable { /// /// diff --git a/src/RapidField.SolidInstruments.ObjectComposition/IObjectContainer.cs b/src/RapidField.SolidInstruments.ObjectComposition/IObjectContainer.cs index 6fb68b97..d779224b 100644 --- a/src/RapidField.SolidInstruments.ObjectComposition/IObjectContainer.cs +++ b/src/RapidField.SolidInstruments.ObjectComposition/IObjectContainer.cs @@ -2,6 +2,7 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= +using RapidField.SolidInstruments.Core; using System; using System.Collections.Generic; @@ -10,7 +11,7 @@ namespace RapidField.SolidInstruments.ObjectComposition /// /// Manages object creation, storage, resolution and disposal for a related group of object instances. /// - public interface IObjectContainer : IDisposable + public interface IObjectContainer : IInstrument { /// /// Returns the instance of specified type that is managed by the current . diff --git a/src/RapidField.SolidInstruments.ObjectComposition/IObjectFactory.cs b/src/RapidField.SolidInstruments.ObjectComposition/IObjectFactory.cs index 5986d282..d6552911 100644 --- a/src/RapidField.SolidInstruments.ObjectComposition/IObjectFactory.cs +++ b/src/RapidField.SolidInstruments.ObjectComposition/IObjectFactory.cs @@ -48,7 +48,7 @@ Type ProductBaseType /// /// Encapsulates creation of new object instances using explicit types. /// - public interface IObjectFactory : IDisposable + public interface IObjectFactory : IAsyncDisposable, IDisposable { /// /// Creates a new instance of an object of the specified type. diff --git a/src/RapidField.SolidInstruments.Service/IServiceExecutionLifetime.cs b/src/RapidField.SolidInstruments.Service/IServiceExecutionLifetime.cs index 8865979a..1bb26860 100644 --- a/src/RapidField.SolidInstruments.Service/IServiceExecutionLifetime.cs +++ b/src/RapidField.SolidInstruments.Service/IServiceExecutionLifetime.cs @@ -9,7 +9,7 @@ namespace RapidField.SolidInstruments.Service /// /// Provides control over the lifetime of execution for a service. /// - public interface IServiceExecutionLifetime : IDisposable + public interface IServiceExecutionLifetime : IAsyncDisposable, IDisposable { /// /// Unblocks waiting threads and ends the execution lifetime. diff --git a/src/RapidField.SolidInstruments.Service/IServiceExecutor.cs b/src/RapidField.SolidInstruments.Service/IServiceExecutor.cs index 2f6b1ad7..c6232ab3 100644 --- a/src/RapidField.SolidInstruments.Service/IServiceExecutor.cs +++ b/src/RapidField.SolidInstruments.Service/IServiceExecutor.cs @@ -9,7 +9,7 @@ namespace RapidField.SolidInstruments.Service /// /// Prepares for and performs execution of a service. /// - public interface IServiceExecutor : IDisposable + public interface IServiceExecutor : IAsyncDisposable, IDisposable { /// /// Begins execution of the service and performs the service operations. diff --git a/src/RapidField.SolidInstruments.SignalProcessing/IChannel.cs b/src/RapidField.SolidInstruments.SignalProcessing/IChannel.cs index d5a9d21e..5dea4cdc 100644 --- a/src/RapidField.SolidInstruments.SignalProcessing/IChannel.cs +++ b/src/RapidField.SolidInstruments.SignalProcessing/IChannel.cs @@ -2,6 +2,7 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= +using RapidField.SolidInstruments.Core; using System; using System.Threading.Tasks; @@ -145,7 +146,7 @@ InvalidReadBehavior InvalidReadBehavior /// /// Represents a streaming data signal. /// - public interface IChannel : IDisposable + public interface IChannel : IInstrument { /// /// Changes the channel's status to if it is currently silent, otherwise changes the diff --git a/test/RapidField.SolidInstruments.Core.UnitTests/InstrumentTests.cs b/test/RapidField.SolidInstruments.Core.UnitTests/InstrumentTests.cs index 9bb0938b..a3510573 100644 --- a/test/RapidField.SolidInstruments.Core.UnitTests/InstrumentTests.cs +++ b/test/RapidField.SolidInstruments.Core.UnitTests/InstrumentTests.cs @@ -45,6 +45,7 @@ public void RejectIfDisposed_ShouldRaiseObjectDisposedException_ForDisposedTarge using (target = new SimulatedInstrument(stateControlMode)) { + // Act. target.StoreIntegerValue(integerValue); } @@ -74,14 +75,15 @@ public void StateControl_ShouldProduceDesiredResults_ForMultipleThreadConcurrenc { for (var i = 0; i < taskCount; i++) { - var task = new Task(() => target.SimulateThreadSafeOperation()); - tasks[i] = task; - task.Start(); + // Act. + tasks[i] = Task.Factory.StartNew(target.SimulateThreadSafeOperation); } + // Act. Task.WaitAll(tasks); // Assert. + target.IsBusy.Should().BeFalse(); tasks.Count(task => task.Status == TaskStatus.RanToCompletion).Should().Be(taskCount); target.StateIsVolatile.Should().BeFalse(); } @@ -107,14 +109,15 @@ public void StateControl_ShouldProduceDesiredResults_ForSingleThreadConcurrencyC { for (var i = 0; i < taskCount; i++) { - var task = new Task(() => target.SimulateThreadSafeOperation()); - tasks[i] = task; - task.Start(); + // Act. + tasks[i] = Task.Factory.StartNew(target.SimulateThreadSafeOperation); } + // Act. Task.WaitAll(tasks); // Assert. + target.IsBusy.Should().BeFalse(); tasks.Count(task => task.Status == TaskStatus.RanToCompletion).Should().Be(taskCount); target.StateIsVolatile.Should().BeFalse(); } diff --git a/test/RapidField.SolidInstruments.Core.UnitTests/SimulatedInstrument.cs b/test/RapidField.SolidInstruments.Core.UnitTests/SimulatedInstrument.cs index 4410eb50..22b572fe 100644 --- a/test/RapidField.SolidInstruments.Core.UnitTests/SimulatedInstrument.cs +++ b/test/RapidField.SolidInstruments.Core.UnitTests/SimulatedInstrument.cs @@ -119,7 +119,7 @@ public Boolean StateIsVolatile /// /// Represents the amount of time delays for each invocation. /// - private static readonly TimeSpan ThreadSafeOperationDelayDuration = TimeSpan.FromTicks(21); + private static readonly TimeSpan ThreadSafeOperationDelayDuration = TimeSpan.FromTicks(34); /// /// Represents an object that is used to synchronize access to the associated resource(s). From 3c22c93fd364be433e4292153511bdacc97c4831 Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Sun, 16 Feb 2020 20:48:58 -0600 Subject: [PATCH 07/55] Supporting asynchronous disposal of managed references. --- .../Extensions/LazyExtensions.cs | 18 ++++++ .../ReferenceManager.cs | 60 +++++++++++++++---- 2 files changed, 67 insertions(+), 11 deletions(-) diff --git a/src/RapidField.SolidInstruments.Core/Extensions/LazyExtensions.cs b/src/RapidField.SolidInstruments.Core/Extensions/LazyExtensions.cs index e3489c48..240683cc 100644 --- a/src/RapidField.SolidInstruments.Core/Extensions/LazyExtensions.cs +++ b/src/RapidField.SolidInstruments.Core/Extensions/LazyExtensions.cs @@ -3,6 +3,7 @@ // ================================================================================================================================= using System; +using System.Threading.Tasks; namespace RapidField.SolidInstruments.Core.Extensions { @@ -25,5 +26,22 @@ public static void Dispose(this Lazy target) target.Value.Dispose(); } } + + /// + /// Releases all resources consumed by the value of the current , if the value has been created. + /// + /// + /// The current instance of the . + /// + public static Task DisposeAsync(this Lazy target) + where T : IAsyncDisposable + { + if ((target?.IsValueCreated).GetValueOrDefault(false)) + { + return target.Value.DisposeAsync().AsTask(); + } + + return Task.CompletedTask; + } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Core/ReferenceManager.cs b/src/RapidField.SolidInstruments.Core/ReferenceManager.cs index 26fee274..a3d7d2db 100644 --- a/src/RapidField.SolidInstruments.Core/ReferenceManager.cs +++ b/src/RapidField.SolidInstruments.Core/ReferenceManager.cs @@ -5,6 +5,8 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; namespace RapidField.SolidInstruments.Core { @@ -72,18 +74,32 @@ protected override void Dispose(Boolean disposing) { if (disposing) { - var disposedReferences = new List(); - - while (References.Count > 0) + try { - IDisposable reference; + var disposeTasks = new List(); - using (var controlToken = StateControl.Enter()) + while (References.Count > 0) { - reference = References.Dequeue(); + IAsyncDisposable reference; + + using (var controlToken = StateControl.Enter()) + { + reference = References.Dequeue(); + } + + disposeTasks.Add(reference?.DisposeAsync().AsTask()); } - reference.Dispose(); + var disposeTaskArray = disposeTasks.Where(task => task is null == false).ToArray(); + + if (disposeTaskArray.Any()) + { + Task.WaitAll(disposeTaskArray); + } + } + finally + { + References.Clear(); } } } @@ -102,7 +118,7 @@ protected override void Dispose(Boolean disposing) /// Represents the objects that are managed by the current . /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly Queue References = new Queue(); + private readonly Queue References = new Queue(); /// /// Represents an object that is managed by a . @@ -110,7 +126,7 @@ protected override void Dispose(Boolean disposing) /// /// The type of the managed object. /// - private class ManagedReference : IDisposable, IEquatable> + private class ManagedReference : IAsyncDisposable, IDisposable, IEquatable> where T : class { /// @@ -175,6 +191,15 @@ public void Dispose() GC.SuppressFinalize(this); } + /// + /// Asynchronously releases all resources consumed by the current . + /// + /// + /// A task representing the asynchronous operation. + /// + [DebuggerHidden] + public ValueTask DisposeAsync() => new ValueTask(Task.Factory.StartNew(Dispose)); + /// /// Determines whether or not the current is equal to the specified /// . @@ -246,8 +271,21 @@ protected void Dispose(Boolean disposing) return; } - (Target as IDisposable)?.Dispose(); - Target = null; + try + { + if (Target is IDisposable disposableTarget) + { + disposableTarget?.Dispose(); + } + else if (Target is IAsyncDisposable asyncDisposableTarget) + { + asyncDisposableTarget?.DisposeAsync().AsTask().Wait(); + } + } + finally + { + Target = null; + } } } From e02a8fd206a17692ebe06cc789b0881022ae9b8e Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Sun, 16 Feb 2020 21:36:03 -0600 Subject: [PATCH 08/55] Segregating interfaces. --- .../ICalculatedSequence.cs | 8 ++- .../ICircularBuffer.cs | 12 +++- .../IInfiniteSequence.cs | 10 ++- .../ITreeNode.cs | 40 ++++++----- .../Hashing/IHashTree.cs | 13 +++- .../Hashing/IHashingProcessor.cs | 68 ++++++++++--------- .../Secrets/ISecret.cs | 9 ++- .../ISerializer.cs | 8 ++- .../ISignalProcessor.cs | 16 +++-- 9 files changed, 120 insertions(+), 64 deletions(-) diff --git a/src/RapidField.SolidInstruments.Collections/ICalculatedSequence.cs b/src/RapidField.SolidInstruments.Collections/ICalculatedSequence.cs index 78df3b5e..d85a99d9 100644 --- a/src/RapidField.SolidInstruments.Collections/ICalculatedSequence.cs +++ b/src/RapidField.SolidInstruments.Collections/ICalculatedSequence.cs @@ -12,7 +12,7 @@ namespace RapidField.SolidInstruments.Collections /// /// The element type of the sequence. /// - public interface ICalculatedSequence + public interface ICalculatedSequence : ICalculatedSequence { /// /// Calculates the next term in the sequence. @@ -46,7 +46,13 @@ public interface ICalculatedSequence /// An array containing the calculated terms in the specified range. /// T[] ToArray(Int32 startIndex, Int32 count); + } + /// + /// Represents a sequence of calculated terms. + /// + public interface ICalculatedSequence + { /// /// Gets the number of terms that have been calculated. /// diff --git a/src/RapidField.SolidInstruments.Collections/ICircularBuffer.cs b/src/RapidField.SolidInstruments.Collections/ICircularBuffer.cs index 0aafcffc..6e3a3e01 100644 --- a/src/RapidField.SolidInstruments.Collections/ICircularBuffer.cs +++ b/src/RapidField.SolidInstruments.Collections/ICircularBuffer.cs @@ -14,7 +14,7 @@ namespace RapidField.SolidInstruments.Collections /// /// The element type of the collection. /// - public interface ICircularBuffer : IAsyncDisposable, IDisposable, IEnumerable, IEnumerable + public interface ICircularBuffer : ICircularBuffer, IEnumerable { /// /// Gets the element at the specified index. @@ -67,9 +67,15 @@ T this[Int32 index] /// is and the write operation would have caused overwrite. /// void Write(T element, Boolean permitOverwrite); + } + /// + /// Represents a thread-safe, contiguous, generic collection of elements. + /// + public interface ICircularBuffer : IAsyncDisposable, IDisposable, IEnumerable + { /// - /// Gets the maximum number of elements that the current can accommodate. + /// Gets the maximum number of elements that the current can accommodate. /// Int32 Capacity { @@ -77,7 +83,7 @@ Int32 Capacity } /// - /// Gets the number of elements contained by the current . + /// Gets the number of elements contained by the current . /// Int32 Length { diff --git a/src/RapidField.SolidInstruments.Collections/IInfiniteSequence.cs b/src/RapidField.SolidInstruments.Collections/IInfiniteSequence.cs index 808b2008..32930b3b 100644 --- a/src/RapidField.SolidInstruments.Collections/IInfiniteSequence.cs +++ b/src/RapidField.SolidInstruments.Collections/IInfiniteSequence.cs @@ -12,7 +12,7 @@ namespace RapidField.SolidInstruments.Collections /// /// The element type of the sequence. /// - public interface IInfiniteSequence : ICalculatedSequence + public interface IInfiniteSequence : ICalculatedSequence, IInfiniteSequence { /// /// Gets the term at the specified index. @@ -30,9 +30,15 @@ T this[Int32 index] { get; } + } + /// + /// Represents a thread-safe, infinite sequence of calculated values. + /// + public interface IInfiniteSequence : ICalculatedSequence + { /// - /// Clears the terms in the current , leaving in place the seed terms. + /// Clears the terms in the current , leaving in place the seed terms. /// void Reset(); } diff --git a/src/RapidField.SolidInstruments.Collections/ITreeNode.cs b/src/RapidField.SolidInstruments.Collections/ITreeNode.cs index cfea3126..116fddc6 100644 --- a/src/RapidField.SolidInstruments.Collections/ITreeNode.cs +++ b/src/RapidField.SolidInstruments.Collections/ITreeNode.cs @@ -36,7 +36,7 @@ public interface ITreeNode : ITreeNode /// /// The value type of the node. /// - public interface ITreeNode : IEnumerable>, IEnumerable + public interface ITreeNode : IEnumerable>, ITreeNode { /// /// Gets the child elements of the node, or an empty collection if the current is a terminal @@ -47,22 +47,6 @@ IEnumerable> Children get; } - /// - /// Gets the number of connections from the tree's root node to the current . - /// - Int32 Depth - { - get; - } - - /// - /// Gets the number of connections on the longest path between the current and a leaf node. - /// - Int32 Height - { - get; - } - /// /// Gets a value indicating whether or not the current is a leaf node (a node without children). /// @@ -103,4 +87,26 @@ T Value set; } } + + /// + /// Represents a node in a tree structure. + /// + public interface ITreeNode : IEnumerable + { + /// + /// Gets the number of connections from the tree's root node to the current . + /// + Int32 Depth + { + get; + } + + /// + /// Gets the number of connections on the longest path between the current and a leaf node. + /// + Int32 Height + { + get; + } + } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Hashing/IHashTree.cs b/src/RapidField.SolidInstruments.Cryptography/Hashing/IHashTree.cs index 4c408c9b..9a2fdbd1 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Hashing/IHashTree.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Hashing/IHashTree.cs @@ -16,7 +16,7 @@ namespace RapidField.SolidInstruments.Cryptography.Hashing /// /// The type of the data block objects underlying the hash tree. /// - public interface IHashTree : IAsyncDisposable, IDisposable + public interface IHashTree : IHashTree where TBlock : class { /// @@ -46,9 +46,16 @@ public interface IHashTree : IAsyncDisposable, IDisposable /// An exception was raised during hashing or serialization. /// void AddBlockRange(IEnumerable blocks); + } + /// + /// Represents hash values for an ordered series of data block objects and produces a deterministic root hash using a Merkle + /// tree. + /// + public interface IHashTree : IAsyncDisposable, IDisposable + { /// - /// Gets the number of leaf nodes in the current . + /// Gets the number of leaf nodes in the current . /// Int32 LeafCount { @@ -56,7 +63,7 @@ Int32 LeafCount } /// - /// Gets the root node for the current . + /// Gets the root node for the current . /// ITreeNode RootNode { diff --git a/src/RapidField.SolidInstruments.Cryptography/Hashing/IHashingProcessor.cs b/src/RapidField.SolidInstruments.Cryptography/Hashing/IHashingProcessor.cs index 2e7116e5..71278068 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Hashing/IHashingProcessor.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Hashing/IHashingProcessor.cs @@ -13,88 +13,94 @@ namespace RapidField.SolidInstruments.Cryptography.Hashing /// /// The type of the object that can be hashed. /// - public interface IHashingProcessor + public interface IHashingProcessor : IHashingProcessor { /// - /// Calculates a hash value for the specified plaintext binary array. + /// Calculates a hash value for the specified plaintext object. /// - /// - /// The plaintext binary array to hash. + /// + /// The plaintext object to hash. /// /// /// The algorithm specification used to transform the plaintext. /// + /// + /// A value specifying whether or not salt is applied to the plaintext. + /// /// /// The resulting hash value. /// /// /// An exception was raised during hashing or serialization. /// - Byte[] CalculateHash(Byte[] plaintextBinaryArray, HashingAlgorithmSpecification algorithm); + Byte[] CalculateHash(T plaintextObject, HashingAlgorithmSpecification algorithm, SaltingMode saltingMode); /// - /// Calculates a hash value for the specified plaintext binary array. + /// Calculates a hash value for the specified plaintext object and compares the result with the specified hash value. /// - /// - /// The plaintext binary array to hash. + /// + /// The hash value to evaluate. + /// + /// + /// The plaintext object to evaluate. /// /// /// The algorithm specification used to transform the plaintext. /// - /// - /// The salt to apply to the plaintext, or if the plaintext is unsalted. The default value is - /// . + /// + /// A value specifying whether or not salt is applied to the plaintext. /// /// - /// The resulting hash value. + /// if the resulting hash value matches , otherwise + /// . /// /// /// An exception was raised during hashing or serialization. /// - Byte[] CalculateHash(Byte[] plaintextBinaryArray, HashingAlgorithmSpecification algorithm, Byte[] salt); + Boolean EvaluateHash(Byte[] hash, T plaintextObject, HashingAlgorithmSpecification algorithm, SaltingMode saltingMode); + } + /// + /// Provides facilities for hashing typed objects and binary arrays. + /// + public interface IHashingProcessor + { /// - /// Calculates a hash value for the specified plaintext object. + /// Calculates a hash value for the specified plaintext binary array. /// - /// - /// The plaintext object to hash. + /// + /// The plaintext binary array to hash. /// /// /// The algorithm specification used to transform the plaintext. /// - /// - /// A value specifying whether or not salt is applied to the plaintext. - /// /// /// The resulting hash value. /// /// /// An exception was raised during hashing or serialization. /// - Byte[] CalculateHash(T plaintextObject, HashingAlgorithmSpecification algorithm, SaltingMode saltingMode); + Byte[] CalculateHash(Byte[] plaintextBinaryArray, HashingAlgorithmSpecification algorithm); /// - /// Calculates a hash value for the specified plaintext object and compares the result with the specified hash value. + /// Calculates a hash value for the specified plaintext binary array. /// - /// - /// The hash value to evaluate. - /// - /// - /// The plaintext object to evaluate. + /// + /// The plaintext binary array to hash. /// /// /// The algorithm specification used to transform the plaintext. /// - /// - /// A value specifying whether or not salt is applied to the plaintext. + /// + /// The salt to apply to the plaintext, or if the plaintext is unsalted. The default value is + /// . /// /// - /// if the resulting hash value matches , otherwise - /// . + /// The resulting hash value. /// /// /// An exception was raised during hashing or serialization. /// - Boolean EvaluateHash(Byte[] hash, T plaintextObject, HashingAlgorithmSpecification algorithm, SaltingMode saltingMode); + Byte[] CalculateHash(Byte[] plaintextBinaryArray, HashingAlgorithmSpecification algorithm, Byte[] salt); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecret.cs index f36d171a..ad0fd2f3 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecret.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecret.cs @@ -12,7 +12,7 @@ namespace RapidField.SolidInstruments.Cryptography.Secrets /// /// The type of the value. /// - public interface ISecret : IReadOnlySecret + public interface ISecret : IReadOnlySecret, ISecret { /// /// Performs the specified write operation and encrypts the resulting value as a thread-safe, atomic operation. @@ -31,4 +31,11 @@ public interface ISecret : IReadOnlySecret /// void Write(Func writeFunction); } + + /// + /// Represents a named secret value that is pinned in memory and encrypted at rest. + /// + public interface ISecret : IReadOnlySecret + { + } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Serialization/ISerializer.cs b/src/RapidField.SolidInstruments.Serialization/ISerializer.cs index 18741df6..f1af8c80 100644 --- a/src/RapidField.SolidInstruments.Serialization/ISerializer.cs +++ b/src/RapidField.SolidInstruments.Serialization/ISerializer.cs @@ -12,7 +12,7 @@ namespace RapidField.SolidInstruments.Serialization /// /// The type of the serializable object. /// - public interface ISerializer + public interface ISerializer : ISerializer where T : class { /// @@ -42,7 +42,13 @@ public interface ISerializer /// is . /// Byte[] Serialize(T target); + } + /// + /// Performs serialization and deserialization for a given type. + /// + public interface ISerializer + { /// /// Gets the format to use for serialization and deserialization. /// diff --git a/src/RapidField.SolidInstruments.SignalProcessing/ISignalProcessor.cs b/src/RapidField.SolidInstruments.SignalProcessing/ISignalProcessor.cs index 3058cbbf..7848f29b 100644 --- a/src/RapidField.SolidInstruments.SignalProcessing/ISignalProcessor.cs +++ b/src/RapidField.SolidInstruments.SignalProcessing/ISignalProcessor.cs @@ -13,21 +13,27 @@ namespace RapidField.SolidInstruments.SignalProcessing /// /// The type of the operational settings for the signal processor. /// - public interface ISignalProcessor : IChannel + public interface ISignalProcessor : IChannel, ISignalProcessor where TSettings : SignalProcessorSettings, new() { /// - /// Gets the input channels for the current . + /// Gets the operational settings for the current . /// - IChannelCollection InputChannels + TSettings Settings { get; } + } + /// + /// Converts one or more input signals to a single output signal. + /// + public interface ISignalProcessor : IChannel + { /// - /// Gets the operational settings for the current . + /// Gets the input channels for the current . /// - TSettings Settings + IChannelCollection InputChannels { get; } From 4a595bedd4c275aa79bc0997d3c6158f91aadcfa Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Sun, 16 Feb 2020 22:08:37 -0600 Subject: [PATCH 09/55] Minor tweaks. --- .../InfiniteSequence.cs | 2 +- src/RapidField.SolidInstruments.Core/TimeOfDay.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/RapidField.SolidInstruments.Collections/InfiniteSequence.cs b/src/RapidField.SolidInstruments.Collections/InfiniteSequence.cs index 47cf1966..4264f17d 100644 --- a/src/RapidField.SolidInstruments.Collections/InfiniteSequence.cs +++ b/src/RapidField.SolidInstruments.Collections/InfiniteSequence.cs @@ -184,7 +184,7 @@ public T[] ToArray(Int32 startIndex, Int32 count) /// /// A string representation of the current . /// - public override String ToString() => $"Calculated term count: {CalculatedTermCount}"; + public override String ToString() => $"Calculated term count: {CalculatedTermCount}, Last term: {CalculatedTerms.Last()}"; /// /// Calculates the next term in the sequence. diff --git a/src/RapidField.SolidInstruments.Core/TimeOfDay.cs b/src/RapidField.SolidInstruments.Core/TimeOfDay.cs index 1507f1fd..6120ff77 100644 --- a/src/RapidField.SolidInstruments.Core/TimeOfDay.cs +++ b/src/RapidField.SolidInstruments.Core/TimeOfDay.cs @@ -470,8 +470,8 @@ public Byte[] ToByteArray() /// public override String ToString() { - Int32? twelveHourClockFormatHourValue = null; - String meridiemStringFragment = null; + Int32? twelveHourClockFormatHourValue; + String meridiemStringFragment; switch (Hour) { From a1ea58df654d3d28ffc4b089dd40521066317fd5 Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Wed, 19 Feb 2020 17:01:35 -0600 Subject: [PATCH 10/55] Normalizing ToString implementation. --- .../CircularBuffer.cs | 2 +- .../InfiniteSequence.cs | 2 +- .../ReadOnlyPinnedBuffer.cs | 8 ++++++++ .../TreeNode.cs | 8 ++++++++ .../Command.cs | 16 ++++++++++++++++ .../Concurrency/ConcurrencyControl.cs | 8 ++++++++ .../Concurrency/ConcurrencyControlToken.cs | 2 +- src/RapidField.SolidInstruments.Core/Model.cs | 2 +- .../ReferenceManager.cs | 2 +- .../Secrets/Secret.cs | 2 +- .../Secrets/SecretVault.cs | 8 ++++++++ .../SecureBuffer.cs | 8 ++++++++ .../Event.cs | 2 +- .../Statistics/DescriptiveStatistics.cs | 2 +- .../MessageAdapter.cs | 6 ------ .../MessageHandler.cs | 17 +++++++++++++++++ .../DurableMessageLockToken.cs | 2 +- .../TransportPrimitives/DurableMessageQueue.cs | 2 +- .../ObjectContainerDefinition.cs | 2 +- .../ObjectFactory.cs | 8 ++++++++ .../ObjectFactoryProductionFunction.cs | 2 +- .../DynamicSerializer.cs | 2 +- .../ServiceExecutor.cs | 11 +++++++++++ .../Channel.cs | 2 +- .../ChannelCollection.cs | 8 ++++++++ .../DiscreteUnitOfOutput.cs | 8 ++++++++ .../SignalSample.cs | 8 ++++++++ 27 files changed, 130 insertions(+), 20 deletions(-) diff --git a/src/RapidField.SolidInstruments.Collections/CircularBuffer.cs b/src/RapidField.SolidInstruments.Collections/CircularBuffer.cs index 667a0cb4..6fd0e664 100644 --- a/src/RapidField.SolidInstruments.Collections/CircularBuffer.cs +++ b/src/RapidField.SolidInstruments.Collections/CircularBuffer.cs @@ -122,7 +122,7 @@ public T Read() /// /// A string representation of the current . /// - public override String ToString() => $"Capacity: {Capacity}, Length: {Length}"; + public override String ToString() => $"{{ {nameof(Capacity)}: {Capacity}, {nameof(Length)}: {Length} }}"; /// /// Writes an element at the tail of the current . diff --git a/src/RapidField.SolidInstruments.Collections/InfiniteSequence.cs b/src/RapidField.SolidInstruments.Collections/InfiniteSequence.cs index 4264f17d..5bbecd38 100644 --- a/src/RapidField.SolidInstruments.Collections/InfiniteSequence.cs +++ b/src/RapidField.SolidInstruments.Collections/InfiniteSequence.cs @@ -184,7 +184,7 @@ public T[] ToArray(Int32 startIndex, Int32 count) /// /// A string representation of the current . /// - public override String ToString() => $"Calculated term count: {CalculatedTermCount}, Last term: {CalculatedTerms.Last()}"; + public override String ToString() => $"{{ {nameof(CalculatedTermCount)}: {CalculatedTermCount} }}"; /// /// Calculates the next term in the sequence. diff --git a/src/RapidField.SolidInstruments.Collections/ReadOnlyPinnedBuffer.cs b/src/RapidField.SolidInstruments.Collections/ReadOnlyPinnedBuffer.cs index f2347d1d..c5c10c0d 100644 --- a/src/RapidField.SolidInstruments.Collections/ReadOnlyPinnedBuffer.cs +++ b/src/RapidField.SolidInstruments.Collections/ReadOnlyPinnedBuffer.cs @@ -191,6 +191,14 @@ public override Int32 GetHashCode() return hashCode ^ 0x55555555; } + /// + /// Converts the value of the current to its equivalent string representation. + /// + /// + /// A string representation of the current . + /// + public override String ToString() => $"{{ {nameof(Length)}: {Length}, {nameof(LengthInBytes)}: {LengthInBytes} }}"; + /// /// Releases all resources consumed by the current . /// diff --git a/src/RapidField.SolidInstruments.Collections/TreeNode.cs b/src/RapidField.SolidInstruments.Collections/TreeNode.cs index 44fb2966..f408552e 100644 --- a/src/RapidField.SolidInstruments.Collections/TreeNode.cs +++ b/src/RapidField.SolidInstruments.Collections/TreeNode.cs @@ -262,6 +262,14 @@ public Boolean RemoveChild(TreeNode childNode) } } + /// + /// Converts the value of the current to its equivalent string representation. + /// + /// + /// A string representation of the current . + /// + public override String ToString() => $"{{ {nameof(Depth)}: {Depth}, {nameof(Height)}: {Height}, {nameof(Value)}: {Value} }}"; + /// /// Adds the specified node to and sets its to the current node. /// diff --git a/src/RapidField.SolidInstruments.Command/Command.cs b/src/RapidField.SolidInstruments.Command/Command.cs index b40409ef..6ef3993f 100644 --- a/src/RapidField.SolidInstruments.Command/Command.cs +++ b/src/RapidField.SolidInstruments.Command/Command.cs @@ -27,6 +27,14 @@ protected Command() return; } + /// + /// Converts the value of the current to its equivalent string representation. + /// + /// + /// A string representation of the current . + /// + public override String ToString() => $"{{ {nameof(ResultType)}: {ResultType.FullName} }}"; + /// /// Gets the type of the result that is emitted when processing the command. /// @@ -55,6 +63,14 @@ protected Command() return; } + /// + /// Converts the value of the current to its equivalent string representation. + /// + /// + /// A string representation of the current . + /// + public override String ToString() => $"{{ {nameof(ResultType)}: {ResultType.FullName} }}"; + /// /// Gets the type of the result that is emitted when processing the command. /// diff --git a/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControl.cs b/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControl.cs index feb2362c..aa5a0674 100644 --- a/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControl.cs +++ b/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControl.cs @@ -183,6 +183,14 @@ public void Exit(ConcurrencyControlToken token) throw new ConcurrencyControlOperationException("The specified token is not valid for release by the control. It was issued by a different control or it was already released."); } + /// + /// Converts the value of the current to its equivalent string representation. + /// + /// + /// A string representation of the current . + /// + public override String ToString() => $"{{ {nameof(ConsumptionState)}: {ConsumptionState} }}"; + /// /// Releases all resources consumed by the current . /// diff --git a/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControlToken.cs b/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControlToken.cs index 0cee5c9c..d4724f33 100644 --- a/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControlToken.cs +++ b/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControlToken.cs @@ -397,7 +397,7 @@ public void Release() /// /// A string representation of the current . /// - public override String ToString() => $"{(IsActive ? "Active" : "Inactive")} ({Identifier})"; + public override String ToString() => $"{{ {nameof(Identifier)}: {Identifier}, {nameof(IsActive)}: {IsActive} }}"; /// /// Informs the issuing control that the associated thread is finished consuming the resource. diff --git a/src/RapidField.SolidInstruments.Core/Model.cs b/src/RapidField.SolidInstruments.Core/Model.cs index 9ec69f93..ee4959ba 100644 --- a/src/RapidField.SolidInstruments.Core/Model.cs +++ b/src/RapidField.SolidInstruments.Core/Model.cs @@ -183,7 +183,7 @@ protected Model(TIdentifier identifier) /// /// A string representation of the current . /// - public override String ToString() => Identifier.ToString(); + public override String ToString() => $"{{ {nameof(Identifier)}: {Identifier} }}"; /// /// Gets or sets a value that uniquely identifies the current . diff --git a/src/RapidField.SolidInstruments.Core/ReferenceManager.cs b/src/RapidField.SolidInstruments.Core/ReferenceManager.cs index a3d7d2db..002f8b49 100644 --- a/src/RapidField.SolidInstruments.Core/ReferenceManager.cs +++ b/src/RapidField.SolidInstruments.Core/ReferenceManager.cs @@ -60,7 +60,7 @@ public void AddObject(T reference) /// /// A string representation of the current . /// - public override String ToString() => $"Object count: {ObjectCount}"; + public override String ToString() => $"{{ {nameof(ObjectCount)}: {ObjectCount} }}"; /// /// Releases all resources consumed by the current . diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/Secret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/Secret.cs index 29d20c7b..a9579263 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/Secret.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/Secret.cs @@ -211,7 +211,7 @@ public void Read(Action readAction) /// /// A string representation of the current . /// - public override String ToString() => Name; + public override String ToString() => $"{{ {nameof(Name)}: {Name} }}"; /// /// Performs the specified write operation and encrypts the resulting value as a thread-safe, atomic operation. diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretVault.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretVault.cs index 38bdeb88..79fc7e95 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretVault.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretVault.cs @@ -430,6 +430,14 @@ public void Clear() /// public Task ReadAsync(String name, Action readAction) => ReadAsync(name, readAction.RejectIf().IsNull(nameof(readAction))); + /// + /// Converts the value of the current to its equivalent string representation. + /// + /// + /// A string representation of the current . + /// + public override String ToString() => $"{{ {nameof(Count)}: {Count} }}"; + /// /// Attempts to remove a secret with the specified name. /// diff --git a/src/RapidField.SolidInstruments.Cryptography/SecureBuffer.cs b/src/RapidField.SolidInstruments.Cryptography/SecureBuffer.cs index 9a5e3da5..16d1cdc3 100644 --- a/src/RapidField.SolidInstruments.Cryptography/SecureBuffer.cs +++ b/src/RapidField.SolidInstruments.Cryptography/SecureBuffer.cs @@ -116,6 +116,14 @@ public void Access(Action action) /// public override Int32 GetHashCode() => Ciphertext.ComputeThirtyTwoBitHash() ^ 0x3a566a5c; + /// + /// Converts the value of the current to its equivalent string representation. + /// + /// + /// A string representation of the current . + /// + public override String ToString() => $"{{ {nameof(LengthInBytes)}: {LengthInBytes} }}"; + /// /// Releases all resources consumed by the current . /// diff --git a/src/RapidField.SolidInstruments.EventAuthoring/Event.cs b/src/RapidField.SolidInstruments.EventAuthoring/Event.cs index 6e4f62f0..972ee9c9 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/Event.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/Event.cs @@ -334,7 +334,7 @@ public Boolean Equals(IEvent other) /// /// A string representation of the current . /// - public override String ToString() => Description; + public override String ToString() => $"{{ {nameof(Description)}: \"{Description}\" }}"; /// /// Gets or sets the category of the event. diff --git a/src/RapidField.SolidInstruments.Mathematics/Statistics/DescriptiveStatistics.cs b/src/RapidField.SolidInstruments.Mathematics/Statistics/DescriptiveStatistics.cs index bb9c1922..409079ad 100644 --- a/src/RapidField.SolidInstruments.Mathematics/Statistics/DescriptiveStatistics.cs +++ b/src/RapidField.SolidInstruments.Mathematics/Statistics/DescriptiveStatistics.cs @@ -68,7 +68,7 @@ internal DescriptiveStatistics(Int32 size, Decimal minimum, Decimal maximum, Dec /// /// A string representation of the current . /// - public override String ToString() => $"Size: {Size}, Mean: {Mean.RoundedTo(3)}, StDev: {StandardDeviation.RoundedTo(3)}"; + public override String ToString() => $"{{ {nameof(Size)}: {Size}, {nameof(Mean)}: {Mean.RoundedTo(3)}, {nameof(StandardDeviation)}: {StandardDeviation.RoundedTo(3)} }}"; /// /// Represents the highest value of the numeric collection represented by the current . diff --git a/src/RapidField.SolidInstruments.Messaging/MessageAdapter.cs b/src/RapidField.SolidInstruments.Messaging/MessageAdapter.cs index 43a3ef0e..6154a64d 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessageAdapter.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessageAdapter.cs @@ -137,11 +137,5 @@ public SerializationFormat MessageSerializationFormat /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] private const SerializationFormat DefaultMessageSerializationFormat = SerializationFormat.Binary; - - /// - /// Represents the default concurrency control mode that is used to manage state. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const ConcurrencyControlMode DefaultStateControlMode = ConcurrencyControlMode.ProcessorCountSemaphore; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/MessageHandler.cs b/src/RapidField.SolidInstruments.Messaging/MessageHandler.cs index 506ec246..59d22f06 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessageHandler.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessageHandler.cs @@ -46,6 +46,14 @@ protected MessageHandler(ICommandMediator mediator, MessageHandlerRole role, Mes Role = role.RejectIf().IsEqualToValue(MessageHandlerRole.Unspecified, nameof(role)); } + /// + /// Converts the value of the current to its equivalent string representation. + /// + /// + /// A string representation of the current . + /// + public override String ToString() => $"{{ {nameof(EntityType)}: {EntityType}, {nameof(Role)}: {Role} }}"; + /// /// Releases all resources consumed by the current . /// @@ -113,6 +121,15 @@ protected MessageHandler(ICommandMediator mediator, MessageHandlerRole role, Mes Role = role.RejectIf().IsEqualToValue(MessageHandlerRole.Unspecified, nameof(role)); } + /// + /// Converts the value of the current to its equivalent string + /// representation. + /// + /// + /// A string representation of the current . + /// + public override String ToString() => $"{{ {nameof(EntityType)}: {EntityType}, {nameof(Role)}: {Role} }}"; + /// /// Releases all resources consumed by the current . /// diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessageLockToken.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessageLockToken.cs index e8d77ea3..ed0f88cb 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessageLockToken.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessageLockToken.cs @@ -274,7 +274,7 @@ public Byte[] ToByteArray() /// /// A string representation of the current . /// - public sealed override String ToString() => $"{Identifier.ToSerializedString()} | {ExpirationDateTime.ToSerializedString()}"; + public sealed override String ToString() => $"{{ {nameof(Identifier)}: {Identifier.ToSerializedString()}, {nameof(ExpirationDateTime)}: {ExpirationDateTime.ToSerializedString()} }}"; /// /// Represents the date and time of expiration for the lock, after which the message will become available for processing. diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessageQueue.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessageQueue.cs index 7560d303..b24e2f59 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessageQueue.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessageQueue.cs @@ -477,7 +477,7 @@ public Task EnqueueAsync(IMessageBase message) /// /// A string representation of the current . /// - public sealed override String ToString() => Path; + public sealed override String ToString() => $"{{ {nameof(Path)}: {Path} }}"; /// /// Attempts to set the operational state of the current to diff --git a/src/RapidField.SolidInstruments.ObjectComposition/ObjectContainerDefinition.cs b/src/RapidField.SolidInstruments.ObjectComposition/ObjectContainerDefinition.cs index 44d2ef32..1cac1b16 100644 --- a/src/RapidField.SolidInstruments.ObjectComposition/ObjectContainerDefinition.cs +++ b/src/RapidField.SolidInstruments.ObjectComposition/ObjectContainerDefinition.cs @@ -251,7 +251,7 @@ public Byte[] ToByteArray() /// /// A string representation of the current . /// - public override String ToString() => $"RequestType: {RequestType.FullName} | ProductType: {ProductType.FullName}"; + public override String ToString() => $"{{ {nameof(RequestType)}: {RequestType.FullName}, {nameof(ProductType)}: {ProductType.FullName} }}"; /// /// Gets the type that is produced as a result of a request for . diff --git a/src/RapidField.SolidInstruments.ObjectComposition/ObjectFactory.cs b/src/RapidField.SolidInstruments.ObjectComposition/ObjectFactory.cs index 859b916c..0b4f792f 100644 --- a/src/RapidField.SolidInstruments.ObjectComposition/ObjectFactory.cs +++ b/src/RapidField.SolidInstruments.ObjectComposition/ObjectFactory.cs @@ -165,6 +165,14 @@ public Object Produce(Type type) throw new ArgumentException($"The specified product type, {type.FullName}, is not supported by the object factory type {GetType().FullName}.", nameof(type)); } + /// + /// Converts the value of the current to its equivalent string representation. + /// + /// + /// A string representation of the current . + /// + public override String ToString() => $"{{ {nameof(ProductBaseType)}: {ProductBaseType.FullName} }}"; + /// /// Returns a collection of supported product types paired with functions to create them. /// diff --git a/src/RapidField.SolidInstruments.ObjectComposition/ObjectFactoryProductionFunction.cs b/src/RapidField.SolidInstruments.ObjectComposition/ObjectFactoryProductionFunction.cs index 457c46bc..cb2fab97 100644 --- a/src/RapidField.SolidInstruments.ObjectComposition/ObjectFactoryProductionFunction.cs +++ b/src/RapidField.SolidInstruments.ObjectComposition/ObjectFactoryProductionFunction.cs @@ -103,7 +103,7 @@ protected ObjectFactoryProductionFunction() /// /// A string representation of the current . /// - public override String ToString() => $"Product type: {ProductType.FullName}"; + public override String ToString() => $"{{ {nameof(ProductType)}: {ProductType.FullName} }}"; /// /// Gets the type of the object produced by the function. diff --git a/src/RapidField.SolidInstruments.Serialization/DynamicSerializer.cs b/src/RapidField.SolidInstruments.Serialization/DynamicSerializer.cs index b3d4bf52..a83eeff0 100644 --- a/src/RapidField.SolidInstruments.Serialization/DynamicSerializer.cs +++ b/src/RapidField.SolidInstruments.Serialization/DynamicSerializer.cs @@ -376,7 +376,7 @@ protected DynamicSerializer(SerializationFormat format) /// /// A string representation of the current . /// - public override String ToString() => $"Format: {Format}"; + public override String ToString() => $"{{ {nameof(Format)}: {Format} }}"; /// /// Converts the specified buffer to its typed equivalent. diff --git a/src/RapidField.SolidInstruments.Service/ServiceExecutor.cs b/src/RapidField.SolidInstruments.Service/ServiceExecutor.cs index 75dfaa15..b904cdc5 100644 --- a/src/RapidField.SolidInstruments.Service/ServiceExecutor.cs +++ b/src/RapidField.SolidInstruments.Service/ServiceExecutor.cs @@ -85,6 +85,17 @@ public void Execute() } } + /// + /// Converts the value of the current + /// to its equivalent string + /// representation. + /// + /// + /// A string representation of the current + /// . + /// + public override String ToString() => $"{{ {nameof(ServiceName)}: \"{ServiceName}\" }}"; + /// /// Builds the application configuration for the service. /// diff --git a/src/RapidField.SolidInstruments.SignalProcessing/Channel.cs b/src/RapidField.SolidInstruments.SignalProcessing/Channel.cs index d40d8b4f..ff56dae1 100644 --- a/src/RapidField.SolidInstruments.SignalProcessing/Channel.cs +++ b/src/RapidField.SolidInstruments.SignalProcessing/Channel.cs @@ -438,7 +438,7 @@ public void Toggle() /// /// A string representation of the current . /// - public override String ToString() => $"{Identifier.ToEnhancedReadabilityGuid()} | {Name}"; + public override String ToString() => $"{{ {nameof(Identifier)}: {Identifier.ToSerializedString()}, {nameof(Name)}: {Name} }}"; /// /// Releases all resources consumed by the current . diff --git a/src/RapidField.SolidInstruments.SignalProcessing/ChannelCollection.cs b/src/RapidField.SolidInstruments.SignalProcessing/ChannelCollection.cs index 2dfe3498..186fb6ff 100644 --- a/src/RapidField.SolidInstruments.SignalProcessing/ChannelCollection.cs +++ b/src/RapidField.SolidInstruments.SignalProcessing/ChannelCollection.cs @@ -2527,6 +2527,14 @@ public IEnumerator GetEnumerator() /// IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + /// + /// Converts the value of the current to its equivalent string representation. + /// + /// + /// A string representation of the current . + /// + public override String ToString() => $"{{ {nameof(Identifier)}: {Identifier.ToSerializedString()}, {nameof(Count)}: {Count} }}"; + /// /// Gets the number of channels in the collection. /// diff --git a/src/RapidField.SolidInstruments.SignalProcessing/DiscreteUnitOfOutput.cs b/src/RapidField.SolidInstruments.SignalProcessing/DiscreteUnitOfOutput.cs index 20bb44e8..58ef4fb9 100644 --- a/src/RapidField.SolidInstruments.SignalProcessing/DiscreteUnitOfOutput.cs +++ b/src/RapidField.SolidInstruments.SignalProcessing/DiscreteUnitOfOutput.cs @@ -36,6 +36,14 @@ public DiscreteUnitOfOutput(T value, Int32 channelReadIndex) Value = value; } + /// + /// Converts the value of the current to its equivalent string representation. + /// + /// + /// A string representation of the current . + /// + public override String ToString() => $"{{ {nameof(ChannelReadIndex)}: {ChannelReadIndex}, {nameof(Value)}: {Value} }}"; + /// /// Gets the zero-based index for the current within the associated channel's output /// stream. diff --git a/src/RapidField.SolidInstruments.SignalProcessing/SignalSample.cs b/src/RapidField.SolidInstruments.SignalProcessing/SignalSample.cs index 14bc860c..a4da0c19 100644 --- a/src/RapidField.SolidInstruments.SignalProcessing/SignalSample.cs +++ b/src/RapidField.SolidInstruments.SignalProcessing/SignalSample.cs @@ -78,6 +78,14 @@ public SignalSample(IDiscreteUnitOfOutput unitOfOutput, IOutputRange lookB LookBehindRange = lookBehindRange.RejectIf().IsNull(nameof(lookBehindRange)).TargetArgument; } + /// + /// Converts the value of the current to its equivalent string representation. + /// + /// + /// A string representation of the current . + /// + public override String ToString() => $"{{ {nameof(UnitOfOutput)}: {UnitOfOutput} }}"; + /// /// Gets a range of discrete units of output following in the channel's output stream, or an /// empty range if look-ahead was not requested. From 06c42ee204027006c27fdfdbfcae54b697c20870 Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Thu, 20 Feb 2020 19:01:17 -0600 Subject: [PATCH 11/55] Improving ToString implementations. --- src/RapidField.SolidInstruments.Core/Model.cs | 2 +- .../Service/HeartbeatScheduleItem.cs | 9 +++++++++ .../DynamicSerializer.cs | 8 ++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/RapidField.SolidInstruments.Core/Model.cs b/src/RapidField.SolidInstruments.Core/Model.cs index ee4959ba..32cfee08 100644 --- a/src/RapidField.SolidInstruments.Core/Model.cs +++ b/src/RapidField.SolidInstruments.Core/Model.cs @@ -321,6 +321,6 @@ public Boolean Equals(IModel other) /// /// A string representation of the current . /// - public override String ToString() => GetHashCode().ToString(); + public override String ToString() => Convert.ToBase64String(GetHashCode().ToByteArray()); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/Service/HeartbeatScheduleItem.cs b/src/RapidField.SolidInstruments.Messaging/Service/HeartbeatScheduleItem.cs index f469aaf4..4d878882 100644 --- a/src/RapidField.SolidInstruments.Messaging/Service/HeartbeatScheduleItem.cs +++ b/src/RapidField.SolidInstruments.Messaging/Service/HeartbeatScheduleItem.cs @@ -267,6 +267,15 @@ public override Boolean Equals(Object obj) /// public override Int32 GetHashCode() => ((IntervalInSeconds ^ (Int32)EntityType) ^ ((Label is null ? 0 : Label.GetHashCode()) ^ MessageType.FullName.GetHashCode())); + /// + /// Converts the value of the current to its equivalent string + /// representation. + /// + /// + /// A string representation of the current . + /// + public override String ToString() => $"{{ {nameof(EntityType)}: {EntityType}, {nameof(MessageType)}: {MessageType.FullName}, {nameof(IntervalInSeconds)}: {IntervalInSeconds}, {nameof(Label)}: \"{Label}\" }}"; + /// /// Asynchronously transmits a single heartbeat message with characteristics defined by the current /// . diff --git a/src/RapidField.SolidInstruments.Serialization/DynamicSerializer.cs b/src/RapidField.SolidInstruments.Serialization/DynamicSerializer.cs index a83eeff0..4a121cbe 100644 --- a/src/RapidField.SolidInstruments.Serialization/DynamicSerializer.cs +++ b/src/RapidField.SolidInstruments.Serialization/DynamicSerializer.cs @@ -84,6 +84,14 @@ public DynamicSerializer(SerializationFormat format) /// public Byte[] Serialize(T target) => Serialize(target.RejectIf().IsNull(nameof(target)), Format); + /// + /// Converts the value of the current to its equivalent string representation. + /// + /// + /// A string representation of the current . + /// + public override String ToString() => $"{{ {nameof(Format)}: {Format}, {nameof(ContractType)}: {ContractType.FullName} }}"; + /// /// Converts the specified buffer to its typed equivalent. /// From 98f29edce4d2afe7b84c5cce2c7f50513f1262c0 Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Wed, 26 Feb 2020 18:04:26 -0600 Subject: [PATCH 12/55] Adding some minor functionality to interfaces. --- .../ICommandHandler.cs | 3 ++- .../ICommandMediator.cs | 3 ++- .../IDependencyContainer.cs | 3 ++- .../IDurableMessageTransport.cs | 18 +++++++++++++++++- .../IServiceExecutor.cs | 3 ++- 5 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/RapidField.SolidInstruments.Command/ICommandHandler.cs b/src/RapidField.SolidInstruments.Command/ICommandHandler.cs index e39e320e..187e8449 100644 --- a/src/RapidField.SolidInstruments.Command/ICommandHandler.cs +++ b/src/RapidField.SolidInstruments.Command/ICommandHandler.cs @@ -2,6 +2,7 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= +using RapidField.SolidInstruments.Core; using System; namespace RapidField.SolidInstruments.Command @@ -50,7 +51,7 @@ public interface ICommandHandler : ICommandHandler /// The type of the command that is processed by the handler. /// - public interface ICommandHandler : IAsyncDisposable, IDisposable + public interface ICommandHandler : IInstrument where TCommand : class, ICommandBase { } diff --git a/src/RapidField.SolidInstruments.Command/ICommandMediator.cs b/src/RapidField.SolidInstruments.Command/ICommandMediator.cs index ead30b9f..ae72b914 100644 --- a/src/RapidField.SolidInstruments.Command/ICommandMediator.cs +++ b/src/RapidField.SolidInstruments.Command/ICommandMediator.cs @@ -2,6 +2,7 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= +using RapidField.SolidInstruments.Core; using System; using System.Threading.Tasks; @@ -10,7 +11,7 @@ namespace RapidField.SolidInstruments.Command /// /// Serves as a dependency resolver and processing intermediary for commands. /// - public interface ICommandMediator : IAsyncDisposable, IDisposable + public interface ICommandMediator : IInstrument { /// /// Processes the specified . diff --git a/src/RapidField.SolidInstruments.InversionOfControl/IDependencyContainer.cs b/src/RapidField.SolidInstruments.InversionOfControl/IDependencyContainer.cs index 0bf55fb3..2b85eaf1 100644 --- a/src/RapidField.SolidInstruments.InversionOfControl/IDependencyContainer.cs +++ b/src/RapidField.SolidInstruments.InversionOfControl/IDependencyContainer.cs @@ -2,6 +2,7 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= +using RapidField.SolidInstruments.Core; using System; namespace RapidField.SolidInstruments.InversionOfControl @@ -9,7 +10,7 @@ namespace RapidField.SolidInstruments.InversionOfControl /// /// Represents an abstraction for a utility that creates, destroys, and manages scoping for dependencies. /// - public interface IDependencyContainer : IAsyncDisposable, IDisposable + public interface IDependencyContainer : IInstrument { /// /// Creates a new initialization and disposal scope for the current . diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageTransport.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageTransport.cs index d5e46825..f60f8c2e 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageTransport.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageTransport.cs @@ -2,6 +2,7 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= +using RapidField.SolidInstruments.Core; using System; using System.Threading.Tasks; @@ -10,14 +11,29 @@ namespace RapidField.SolidInstruments.Messaging.TransportPrimitives /// /// Supports message exchange for a collection of queues and topics. /// - public interface IDurableMessageTransport : IAsyncDisposable, IDisposable + public interface IDurableMessageTransport : IInstrument { /// + /// Asynchronously creates a new queue. /// /// + /// A unique textual path that identifies the new queue. /// /// + /// A task representing the asynchronous operation. /// + /// + /// A + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// Task CreateQueueAsync(String path); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Service/IServiceExecutor.cs b/src/RapidField.SolidInstruments.Service/IServiceExecutor.cs index c6232ab3..984b3ebf 100644 --- a/src/RapidField.SolidInstruments.Service/IServiceExecutor.cs +++ b/src/RapidField.SolidInstruments.Service/IServiceExecutor.cs @@ -2,6 +2,7 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= +using RapidField.SolidInstruments.Core; using System; namespace RapidField.SolidInstruments.Service @@ -9,7 +10,7 @@ namespace RapidField.SolidInstruments.Service /// /// Prepares for and performs execution of a service. /// - public interface IServiceExecutor : IAsyncDisposable, IDisposable + public interface IServiceExecutor : IInstrument { /// /// Begins execution of the service and performs the service operations. From 64082f8e31d781883375f07eee5e2e5fae16f80f Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Sun, 1 Mar 2020 19:30:25 -0600 Subject: [PATCH 13/55] Refactoring messaging paths. --- en-US_User.dic | 22 + .../Concurrency/ConcurrencyControlToken.cs | 25 +- .../AzureServiceBusClientFactory.cs | 30 +- .../IMessageListeningFacade.cs | 22 +- .../IMessageTransmittingFacade.cs | 22 +- .../IMessagingClientFactory.cs | 44 +- .../IMessagingEntityPath.cs | 85 ++ .../MessageListeningFacade.cs | 41 +- .../MessageTransmittingFacade.cs | 41 +- .../MessagingClientFactory.cs | 242 ++--- .../MessagingEntityPath.cs | 930 ++++++++++++++++++ .../MessagingFacade.cs | 2 +- .../DurableMessageQueue.cs | 51 +- .../DurableMessageQueueSnapshot.cs | 4 +- .../IDurableMessageQueue.cs | 4 +- .../Symmetric/CascadingSymmetricKeyTests.cs | 8 +- .../MessagingEntityPathTests.cs | 179 ++++ 17 files changed, 1481 insertions(+), 271 deletions(-) create mode 100644 src/RapidField.SolidInstruments.Messaging/IMessagingEntityPath.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/MessagingEntityPath.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.UnitTests/MessagingEntityPathTests.cs diff --git a/en-US_User.dic b/en-US_User.dic index 4273844f..6c4f066d 100644 --- a/en-US_User.dic +++ b/en-US_User.dic @@ -116,6 +116,18 @@ ioc ipsum js json +labeldelimiterone +labeldelimiterthree +labeldelimitertwo +labelone +labelthree +labeltokendelimiterone +labeltokendelimiterthree +labeltokendelimitertwo +labeltokenone +labeltokenthree +labeltokentwo +labeltwo leanify li loglevel @@ -126,6 +138,10 @@ mcg mensiversary Mergify Merkle +messagetype +messagetypedelimiter +messagetypetoken +messagetypeword minifier minifies Minify @@ -157,6 +173,9 @@ plaintext postfix powershell pre +prefixdelimiter +prefixtoken +prefixword prerelease psake quantized @@ -210,6 +229,9 @@ thumbsdown thumbsup timeofday toc +tokenworddelimiterone +tokenworddelimiterthree +tokenworddelimitertwo tt Twofish uid diff --git a/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControlToken.cs b/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControlToken.cs index d4724f33..ab06303d 100644 --- a/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControlToken.cs +++ b/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControlToken.cs @@ -16,7 +16,7 @@ namespace RapidField.SolidInstruments.Core.Concurrency /// /// Represents exclusive or semi-exclusive control of a resource or block of code by a single thread. /// - public sealed class ConcurrencyControlToken : IComparable, IEquatable, IDisposable + public sealed class ConcurrencyControlToken : IAsyncDisposable, IComparable, IEquatable, IDisposable { /// /// Initializes a new instance of the class. @@ -156,6 +156,21 @@ internal ConcurrencyControlToken(SynchronizationContext context, Thread granteeT /// public static Boolean operator >=(ConcurrencyControlToken a, ConcurrencyControlToken b) => a.CompareTo(b) > -1; + /// + /// Instructs the current to wait for the specified task to complete before releasing + /// control to another thread. + /// + /// + /// An action to wait upon. + /// + /// + /// is . + /// + /// + /// is . + /// + public void AttachTask(Action action) => AttachTask(Task.Factory.StartNew(action.RejectIf().IsNull(nameof(action)))); + /// /// Instructs the current to wait for the specified task to complete before releasing /// control to another thread. @@ -217,6 +232,14 @@ public void Dispose() } } + /// + /// Asynchronously releases all resources consumed by the current . + /// + /// + /// A task representing the asynchronous operation. + /// + public ValueTask DisposeAsync() => new ValueTask(Task.Factory.StartNew(Dispose)); + /// /// Determines whether or not the current is equal to the specified /// . diff --git a/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusClientFactory.cs b/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusClientFactory.cs index bef2d642..0bb860c2 100644 --- a/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusClientFactory.cs +++ b/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusClientFactory.cs @@ -54,7 +54,7 @@ public AzureServiceBusClientFactory(ServiceBusConnection connection) /// /// A new implementation-specific client that facilitates receive operations. /// - protected sealed override IReceiverClient CreateMessageReceiver(ServiceBusConnection connection, MessagingEntityType entityType, String entityPath, String subscriptionName) => entityType switch + protected sealed override IReceiverClient CreateMessageReceiver(ServiceBusConnection connection, MessagingEntityType entityType, IMessagingEntityPath entityPath, String subscriptionName) => entityType switch { MessagingEntityType.Queue => CreateQueueClient(connection, entityPath), MessagingEntityType.Topic => CreateSubscriptionClient(connection, entityPath, subscriptionName), @@ -79,7 +79,7 @@ public AzureServiceBusClientFactory(ServiceBusConnection connection) /// /// A new implementation-specific client that facilitates send operations. /// - protected sealed override ISenderClient CreateMessageSender(ServiceBusConnection connection, MessagingEntityType entityType, String entityPath) => entityType switch + protected sealed override ISenderClient CreateMessageSender(ServiceBusConnection connection, MessagingEntityType entityType, IMessagingEntityPath entityPath) => entityType switch { MessagingEntityType.Queue => CreateQueueClient(connection, entityPath), MessagingEntityType.Topic => CreateTopicClient(connection, entityPath), @@ -113,11 +113,11 @@ public AzureServiceBusClientFactory(ServiceBusConnection connection) /// An exception was raised while creating the client. /// [DebuggerHidden] - private IQueueClient CreateQueueClient(ServiceBusConnection connection, String queuePath) + private IQueueClient CreateQueueClient(ServiceBusConnection connection, IMessagingEntityPath queuePath) where TMessage : class { EnsureQueueExistanceAsync(queuePath).Wait(); - return new QueueClient(connection, queuePath, ReceiveBehavior, RetryBehavior); + return new QueueClient(connection, queuePath.ToString(), ReceiveBehavior, RetryBehavior); } /// @@ -142,11 +142,11 @@ private IQueueClient CreateQueueClient(ServiceBusConnection connection /// An exception was raised while creating the client. /// [DebuggerHidden] - private ISubscriptionClient CreateSubscriptionClient(ServiceBusConnection connection, String topicPath, String subscriptionName) + private ISubscriptionClient CreateSubscriptionClient(ServiceBusConnection connection, IMessagingEntityPath topicPath, String subscriptionName) where TMessage : class { EnsureSubscriptionExistanceAsync(topicPath, subscriptionName).Wait(); - return new SubscriptionClient(connection, topicPath, subscriptionName, ReceiveBehavior, RetryBehavior); + return new SubscriptionClient(connection, topicPath.ToString(), subscriptionName, ReceiveBehavior, RetryBehavior); } /// @@ -168,11 +168,11 @@ private ISubscriptionClient CreateSubscriptionClient(ServiceBusConnect /// An exception was raised while creating the client. /// [DebuggerHidden] - private ITopicClient CreateTopicClient(ServiceBusConnection connection, String topicPath) + private ITopicClient CreateTopicClient(ServiceBusConnection connection, IMessagingEntityPath topicPath) where TMessage : class { EnsureTopicExistanceAsync(topicPath).Wait(); - return new TopicClient(connection, topicPath, RetryBehavior); + return new TopicClient(connection, topicPath.ToString(), RetryBehavior); } /// @@ -185,9 +185,9 @@ private ITopicClient CreateTopicClient(ServiceBusConnection connection /// A task representing the asynchronous operation. /// [DebuggerHidden] - private Task EnsureQueueExistanceAsync(String queuePath) => ManagementClient.QueueExistsAsync(queuePath).ContinueWith(queueExistsTask => + private Task EnsureQueueExistanceAsync(IMessagingEntityPath queuePath) => ManagementClient.QueueExistsAsync(queuePath.ToString()).ContinueWith(queueExistsTask => { - return queueExistsTask.Result ? Task.CompletedTask : ManagementClient.CreateQueueAsync(queuePath); + return queueExistsTask.Result ? Task.CompletedTask : ManagementClient.CreateQueueAsync(queuePath.ToString()); }); /// @@ -203,11 +203,11 @@ private Task EnsureQueueExistanceAsync(String queuePath) => ManagementClient.Que /// A task representing the asynchronous operation. /// [DebuggerHidden] - private Task EnsureSubscriptionExistanceAsync(String topicPath, String subscriptionName) => EnsureTopicExistanceAsync(topicPath).ContinueWith(ensureTopicExistenceTask => + private Task EnsureSubscriptionExistanceAsync(IMessagingEntityPath topicPath, String subscriptionName) => EnsureTopicExistanceAsync(topicPath).ContinueWith(ensureTopicExistenceTask => { - return ManagementClient.SubscriptionExistsAsync(topicPath, subscriptionName).ContinueWith(subscriptionExistsTask => + return ManagementClient.SubscriptionExistsAsync(topicPath.ToString(), subscriptionName).ContinueWith(subscriptionExistsTask => { - return subscriptionExistsTask.Result ? Task.CompletedTask : ManagementClient.CreateSubscriptionAsync(topicPath, subscriptionName); + return subscriptionExistsTask.Result ? Task.CompletedTask : ManagementClient.CreateSubscriptionAsync(topicPath.ToString(), subscriptionName); }); }); @@ -221,9 +221,9 @@ private Task EnsureSubscriptionExistanceAsync(String topicPath, String subscript /// A task representing the asynchronous operation. /// [DebuggerHidden] - private Task EnsureTopicExistanceAsync(String topicPath) => ManagementClient.TopicExistsAsync(topicPath).ContinueWith(topicExistsTask => + private Task EnsureTopicExistanceAsync(IMessagingEntityPath topicPath) => ManagementClient.TopicExistsAsync(topicPath.ToString()).ContinueWith(topicExistsTask => { - return topicExistsTask.Result ? Task.CompletedTask : ManagementClient.CreateTopicAsync(topicPath); + return topicExistsTask.Result ? Task.CompletedTask : ManagementClient.CreateTopicAsync(topicPath.ToString()); }); /// diff --git a/src/RapidField.SolidInstruments.Messaging/IMessageListeningFacade.cs b/src/RapidField.SolidInstruments.Messaging/IMessageListeningFacade.cs index b5d981a2..9d7465cf 100644 --- a/src/RapidField.SolidInstruments.Messaging/IMessageListeningFacade.cs +++ b/src/RapidField.SolidInstruments.Messaging/IMessageListeningFacade.cs @@ -80,12 +80,13 @@ void RegisterQueueMessageHandler(Action messageHandler) /// /// An action that handles a message. /// - /// - /// An ordered collection of non-null, non-empty alphanumeric string tokens from which to construct the path, or - /// to omit path tokens. The default value is . + /// + /// An ordered collection of as many as three non-null, non-empty alphanumeric textual labels to append to the path, or + /// to omit path labels. The default value is . /// /// - /// contains one or more null or empty tokens and/or tokens with non-alphanumeric characters. + /// contains one or more null or empty labels and/or labels with non-alphanumeric characters, + /// or contains more than three elements. /// /// /// is . @@ -96,7 +97,7 @@ void RegisterQueueMessageHandler(Action messageHandler) /// /// The object is disposed. /// - void RegisterQueueMessageHandler(Action messageHandler, IEnumerable pathTokens) + void RegisterQueueMessageHandler(Action messageHandler, IEnumerable pathLabels) where TMessage : class, IMessage; /// @@ -154,12 +155,13 @@ void RegisterTopicMessageHandler(Action messageHandler) /// /// An action that handles a message. /// - /// - /// An ordered collection of non-null, non-empty alphanumeric string tokens from which to construct the path, or - /// to omit path tokens. The default value is . + /// + /// An ordered collection of as many as three non-null, non-empty alphanumeric textual labels to append to the path, or + /// to omit path labels. The default value is . /// /// - /// contains one or more null or empty tokens and/or tokens with non-alphanumeric characters. + /// contains one or more null or empty labels and/or labels with non-alphanumeric characters, + /// or contains more than three elements. /// /// /// is . @@ -170,7 +172,7 @@ void RegisterTopicMessageHandler(Action messageHandler) /// /// The object is disposed. /// - void RegisterTopicMessageHandler(Action messageHandler, IEnumerable pathTokens) + void RegisterTopicMessageHandler(Action messageHandler, IEnumerable pathLabels) where TMessage : class, IMessage; /// diff --git a/src/RapidField.SolidInstruments.Messaging/IMessageTransmittingFacade.cs b/src/RapidField.SolidInstruments.Messaging/IMessageTransmittingFacade.cs index 85f75374..ef606806 100644 --- a/src/RapidField.SolidInstruments.Messaging/IMessageTransmittingFacade.cs +++ b/src/RapidField.SolidInstruments.Messaging/IMessageTransmittingFacade.cs @@ -39,15 +39,16 @@ public interface IMessageTransmittingFacade : IMessagingFacade /// /// The message to transmit. /// - /// - /// An ordered collection of non-null, non-empty alphanumeric string tokens from which to construct the path, or - /// to omit path tokens. The default value is . + /// + /// An ordered collection of as many as three non-null, non-empty alphanumeric textual labels to append to the path, or + /// to omit path labels. The default value is . /// /// /// A task representing the asynchronous operation. /// /// - /// contains one or more null or empty tokens and/or tokens with non-alphanumeric characters. + /// contains one or more null or empty labels and/or labels with non-alphanumeric characters, + /// or contains more than three elements. /// /// /// is . @@ -58,7 +59,7 @@ public interface IMessageTransmittingFacade : IMessagingFacade /// /// The object is disposed. /// - Task TransmitToQueueAsync(TMessage message, IEnumerable pathTokens) + Task TransmitToQueueAsync(TMessage message, IEnumerable pathLabels) where TMessage : class, IMessageBase; /// @@ -94,15 +95,16 @@ Task TransmitToQueueAsync(TMessage message) /// /// The message to transmit. /// - /// - /// An ordered collection of non-null, non-empty alphanumeric string tokens from which to construct the path, or - /// to omit path tokens. The default value is . + /// + /// An ordered collection of as many as three non-null, non-empty alphanumeric textual labels to append to the path, or + /// to omit path labels. The default value is . /// /// /// A task representing the asynchronous operation. /// /// - /// contains one or more null or empty tokens and/or tokens with non-alphanumeric characters. + /// contains one or more null or empty labels and/or labels with non-alphanumeric characters, + /// or contains more than three elements. /// /// /// is . @@ -113,7 +115,7 @@ Task TransmitToQueueAsync(TMessage message) /// /// The object is disposed. /// - Task TransmitToTopicAsync(TMessage message, IEnumerable pathTokens) + Task TransmitToTopicAsync(TMessage message, IEnumerable pathLabels) where TMessage : class, IMessageBase; /// diff --git a/src/RapidField.SolidInstruments.Messaging/IMessagingClientFactory.cs b/src/RapidField.SolidInstruments.Messaging/IMessagingClientFactory.cs index a05afb8e..a77fcab7 100644 --- a/src/RapidField.SolidInstruments.Messaging/IMessagingClientFactory.cs +++ b/src/RapidField.SolidInstruments.Messaging/IMessagingClientFactory.cs @@ -47,15 +47,16 @@ TReceiver GetQueueReceiver() /// /// The type of the message that the client handles. /// - /// - /// An ordered collection of non-null, non-empty alphanumeric string tokens from which to construct the path, or - /// to omit path tokens. The default value is . + /// + /// An ordered collection of as many as three non-null, non-empty alphanumeric textual labels to append to the path, or + /// to omit path labels. The default value is . /// /// /// The managed, implementation-specific message receiver. /// /// - /// contains one or more null or empty tokens and/or tokens with non-alphanumeric characters. + /// contains one or more null or empty labels and/or labels with non-alphanumeric characters, + /// or contains more than three elements. /// /// /// An exception was raised while creating the client. @@ -63,7 +64,7 @@ TReceiver GetQueueReceiver() /// /// The object is disposed. /// - TReceiver GetQueueReceiver(IEnumerable pathTokens) + TReceiver GetQueueReceiver(IEnumerable pathLabels) where TMessage : class; /// @@ -90,15 +91,16 @@ TSender GetQueueSender() /// /// The type of the message that the client handles. /// - /// - /// An ordered collection of non-null, non-empty alphanumeric string tokens from which to construct the path, or - /// to omit path tokens. The default value is . + /// + /// An ordered collection of as many as three non-null, non-empty alphanumeric textual labels to append to the path, or + /// to omit path labels. The default value is . /// /// /// The managed, implementation-specific message sender. /// /// - /// contains one or more null or empty tokens and/or tokens with non-alphanumeric characters. + /// contains one or more null or empty labels and/or labels with non-alphanumeric characters, + /// or contains more than three elements. /// /// /// An exception was raised while creating the client. @@ -106,7 +108,7 @@ TSender GetQueueSender() /// /// The object is disposed. /// - TSender GetQueueSender(IEnumerable pathTokens) + TSender GetQueueSender(IEnumerable pathLabels) where TMessage : class; /// @@ -145,9 +147,9 @@ TReceiver GetTopicReceiver(String receiverIdentifier) /// /// A unique textual identifier for the message receiver. /// - /// - /// An ordered collection of non-null, non-empty alphanumeric string tokens from which to construct the path, or - /// to omit path tokens. The default value is . + /// + /// An ordered collection of as many as three non-null, non-empty alphanumeric textual labels to append to the path, or + /// to omit path labels. The default value is . /// /// /// The managed, implementation-specific message receiver. @@ -156,7 +158,8 @@ TReceiver GetTopicReceiver(String receiverIdentifier) /// is empty. /// /// - /// contains one or more null or empty tokens and/or tokens with non-alphanumeric characters. + /// contains one or more null or empty labels and/or labels with non-alphanumeric characters, + /// or contains more than three elements. /// /// /// is . @@ -167,7 +170,7 @@ TReceiver GetTopicReceiver(String receiverIdentifier) /// /// The object is disposed. /// - TReceiver GetTopicReceiver(String receiverIdentifier, IEnumerable pathTokens) + TReceiver GetTopicReceiver(String receiverIdentifier, IEnumerable pathLabels) where TMessage : class; /// @@ -194,15 +197,16 @@ TSender GetTopicSender() /// /// The type of the message that the client handles. /// - /// - /// An ordered collection of non-null, non-empty alphanumeric string tokens from which to construct the path, or - /// to omit path tokens. The default value is . + /// + /// An ordered collection of as many as three non-null, non-empty alphanumeric textual labels to append to the path, or + /// to omit path labels. The default value is . /// /// /// The managed, implementation-specific message sender. /// /// - /// contains one or more null or empty tokens and/or tokens with non-alphanumeric characters. + /// contains one or more null or empty labels and/or labels with non-alphanumeric characters, + /// or contains more than three elements. /// /// /// An exception was raised while creating the client. @@ -210,7 +214,7 @@ TSender GetTopicSender() /// /// The object is disposed. /// - TSender GetTopicSender(IEnumerable pathTokens) + TSender GetTopicSender(IEnumerable pathLabels) where TMessage : class; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/IMessagingEntityPath.cs b/src/RapidField.SolidInstruments.Messaging/IMessagingEntityPath.cs new file mode 100644 index 00000000..72a4e216 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/IMessagingEntityPath.cs @@ -0,0 +1,85 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using System; + +namespace RapidField.SolidInstruments.Messaging +{ + /// + /// Represents a textual path that defines a route to a messaging entity. + /// + public interface IMessagingEntityPath : IComparable, IEquatable + { + /// + /// Gets or sets the first label for the current , or if there is + /// not a first label. + /// + /// + /// The specified value is invalid. + /// + String LabelOne + { + get; + set; + } + + /// + /// Gets or sets the third label for the current , or if there is + /// not a third label. + /// + /// + /// The specified value is invalid. + /// + String LabelThree + { + get; + set; + } + + /// + /// Gets or sets the second label for the current , or if there + /// is not a second label. + /// + /// + /// The specified value is invalid. + /// + String LabelTwo + { + get; + set; + } + + /// + /// Gets or sets the message type for the current . + /// + /// + /// The specified value is empty. + /// + /// + /// The specified value is . + /// + /// + /// The specified value is invalid. + /// + String MessageType + { + get; + set; + } + + /// + /// Gets or sets the prefix for the current , or if there is not + /// a prefix. + /// + /// + /// The specified value is invalid. + /// + String Prefix + { + get; + set; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/MessageListeningFacade.cs b/src/RapidField.SolidInstruments.Messaging/MessageListeningFacade.cs index 3c987271..ea4b430b 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessageListeningFacade.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessageListeningFacade.cs @@ -244,12 +244,13 @@ public void RegisterQueueMessageHandler(Action messageHandle /// /// An action that handles a message. /// - /// - /// An ordered collection of non-null, non-empty alphanumeric string tokens from which to construct the path, or - /// to omit path tokens. The default value is . + /// + /// An ordered collection of as many as three non-null, non-empty alphanumeric textual labels to append to the path, or + /// to omit path labels. The default value is . /// /// - /// contains one or more null or empty tokens and/or tokens with non-alphanumeric characters. + /// contains one or more null or empty labels and/or labels with non-alphanumeric characters, + /// or contains more than three elements. /// /// /// is . @@ -260,8 +261,8 @@ public void RegisterQueueMessageHandler(Action messageHandle /// /// The object is disposed. /// - public void RegisterQueueMessageHandler(Action messageHandler, IEnumerable pathTokens) - where TMessage : class, IMessage => RegisterMessageHandler(messageHandler, pathTokens, MessagingEntityType.Queue); + public void RegisterQueueMessageHandler(Action messageHandler, IEnumerable pathLabels) + where TMessage : class, IMessage => RegisterMessageHandler(messageHandler, pathLabels, MessagingEntityType.Queue); /// /// Registers the specified request message handler with the bus. @@ -318,12 +319,13 @@ public void RegisterTopicMessageHandler(Action messageHandle /// /// An action that handles a message. /// - /// - /// An ordered collection of non-null, non-empty alphanumeric string tokens from which to construct the path, or - /// to omit path tokens. The default value is . + /// + /// An ordered collection of as many as three non-null, non-empty alphanumeric textual labels to append to the path, or + /// to omit path labels. The default value is . /// /// - /// contains one or more null or empty tokens and/or tokens with non-alphanumeric characters. + /// contains one or more null or empty labels and/or labels with non-alphanumeric characters, + /// or contains more than three elements. /// /// /// is . @@ -334,8 +336,8 @@ public void RegisterTopicMessageHandler(Action messageHandle /// /// The object is disposed. /// - public void RegisterTopicMessageHandler(Action messageHandler, IEnumerable pathTokens) - where TMessage : class, IMessage => RegisterMessageHandler(messageHandler, pathTokens, MessagingEntityType.Topic); + public void RegisterTopicMessageHandler(Action messageHandler, IEnumerable pathLabels) + where TMessage : class, IMessage => RegisterMessageHandler(messageHandler, pathLabels, MessagingEntityType.Topic); /// /// Asynchronously performs the specified action for the specified message and, upon failure, applies the execution policy. @@ -383,15 +385,16 @@ internal Task HandleMessageAsync(Action messageHandler, TMes /// /// An action that handles a message. /// - /// - /// An ordered collection of non-null, non-empty alphanumeric string tokens from which to construct the path, or - /// to omit path tokens. The default value is . + /// + /// An ordered collection of as many as three non-null, non-empty alphanumeric textual labels to append to the path, or + /// to omit path labels. The default value is . /// /// /// The targeted entity type. /// /// - /// contains one or more null or empty tokens and/or tokens with non-alphanumeric characters. + /// contains one or more null or empty labels and/or labels with non-alphanumeric characters, + /// or contains more than three elements. /// /// /// is . @@ -403,7 +406,7 @@ internal Task HandleMessageAsync(Action messageHandler, TMes /// The object is disposed. /// [DebuggerHidden] - internal void RegisterMessageHandler(Action messageHandler, IEnumerable pathTokens, MessagingEntityType entityType) + internal void RegisterMessageHandler(Action messageHandler, IEnumerable pathLabels, MessagingEntityType entityType) where TMessage : class, IMessage { messageHandler = messageHandler.RejectIf().IsNull(nameof(messageHandler)).TargetArgument; @@ -423,8 +426,8 @@ internal void RegisterMessageHandler(Action messageHandler, var receiveClient = entityType switch { - MessagingEntityType.Queue => ClientFactory.GetQueueReceiver(pathTokens), - MessagingEntityType.Topic => ClientFactory.GetTopicReceiver(Identifier, pathTokens), + MessagingEntityType.Queue => ClientFactory.GetQueueReceiver(pathLabels), + MessagingEntityType.Topic => ClientFactory.GetTopicReceiver(Identifier, pathLabels), _ => throw new UnsupportedSpecificationException($"The specified messaging entity type, {entityType}, is not supported.") }; diff --git a/src/RapidField.SolidInstruments.Messaging/MessageTransmittingFacade.cs b/src/RapidField.SolidInstruments.Messaging/MessageTransmittingFacade.cs index 85423c9e..1e94e96e 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessageTransmittingFacade.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessageTransmittingFacade.cs @@ -83,15 +83,16 @@ public Task TransmitToQueueAsync(TMessage message) /// /// The message to transmit. /// - /// - /// An ordered collection of non-null, non-empty alphanumeric string tokens from which to construct the path, or - /// to omit path tokens. The default value is . + /// + /// An ordered collection of as many as three non-null, non-empty alphanumeric textual labels to append to the path, or + /// to omit path labels. The default value is . /// /// /// A task representing the asynchronous operation. /// /// - /// contains one or more null or empty tokens and/or tokens with non-alphanumeric characters. + /// contains one or more null or empty labels and/or labels with non-alphanumeric characters, + /// or contains more than three elements. /// /// /// is . @@ -102,8 +103,8 @@ public Task TransmitToQueueAsync(TMessage message) /// /// The object is disposed. /// - public Task TransmitToQueueAsync(TMessage message, IEnumerable pathTokens) - where TMessage : class, IMessageBase => TransmitAsync(message, pathTokens, MessagingEntityType.Queue); + public Task TransmitToQueueAsync(TMessage message, IEnumerable pathLabels) + where TMessage : class, IMessageBase => TransmitAsync(message, pathLabels, MessagingEntityType.Queue); /// /// Asynchronously transmits the specified message to a topic. @@ -138,15 +139,16 @@ public Task TransmitToTopicAsync(TMessage message) /// /// The message to transmit. /// - /// - /// An ordered collection of non-null, non-empty alphanumeric string tokens from which to construct the path, or - /// to omit path tokens. The default value is . + /// + /// An ordered collection of as many as three non-null, non-empty alphanumeric textual labels to append to the path, or + /// to omit path labels. The default value is . /// /// /// A task representing the asynchronous operation. /// /// - /// contains one or more null or empty tokens and/or tokens with non-alphanumeric characters. + /// contains one or more null or empty labels and/or labels with non-alphanumeric characters, + /// or contains more than three elements. /// /// /// is . @@ -157,8 +159,8 @@ public Task TransmitToTopicAsync(TMessage message) /// /// The object is disposed. /// - public Task TransmitToTopicAsync(TMessage message, IEnumerable pathTokens) - where TMessage : class, IMessageBase => TransmitAsync(message, pathTokens, MessagingEntityType.Topic); + public Task TransmitToTopicAsync(TMessage message, IEnumerable pathLabels) + where TMessage : class, IMessageBase => TransmitAsync(message, pathLabels, MessagingEntityType.Topic); /// /// Asynchronously transmits the specified message to a bus. @@ -169,9 +171,9 @@ public Task TransmitToTopicAsync(TMessage message, IEnumerable /// /// The message to transmit. /// - /// - /// An ordered collection of non-null, non-empty alphanumeric string tokens from which to construct the path, or - /// to omit path tokens. The default value is . + /// + /// An ordered collection of as many as three non-null, non-empty alphanumeric textual labels to append to the path, or + /// to omit path labels. The default value is . /// /// /// The targeted entity type. @@ -180,7 +182,8 @@ public Task TransmitToTopicAsync(TMessage message, IEnumerable /// A task representing the asynchronous operation. /// /// - /// contains one or more null or empty tokens and/or tokens with non-alphanumeric characters. + /// contains one or more null or empty labels and/or labels with non-alphanumeric characters, + /// or contains more than three elements. /// /// /// is . @@ -195,7 +198,7 @@ public Task TransmitToTopicAsync(TMessage message, IEnumerable /// The object is disposed. /// [DebuggerHidden] - internal Task TransmitAsync(TMessage message, IEnumerable pathTokens, MessagingEntityType entityType) + internal Task TransmitAsync(TMessage message, IEnumerable pathLabels, MessagingEntityType entityType) where TMessage : class, IMessageBase { message = message.RejectIf().IsNull(nameof(message)).TargetArgument; @@ -208,8 +211,8 @@ internal Task TransmitAsync(TMessage message, IEnumerable path sendClient = entityType switch { - MessagingEntityType.Queue => ClientFactory.GetQueueSender(pathTokens), - MessagingEntityType.Topic => ClientFactory.GetTopicSender(pathTokens), + MessagingEntityType.Queue => ClientFactory.GetQueueSender(pathLabels), + MessagingEntityType.Topic => ClientFactory.GetTopicSender(pathLabels), _ => throw new UnsupportedSpecificationException($"The specified messaging entity type, {entityType}, is not supported.") }; diff --git a/src/RapidField.SolidInstruments.Messaging/MessagingClientFactory.cs b/src/RapidField.SolidInstruments.Messaging/MessagingClientFactory.cs index d553c3ad..30488a05 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessagingClientFactory.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessagingClientFactory.cs @@ -5,10 +5,11 @@ using RapidField.SolidInstruments.Core; using RapidField.SolidInstruments.Core.ArgumentValidation; using RapidField.SolidInstruments.Core.Extensions; +using RapidField.SolidInstruments.TextEncoding; using System; using System.Collections.Generic; using System.Diagnostics; -using System.Text; +using System.Linq; using System.Threading; namespace RapidField.SolidInstruments.Messaging @@ -67,7 +68,7 @@ protected MessagingClientFactory(TConnection connection) /// The object is disposed. /// public TReceiver GetQueueReceiver() - where TMessage : class => GetQueueReceiver(pathTokens: null); + where TMessage : class => GetQueueReceiver(pathLabels: null); /// /// Gets a shared, managed, implementation-specific message receiver for a type-defined queue. @@ -75,15 +76,16 @@ public TReceiver GetQueueReceiver() /// /// The type of the message that the client handles. /// - /// - /// An ordered collection of non-null, non-empty alphanumeric string tokens from which to construct the path, or - /// to omit path tokens. The default value is . + /// + /// An ordered collection of as many as three non-null, non-empty alphanumeric textual labels to append to the path, or + /// to omit path labels. The default value is . /// /// /// The managed, implementation-specific message receiver. /// /// - /// contains one or more null or empty tokens and/or tokens with non-alphanumeric characters. + /// contains one or more null or empty labels and/or labels with non-alphanumeric characters, + /// or contains more than three elements. /// /// /// An exception was raised while creating the client. @@ -91,8 +93,8 @@ public TReceiver GetQueueReceiver() /// /// The object is disposed. /// - public TReceiver GetQueueReceiver(IEnumerable pathTokens) - where TMessage : class => GetMessageReceiver(MessagingEntityType.Queue, null, pathTokens); + public TReceiver GetQueueReceiver(IEnumerable pathLabels) + where TMessage : class => GetMessageReceiver(MessagingEntityType.Queue, null, pathLabels); /// /// Gets a shared, managed, implementation-specific message sender for a type-defined queue. @@ -110,7 +112,7 @@ public TReceiver GetQueueReceiver(IEnumerable pathTokens) /// The object is disposed. /// public TSender GetQueueSender() - where TMessage : class => GetQueueSender(pathTokens: null); + where TMessage : class => GetQueueSender(pathLabels: null); /// /// Gets a shared, managed, implementation-specific message sender for a type-defined queue. @@ -118,15 +120,16 @@ public TSender GetQueueSender() /// /// The type of the message that the client handles. /// - /// - /// An ordered collection of non-null, non-empty alphanumeric string tokens from which to construct the path, or - /// to omit path tokens. The default value is . + /// + /// An ordered collection of as many as three non-null, non-empty alphanumeric textual labels to append to the path, or + /// to omit path labels. The default value is . /// /// /// The managed, implementation-specific message sender. /// /// - /// contains one or more null or empty tokens and/or tokens with non-alphanumeric characters. + /// contains one or more null or empty labels and/or labels with non-alphanumeric characters, + /// or contains more than three elements. /// /// /// An exception was raised while creating the client. @@ -134,8 +137,8 @@ public TSender GetQueueSender() /// /// The object is disposed. /// - public TSender GetQueueSender(IEnumerable pathTokens) - where TMessage : class => GetMessageSender(MessagingEntityType.Queue, pathTokens); + public TSender GetQueueSender(IEnumerable pathLabels) + where TMessage : class => GetMessageSender(MessagingEntityType.Queue, pathLabels); /// /// Gets a shared, managed, implementation-specific message receiver for a type-defined topic. @@ -162,7 +165,7 @@ public TSender GetQueueSender(IEnumerable pathTokens) /// The object is disposed. /// public TReceiver GetTopicReceiver(String receiverIdentifier) - where TMessage : class => GetTopicReceiver(receiverIdentifier, pathTokens: null); + where TMessage : class => GetTopicReceiver(receiverIdentifier, pathLabels: null); /// /// Gets a shared, managed, implementation-specific message receiver for a type-defined topic. @@ -173,9 +176,9 @@ public TReceiver GetTopicReceiver(String receiverIdentifier) /// /// A unique textual identifier for the message receiver, which is appended to the path. /// - /// - /// An ordered collection of non-null, non-empty alphanumeric string tokens from which to construct the path, or - /// to omit path tokens. The default value is . + /// + /// An ordered collection of as many as three non-null, non-empty alphanumeric textual labels to append to the path, or + /// to omit path labels. The default value is . /// /// /// The managed, implementation-specific message receiver. @@ -184,7 +187,8 @@ public TReceiver GetTopicReceiver(String receiverIdentifier) /// is empty. /// /// - /// contains one or more null or empty tokens and/or tokens with non-alphanumeric characters. + /// contains one or more null or empty labels and/or labels with non-alphanumeric characters, + /// or contains more than three elements. /// /// /// is . @@ -195,8 +199,8 @@ public TReceiver GetTopicReceiver(String receiverIdentifier) /// /// The object is disposed. /// - public TReceiver GetTopicReceiver(String receiverIdentifier, IEnumerable pathTokens) - where TMessage : class => GetMessageReceiver(MessagingEntityType.Topic, receiverIdentifier.RejectIf().IsNullOrEmpty(nameof(receiverIdentifier)), pathTokens); + public TReceiver GetTopicReceiver(String receiverIdentifier, IEnumerable pathLabels) + where TMessage : class => GetMessageReceiver(MessagingEntityType.Topic, receiverIdentifier.RejectIf().IsNullOrEmpty(nameof(receiverIdentifier)), pathLabels); /// /// Gets a shared, managed, implementation-specific message sender for a type-defined topic. @@ -214,7 +218,7 @@ public TReceiver GetTopicReceiver(String receiverIdentifier, IEnumerab /// The object is disposed. /// public TSender GetTopicSender() - where TMessage : class => GetTopicSender(pathTokens: null); + where TMessage : class => GetTopicSender(pathLabels: null); /// /// Gets a shared, managed, implementation-specific message sender for a type-defined topic. @@ -222,15 +226,16 @@ public TSender GetTopicSender() /// /// The type of the message that the client handles. /// - /// - /// An ordered collection of non-null, non-empty alphanumeric string tokens from which to construct the path, or - /// to omit path tokens. The default value is . + /// + /// An ordered collection of as many as three non-null, non-empty alphanumeric textual labels to append to the path, or + /// to omit path labels. The default value is . /// /// /// The managed, implementation-specific message sender. /// /// - /// contains one or more null or empty tokens and/or tokens with non-alphanumeric characters. + /// contains one or more null or empty labels and/or labels with non-alphanumeric characters, + /// or contains more than three elements. /// /// /// An exception was raised while creating the client. @@ -238,8 +243,8 @@ public TSender GetTopicSender() /// /// The object is disposed. /// - public TSender GetTopicSender(IEnumerable pathTokens) - where TMessage : class => GetMessageSender(MessagingEntityType.Topic, pathTokens); + public TSender GetTopicSender(IEnumerable pathLabels) + where TMessage : class => GetMessageSender(MessagingEntityType.Topic, pathLabels); /// /// Creates a new implementation-specific client that facilitates receive operations. @@ -262,7 +267,7 @@ public TSender GetTopicSender(IEnumerable pathTokens) /// /// A new implementation-specific client that facilitates receive operations. /// - protected abstract TReceiver CreateMessageReceiver(TConnection connection, MessagingEntityType entityType, String entityPath, String subscriptionName) + protected abstract TReceiver CreateMessageReceiver(TConnection connection, MessagingEntityType entityType, IMessagingEntityPath entityPath, String subscriptionName) where TMessage : class; /// @@ -283,7 +288,7 @@ protected abstract TReceiver CreateMessageReceiver(TConnection connect /// /// A new implementation-specific client that facilitates send operations. /// - protected abstract TSender CreateMessageSender(TConnection connection, MessagingEntityType entityType, String entityPath) + protected abstract TSender CreateMessageSender(TConnection connection, MessagingEntityType entityType, IMessagingEntityPath entityPath) where TMessage : class; /// @@ -296,83 +301,66 @@ protected abstract TSender CreateMessageSender(TConnection connection, protected override void Dispose(Boolean disposing) => base.Dispose(disposing); /// - /// Returns an entity path for the specified entity type and message type. + /// Returns an entity path for the specified message type. /// - /// + /// + /// An alphanumeric path prefix, or to omit a prefix. + /// + /// /// The type of the message. - /// - /// - /// The type of the entity. /// - /// - /// An ordered collection of non-null, non-empty alphanumeric string tokens from which to construct the path, or - /// to omit path tokens. The default value is . + /// + /// An ordered collection of as many as three non-null, non-empty alphanumeric textual labels to append to the path, or + /// to omit path labels. The default value is . /// /// - /// An entity path for the specified entity type and message type combination. + /// An entity path for the specified message type. /// /// - /// contains one or more null or empty tokens and/or tokens with non-alphanumeric characters. + /// contains one or more null or empty labels and/or labels with non-alphanumeric characters, + /// or contains more than three elements. /// [DebuggerHidden] - private String GetEntityPath(MessagingEntityType entityType, IEnumerable pathTokens) - where TMessage : class => entityType switch + private static IMessagingEntityPath GetEntityPath(String pathPrefix, Type messageType, IEnumerable pathLabels) + { + try { - MessagingEntityType.Queue => GetQueuePath(pathTokens), - MessagingEntityType.Topic => GetTopicPath(pathTokens), - _ => throw new UnsupportedSpecificationException($"The specified entity type, {entityType}, is not supported.") - }; + return new MessagingEntityPath(messageType, pathPrefix, pathLabels?.ToArray() ?? Array.Empty()); + } + catch (Exception exception) + { + throw new ArgumentException("The specified path label information is invalid. See inner exception.", nameof(pathLabels), exception); + } + } /// - /// Returns an entity path for the specified message type. + /// Returns an entity path for the specified entity type and message type. /// - /// - /// A lower-case alphabetic path prefix, or to omit a prefix. - /// - /// + /// /// The type of the message. + /// + /// + /// The type of the entity. /// - /// - /// An ordered collection of non-null, non-empty alphanumeric string tokens from which to construct the path, or - /// to omit path tokens. The default value is . + /// + /// An ordered collection of as many as three non-null, non-empty alphanumeric textual labels to append to the path, or + /// to omit path labels. The default value is . /// /// - /// An entity path for the specified message type. + /// An entity path for the specified entity type and message type combination. /// /// - /// contains one or more null or empty tokens and/or tokens with non-alphanumeric characters. + /// contains one or more null or empty labels and/or labels with non-alphanumeric characters, + /// or contains more than three elements. /// [DebuggerHidden] - private String GetEntityPath(String pathPrefix, Type messageType, IEnumerable pathTokens) - { - var messageTypeName = messageType.Name.ToLower(); - var processedPathPrefix = pathPrefix.IsNullOrEmpty() ? String.Empty : $"{pathPrefix}{EntityPathDelimitingCharacter}"; - var processedMessageTypeName = messageTypeName.EndsWith(TrimmedMessageTypeNamePostfix) ? messageTypeName.Substring(0, (messageTypeName.Length - TrimmedMessageTypeNamePostfix.Length)) : messageTypeName; - var rootPath = $"{processedPathPrefix}{processedMessageTypeName}"; - - if (pathTokens.IsNullOrEmpty()) - { - return rootPath; - } - - var pathBuilder = new StringBuilder(rootPath); - - foreach (var pathToken in pathTokens) + private IMessagingEntityPath GetEntityPath(MessagingEntityType entityType, IEnumerable pathLabels) + where TMessage : class => entityType switch { - if (pathToken.IsNullOrEmpty()) - { - throw new ArgumentException("One of the specified tokens is null or empty.", nameof(pathTokens)); - } - else if (pathToken.MatchesRegularExpression(FullyAlphanumericRegularExpression) == false) - { - throw new ArgumentException("One of the specified tokens contains non-alphanumeric characters.", nameof(pathTokens)); - } - - pathBuilder.Append($"{EntityPathDelimitingCharacter}{pathToken.ToLower()}"); - } - - return pathBuilder.ToString(); - } + MessagingEntityType.Queue => GetQueuePath(pathLabels), + MessagingEntityType.Topic => GetTopicPath(pathLabels), + _ => throw new UnsupportedSpecificationException($"The specified entity type, {entityType}, is not supported.") + }; /// /// Gets a shared, managed, implementation-specific message receiver. @@ -386,15 +374,16 @@ private String GetEntityPath(String pathPrefix, Type messageType, IEnumerable /// A unique textual identifier for the message receiver, or if the receiver is unspecified. /// - /// - /// An ordered collection of non-null, non-empty alphanumeric string tokens from which to construct the path, or - /// to omit path tokens. The default value is . + /// + /// An ordered collection of as many as three non-null, non-empty alphanumeric textual labels to append to the path, or + /// to omit path labels. The default value is . /// /// /// The managed, implementation-specific message receiver. /// /// - /// contains one or more null or empty tokens and/or tokens with non-alphanumeric characters. + /// contains one or more null or empty labels and/or labels with non-alphanumeric characters, + /// or contains more than three elements. /// /// /// An exception was raised while creating the client. @@ -403,24 +392,25 @@ private String GetEntityPath(String pathPrefix, Type messageType, IEnumerable [DebuggerHidden] - private TReceiver GetMessageReceiver(MessagingEntityType entityType, String receiverIdentifier, IEnumerable pathTokens) + private TReceiver GetMessageReceiver(MessagingEntityType entityType, String receiverIdentifier, IEnumerable pathLabels) where TMessage : class { - var entityPath = GetEntityPath(entityType.RejectIf().IsEqualToValue(MessagingEntityType.Unspecified, nameof(entityType)), pathTokens); + var entityPath = GetEntityPath(entityType.RejectIf().IsEqualToValue(MessagingEntityType.Unspecified, nameof(entityType)), pathLabels); using (var controlToken = StateControl.Enter()) { RejectIfDisposed(); - if (MessageReceivers.TryGetValue(entityPath, out var receiver)) + if (MessageReceivers.TryGetValue(entityPath.ToString(), out var receiver)) { return receiver; } try { - var processedSubscriptionNamePrefix = SubscriptionNamePrefix.IsNullOrEmpty() ? String.Empty : $"{SubscriptionNamePrefix}{EntityPathDelimitingCharacter}"; - var subscriptionName = receiverIdentifier.IsNullOrEmpty() ? null : $"{processedSubscriptionNamePrefix}{entityPath}{EntityPathDelimitingCharacter}{receiverIdentifier}"; + var subscriptionNamePrefix = SubscriptionNamePrefix.IsNullOrEmpty() ? String.Empty : $"{SubscriptionNamePrefix}{MessagingEntityPath.DelimitingCharacterForPrefix}"; + var entityPathHashSuffix = $"{MessagingEntityPath.DelimitingCharacterForLabelToken}{new ZBase32Encoding().GetString(entityPath.GetHashCode().ToByteArray())}"; + var subscriptionName = receiverIdentifier.IsNullOrEmpty() ? null : $"{subscriptionNamePrefix}{receiverIdentifier}{entityPathHashSuffix}"; receiver = CreateMessageReceiver(Connection, entityType, entityPath, subscriptionName); } catch (Exception exception) @@ -428,7 +418,7 @@ private TReceiver GetMessageReceiver(MessagingEntityType entityType, S throw new MessageListeningException(typeof(TMessage), exception); } - MessageReceivers.Add(entityPath, receiver); + MessageReceivers.Add(entityPath.ToString(), receiver); return receiver; } } @@ -442,15 +432,16 @@ private TReceiver GetMessageReceiver(MessagingEntityType entityType, S /// /// The type of the entity. /// - /// - /// An ordered collection of non-null, non-empty alphanumeric string tokens from which to construct the path, or - /// to omit path tokens. The default value is . + /// + /// An ordered collection of as many as three non-null, non-empty alphanumeric textual labels to append to the path, or + /// to omit path labels. The default value is . /// /// /// The managed, implementation-specific message sender. /// /// - /// contains one or more null or empty tokens and/or tokens with non-alphanumeric characters. + /// contains one or more null or empty labels and/or labels with non-alphanumeric characters, + /// or contains more than three elements. /// /// /// An exception was raised while creating the client. @@ -459,16 +450,16 @@ private TReceiver GetMessageReceiver(MessagingEntityType entityType, S /// The object is disposed. /// [DebuggerHidden] - private TSender GetMessageSender(MessagingEntityType entityType, IEnumerable pathTokens) + private TSender GetMessageSender(MessagingEntityType entityType, IEnumerable pathLabels) where TMessage : class { - var entityPath = GetEntityPath(entityType.RejectIf().IsEqualToValue(MessagingEntityType.Unspecified, nameof(entityType)), pathTokens); + var entityPath = GetEntityPath(entityType.RejectIf().IsEqualToValue(MessagingEntityType.Unspecified, nameof(entityType)), pathLabels); using (var controlToken = StateControl.Enter()) { RejectIfDisposed(); - if (MessageSenders.TryGetValue(entityPath, out var sender)) + if (MessageSenders.TryGetValue(entityPath.ToString(), out var sender)) { return sender; } @@ -482,7 +473,7 @@ private TSender GetMessageSender(MessagingEntityType entityType, IEnum throw new MessageTransmissionException(typeof(TMessage), exception); } - MessageSenders.Add(entityPath, sender); + MessageSenders.Add(entityPath.ToString(), sender); return sender; } } @@ -493,19 +484,20 @@ private TSender GetMessageSender(MessagingEntityType entityType, IEnum /// /// The type of the message. /// - /// - /// An ordered collection of non-null, non-empty alphanumeric string tokens from which to construct the path, or - /// to omit path tokens. The default value is . + /// + /// An ordered collection of as many as three non-null, non-empty alphanumeric textual labels to append to the path, or + /// to omit path labels. The default value is . /// /// /// A queue entity path for the specified message type. /// /// - /// contains one or more null or empty tokens and/or tokens with non-alphanumeric characters. + /// contains one or more null or empty labels and/or labels with non-alphanumeric characters, + /// or contains more than three elements. /// [DebuggerHidden] - private String GetQueuePath(IEnumerable pathTokens) - where TMessage : class => GetEntityPath(EntityPathQueuePrefix, typeof(TMessage), pathTokens); + private IMessagingEntityPath GetQueuePath(IEnumerable pathLabels) + where TMessage : class => GetEntityPath(EntityPathQueuePrefix, typeof(TMessage), pathLabels); /// /// Returns a topic entity path for the specified message type. @@ -513,24 +505,20 @@ private String GetQueuePath(IEnumerable pathTokens) /// /// The type of the message. /// - /// - /// An ordered collection of non-null, non-empty alphanumeric string tokens from which to construct the path, or - /// to omit path tokens. The default value is . + /// + /// An ordered collection of as many as three non-null, non-empty alphanumeric textual labels to append to the path, or + /// to omit path labels. The default value is . /// /// /// A topic entity path for the specified message type. /// /// - /// contains one or more null or empty tokens and/or tokens with non-alphanumeric characters. + /// contains one or more null or empty labels and/or labels with non-alphanumeric characters, + /// or contains more than three elements. /// [DebuggerHidden] - private String GetTopicPath(IEnumerable pathTokens) - where TMessage : class => GetEntityPath(EntityPathTopicPrefix, typeof(TMessage), pathTokens); - - /// - /// Gets a character that is used to separate tokens within an entity path. - /// - protected virtual Char EntityPathDelimitingCharacter => '-'; + private IMessagingEntityPath GetTopicPath(IEnumerable pathLabels) + where TMessage : class => GetEntityPath(EntityPathTopicPrefix, typeof(TMessage), pathLabels); /// /// Gets an entity path prefix for queues. @@ -559,18 +547,6 @@ private String GetTopicPath(IEnumerable pathTokens) [DebuggerBrowsable(DebuggerBrowsableState.Never)] private IDictionary MessageSenders => LazyMessageSenders.Value; - /// - /// Represents a regular expression that matches fully alphanumeric strings. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const String FullyAlphanumericRegularExpression = "^[a-zA-Z0-9]*$"; - - /// - /// Represents a postfix for message type names which is trimmed when constructing entity paths. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const String TrimmedMessageTypeNamePostfix = "message"; - /// /// Represents a connection that governs interaction with messaging entities. /// diff --git a/src/RapidField.SolidInstruments.Messaging/MessagingEntityPath.cs b/src/RapidField.SolidInstruments.Messaging/MessagingEntityPath.cs new file mode 100644 index 00000000..a54246ff --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/MessagingEntityPath.cs @@ -0,0 +1,930 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Core.Extensions; +using RapidField.SolidInstruments.TextEncoding; +using System; +using System.Diagnostics; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Text.RegularExpressions; + +namespace RapidField.SolidInstruments.Messaging +{ + /// + /// Represents a textual path that defines a route to a messaging entity. + /// + /// + /// is the default implementation of . + /// + [DataContract] + public sealed class MessagingEntityPath : IMessagingEntityPath + { + /// + /// Initializes a new instance of the class. + /// + public MessagingEntityPath() + { + LabelOneValue = null; + LabelTwoValue = null; + LabelThreeValue = null; + MessageTypeValue = null; + PrefixValue = null; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The message type. + /// + /// + /// The prefix token, or if there is not a prefix. + /// + /// + /// A collection of labels, or an empty collection if there are no labels. + /// + /// + /// contains more than three elements. + /// + /// + /// is . + /// + /// + /// One or more of the specified values is too long. + /// + /// + /// One or more of the specified values contains invalid characters. + /// + [DebuggerHidden] + internal MessagingEntityPath(Type messageType, String prefix, params String[] labels) + : this(messageType.RejectIf().IsNull(nameof(messageType)).TargetArgument.Name, prefix, ExtractLabel(labels, 0), ExtractLabel(labels, 1), ExtractLabel(labels, 2)) + { + if (labels.IsNullOrEmpty()) + { + return; + } + else if (labels.Length > 3) + { + throw new ArgumentException("Messaging entity paths may contain a maximum of three labels."); + } + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The message type token. + /// + /// + /// The prefix token, or if there is not a prefix. + /// + /// + /// The first label token, or if there is not a first label. + /// + /// + /// The second label token, or if there is not a second label. + /// + /// + /// The third label token, or if there is not a third label. + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// One or more of the specified values is too long. + /// + /// + /// One or more of the specified values contains invalid characters. + /// + [DebuggerHidden] + private MessagingEntityPath(String messageType, String prefix, String labelOne, String labelTwo, String labelThree) + : this() + { + MessageType = messageType; + + if (prefix.IsNullOrEmpty() == false) + { + Prefix = prefix; + } + + if (labelOne.IsNullOrEmpty() == false) + { + LabelOne = labelOne; + } + + if (labelTwo.IsNullOrEmpty() == false) + { + LabelTwo = labelTwo; + } + + if (labelThree.IsNullOrEmpty() == false) + { + LabelThree = labelThree; + } + } + + /// + /// Determines whether or not two specified instances are not equal. + /// + /// + /// The first instance to compare. + /// + /// + /// The second instance to compare. + /// + /// + /// A value indicating whether or not the specified instances are not equal. + /// + public static Boolean operator !=(MessagingEntityPath a, IMessagingEntityPath b) => (a == b) == false; + + /// + /// Determines whether or not a specified instance is less than another specified + /// instance. + /// + /// + /// The first instance to compare. + /// + /// + /// The second instance to compare. + /// + /// + /// if the second object is earlier than the first object, otherwise . + /// + public static Boolean operator <(MessagingEntityPath a, IMessagingEntityPath b) => a.CompareTo(b) == -1; + + /// + /// Determines whether or not a specified instance is less than or equal to another + /// supplied instance. + /// + /// + /// The first instance to compare. + /// + /// + /// The second instance to compare. + /// + /// + /// if the second object is earlier than or equal to the first object, otherwise + /// . + /// + public static Boolean operator <=(MessagingEntityPath a, IMessagingEntityPath b) => a.CompareTo(b) < 1; + + /// + /// Determines whether or not two specified instances are equal. + /// + /// + /// The first instance to compare. + /// + /// + /// The second instance to compare. + /// + /// + /// A value indicating whether or not the specified instances are equal. + /// + public static Boolean operator ==(MessagingEntityPath a, IMessagingEntityPath b) + { + if ((Object)a is null && (Object)b is null) + { + return true; + } + else if ((Object)a is null || (Object)b is null) + { + return false; + } + + return a.Equals(b); + } + + /// + /// Determines whether or not a specified instance is greater than another specified + /// instance. + /// + /// + /// The first instance to compare. + /// + /// + /// The second instance to compare. + /// + /// + /// if the second object is later than the first object, otherwise . + /// + public static Boolean operator >(MessagingEntityPath a, IMessagingEntityPath b) => a.CompareTo(b) == 1; + + /// + /// Determines whether or not a specified instance is greater than or equal to another + /// supplied instance. + /// + /// + /// The first instance to compare. + /// + /// + /// The second instance to compare. + /// + /// + /// if the second object is later than or equal to the first object, otherwise + /// . + /// + public static Boolean operator >=(MessagingEntityPath a, IMessagingEntityPath b) => a.CompareTo(b) > -1; + + /// + /// Converts the specified representation of a messaging entity path to its + /// equivalent. + /// + /// + /// A containing a messaging entity path to convert. + /// + /// + /// A that is equivalent to . + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// does not contain a valid representation of a messaging entity path. + /// + public static MessagingEntityPath Parse(String input) + { + if (Parse(input, out var value, true)) + { + return value; + } + + return default; + } + + /// + /// Converts the specified representation of a messaging entity path to its + /// equivalent. The method returns a value that indicates whether the conversion + /// succeeded. + /// + /// + /// A containing a messaging entity path to convert. + /// + /// + /// The parsed result if the operation is successful, otherwise the default instance. + /// + /// + /// if the parse operation is successful, otherwise . + /// + public static Boolean TryParse(String input, out MessagingEntityPath result) + { + if (Parse(input, out var value, false)) + { + result = value; + return true; + } + + result = default; + return false; + } + + /// + /// Compares the current to the specified object and returns an indication of their + /// relative values. + /// + /// + /// The to compare to this instance. + /// + /// + /// Negative one if this instance is earlier than the specified instance; one if this instance is later than the supplied + /// instance; zero if they are equal. + /// + public Int32 CompareTo(IMessagingEntityPath other) => ToString().CompareTo(other?.ToString() ?? String.Empty); + + /// + /// Determines whether or not two specified instances are equal. + /// + /// + /// The to compare to this instance. + /// + /// + /// A value indicating whether or not the specified instances are equal. + /// + public Boolean Equals(IMessagingEntityPath other) + { + if ((Object)other is null) + { + return false; + } + else if (ToString() != other.ToString()) + { + return false; + } + + return true; + } + + /// + /// Determines whether or not the current is equal to the specified + /// . + /// + /// + /// The to compare to this instance. + /// + /// + /// A value indicating whether or not the specified instances are equal. + /// + public override Boolean Equals(Object obj) + { + if (obj is null) + { + return false; + } + else if (obj is IMessagingEntityPath) + { + return Equals((IMessagingEntityPath)obj); + } + + return false; + } + + /// + /// Returns the hash code for this instance. + /// + /// + /// A 32-bit signed integer hash code. + /// + public override Int32 GetHashCode() + { + var hashCode = 433494437 ^ MessageType.GetHashCode(); + + if (Prefix.IsNullOrEmpty() == false) + { + hashCode ^= Prefix.GetHashCode(); + } + + if (LabelOne.IsNullOrEmpty() == false) + { + hashCode ^= LabelOne.GetHashCode(); + } + + if (LabelTwo.IsNullOrEmpty() == false) + { + hashCode ^= LabelTwo.GetHashCode(); + } + + if (LabelThree.IsNullOrEmpty() == false) + { + hashCode ^= LabelThree.GetHashCode(); + } + + return hashCode; + } + + /// + /// Converts the value of the current to its equivalent string representation. + /// + /// + /// A string representation of the current . + /// + public override String ToString() + { + var stringBuilder = new StringBuilder(); + + if (Prefix.IsNullOrEmpty() == false) + { + stringBuilder.Append($"{Prefix}{DelimitingCharacterForPrefix}"); + } + + if (MessageType.IsNullOrEmpty() == false) + { + stringBuilder.Append(MessageType); + } + + if (LabelOne.IsNullOrEmpty() == false) + { + stringBuilder.Append($"{DelimitingCharacterForLabelToken}{LabelOne}"); + } + + if (LabelTwo.IsNullOrEmpty() == false) + { + stringBuilder.Append($"{DelimitingCharacterForLabelToken}{LabelTwo}"); + } + + if (LabelThree.IsNullOrEmpty() == false) + { + stringBuilder.Append($"{DelimitingCharacterForLabelToken}{LabelThree}"); + } + + return stringBuilder.ToString(); + } + + /// + /// Attempts to extract a label at the specified index from the specified collection of labels. + /// + /// + /// A collection of labels, or an empty collection if there are no labels. + /// + /// + /// The zero-based index of the label to extract. + /// + /// + /// The extracted label, or if no label was extracted. + /// + [DebuggerHidden] + private static String ExtractLabel(String[] labels, Int32 index) + { + if (labels.IsNullOrEmpty()) + { + return null; + } + else if (index >= labels.Length) + { + return null; + } + + var label = labels[index].Trim(); + return label.IsNullOrEmpty() ? null : label; + } + + /// + /// Converts the specified representation of a messaging entity path to its + /// equivalent. + /// + /// + /// A containing a messaging entity path to convert. + /// + /// + /// The resulting value, or if the operation is unsuccessful. + /// + /// + /// A value indicating whether or not an exception should be raised if the parse operation fails. + /// + /// + /// if the parse operation is successful, otherwise . + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// does not contain a valid representation of a messaging entity path. + /// + [DebuggerHidden] + private static Boolean Parse(String input, out MessagingEntityPath result, Boolean raiseExceptionOnFail) + { + if (raiseExceptionOnFail) + { + if (input is null) + { + throw new ArgumentNullException(nameof(input)); + } + else if (input.Length == 0) + { + throw new ArgumentEmptyException(nameof(input)); + } + } + else if (input.IsNullOrEmpty()) + { + result = default; + return false; + } + + try + { + var processedString = input.Solidify(); + + if (processedString.Length == 0) + { + if (raiseExceptionOnFail) + { + throw new FormatException(ParseFormatExceptionMessage, new ArgumentException("The input string does not contain any path information.", nameof(input))); + } + + result = default; + return false; + } + + var regularExpression = new Regex(RegularExpressionPatternForCompletePath); + var matchGroups = regularExpression.IsMatch(processedString) ? regularExpression.Match(processedString).Groups : null; + + if (matchGroups.IsNullOrEmpty()) + { + if (raiseExceptionOnFail) + { + throw new FormatException(ParseFormatExceptionMessage, new ArgumentException("The input string is invalid.", nameof(input))); + } + + result = default; + return false; + } + + var messageTypeString = matchGroups.Where(group => group.Success && group.Name == PatternGroupNameForMessageTypeToken).SingleOrDefault()?.Value; + + if (messageTypeString.IsNullOrEmpty()) + { + if (raiseExceptionOnFail) + { + throw new FormatException(ParseFormatExceptionMessage, new ArgumentException("The message type is invalid.", nameof(input))); + } + + result = default; + return false; + } + + var prefixString = matchGroups.Where(group => group.Success && group.Name == PatternGroupNameForPrefixToken).SingleOrDefault()?.Value; + var labelOneString = matchGroups.Where(group => group.Success && group.Name == PatternGroupNameForLabelTokenOne).SingleOrDefault()?.Value; + var labelTwoString = matchGroups.Where(group => group.Success && group.Name == PatternGroupNameForLabelTokenTwo).SingleOrDefault()?.Value; + var labelThreeString = matchGroups.Where(group => group.Success && group.Name == PatternGroupNameForLabelTokenThree).SingleOrDefault()?.Value; + result = new MessagingEntityPath(messageTypeString, prefixString, labelOneString, labelTwoString, labelThreeString); + return true; + } + catch (Exception exception) + { + if (raiseExceptionOnFail) + { + throw new FormatException(ParseFormatExceptionMessage, exception); + } + + result = default; + return false; + } + } + + /// + /// Raises an if the specified label token is invalid. + /// + /// + /// The token to evaluate. + /// + /// + /// The specified token. + /// + /// + /// is too long. + /// + /// + /// is invalid. + /// + [DebuggerHidden] + private static String ValidateLabelToken(String token) => ValidateToken(token?.RejectIf().LengthIsGreaterThan(MaximumCharacterLengthForLabelToken, nameof(token)), RegularExpressionPatternForLabelToken); + + /// + /// Raises an if the specified message type token is invalid. + /// + /// + /// The token to evaluate. + /// + /// + /// The specified token. + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// is too long. + /// + /// + /// is invalid. + /// + [DebuggerHidden] + private static String ValidateMessageTypeToken(String token) => ValidateToken(token.RejectIf().IsNullOrEmpty(nameof(token)).OrIf().LengthIsGreaterThan(MaximumCharacterLengthForMessageTypeToken, nameof(token)), RegularExpressionPatternForMessageTypeToken); + + /// + /// Raises an if the specified prefix token is invalid. + /// + /// + /// The token to evaluate. + /// + /// + /// The specified token. + /// + /// + /// is too long. + /// + /// + /// is invalid. + /// + [DebuggerHidden] + private static String ValidatePrefixToken(String token) => ValidateToken(token?.RejectIf().LengthIsGreaterThan(MaximumCharacterLengthForPrefixToken, nameof(token)), RegularExpressionPatternForPrefixToken); + + /// + /// Raises an if the specified token is invalid. + /// + /// + /// The token to evaluate. + /// + /// + /// The token pattern against which to compare . + /// + /// + /// The specified token. + /// + /// + /// is invalid. + /// + [DebuggerHidden] + private static String ValidateToken(String token, String pattern) => token.IsNullOrEmpty() ? null : token.RejectIf().DoesNotMatchRegularExpression(pattern, nameof(token)); + + /// + /// Gets or sets the first label for the current , or if there is + /// not a first label. + /// + /// + /// The specified value is too long. + /// + /// + /// The specified value contains invalid characters. + /// + [DataMember] + public String LabelOne + { + get => LabelOneValue; + set => LabelOneValue = ValidateLabelToken(value); + } + + /// + /// Gets or sets the third label for the current , or if there is + /// not a third label. + /// + /// + /// The specified value is too long. + /// + /// + /// The specified value contains invalid characters. + /// + [DataMember] + public String LabelThree + { + get => LabelThreeValue; + set => LabelThreeValue = ValidateLabelToken(value); + } + + /// + /// Gets or sets the second label for the current , or if there is + /// not a second label. + /// + /// + /// The specified value is too long. + /// + /// + /// The specified value contains invalid characters. + /// + [DataMember] + public String LabelTwo + { + get => LabelTwoValue; + set => LabelTwoValue = ValidateLabelToken(value); + } + + /// + /// Gets or sets the message type for the current . + /// + /// + /// The specified value is empty. + /// + /// + /// The specified value is . + /// + /// + /// The specified value is too long. + /// + /// + /// The specified value contains invalid characters. + /// + [DataMember] + public String MessageType + { + get + { + if (MessageTypeValue.IsNullOrEmpty()) + { + MessageTypeValue = EnhancedReadabilityGuid.New().ToString(); + } + + return MessageTypeValue; + } + set => MessageTypeValue = ValidateMessageTypeToken(value); + } + + /// + /// Gets or sets the prefix for the current , or if there is not a + /// prefix. + /// + /// + /// The specified value is too long. + /// + /// + /// The specified value contains invalid characters. + /// + [DataMember] + public String Prefix + { + get => PrefixValue; + set => PrefixValue = ValidatePrefixToken(value); + } + + /// + /// Gets a regular expression that is used to validate the complete path. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static String RegularExpressionPatternForCompletePath => $"^{RegularExpressionPatternForPrefix}?{RegularExpressionPatternForMessageType}{RegularExpressionPatternForLabelOne}?{RegularExpressionPatternForLabelTwo}?{RegularExpressionPatternForLabelThree}?$"; + + /// + /// Gets a regular expression that is used to validate the first label. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static String RegularExpressionPatternForLabelOne => $"(?<{PatternGroupNameForLabelOne}>(?<{PatternGroupNameForLabelDelimiterOne}>[{DelimitingCharacterForLabelToken}])(?<{PatternGroupNameForLabelTokenOne}>{RegularExpressionPatternForLabelToken}))"; + + /// + /// Gets a regular expression that is used to validate the third label. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static String RegularExpressionPatternForLabelThree => $"(?<{PatternGroupNameForLabelThree}>(?<{PatternGroupNameForLabelDelimiterThree}>[{DelimitingCharacterForLabelToken}])(?<{PatternGroupNameForLabelTokenThree}>{RegularExpressionPatternForLabelToken}))"; + + /// + /// Gets a regular expression that is used to validate label tokens. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static String RegularExpressionPatternForLabelToken => $"[a-zA-Z0-9]{{1,{MaximumCharacterLengthForLabelToken}}}"; + + /// + /// Gets a regular expression that is used to validate the second label. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static String RegularExpressionPatternForLabelTwo => $"(?<{PatternGroupNameForLabelTwo}>(?<{PatternGroupNameForLabelDelimiterTwo}>[{DelimitingCharacterForLabelToken}])(?<{PatternGroupNameForLabelTokenTwo}>{RegularExpressionPatternForLabelToken}))"; + + /// + /// Gets a regular expression that is used to validate the full message type. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static String RegularExpressionPatternForMessageType => $"(?<{PatternGroupNameForMessageTypeToken}>{RegularExpressionPatternForMessageTypeToken})"; + + /// + /// Gets a regular expression that is used to validate the message type token. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static String RegularExpressionPatternForMessageTypeToken => $"[a-zA-Z0-9]{{1,{MaximumCharacterLengthForMessageTypeToken}}}"; + + /// + /// Gets a regular expression that is used to validate the full prefix. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static String RegularExpressionPatternForPrefix => $"(?<{PatternGroupNameForPrefix}>(?<{PatternGroupNameForPrefixToken}>{RegularExpressionPatternForPrefixToken})(?<{PatternGroupNameForPrefixDelimiter}>[{DelimitingCharacterForPrefix}]))"; + + /// + /// Gets a regular expression that is used to validate the prefix token. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static String RegularExpressionPatternForPrefixToken => $"[a-zA-Z0-9]{{1,{MaximumCharacterLengthForPrefixToken}}}"; + + /// + /// Represents the maximum number of allowed characters for a label token. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + public const Int32 MaximumCharacterLengthForLabelToken = 34; + + /// + /// Represents the maximum number of allowed characters for a message type token. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + public const Int32 MaximumCharacterLengthForMessageTypeToken = 89; + + /// + /// Represents the maximum number of allowed characters for a prefix token. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + public const Int32 MaximumCharacterLengthForPrefixToken = 21; + + /// + /// Represents the delimiting character that precedes label tokens. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal const Char DelimitingCharacterForLabelToken = '_'; + + /// + /// Represents the delimiting character that follows the prefix token. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal const Char DelimitingCharacterForPrefix = '-'; + + /// + /// Represents a message for format exceptions raised by + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String ParseFormatExceptionMessage = "The specified string could not be parsed as a messaging entity path. See the inner exception for details."; + + /// + /// Represents the group name for the first label delimiter. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String PatternGroupNameForLabelDelimiterOne = "labeldelimiterone"; + + /// + /// Represents the group name for the third label delimiter. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String PatternGroupNameForLabelDelimiterThree = "labeldelimiterthree"; + + /// + /// Represents the group name for the second label delimiter. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String PatternGroupNameForLabelDelimiterTwo = "labeldelimitertwo"; + + /// + /// Represents the group name for the first label. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String PatternGroupNameForLabelOne = "labelone"; + + /// + /// Represents the group name for the third label. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String PatternGroupNameForLabelThree = "labelthree"; + + /// + /// Represents the group name for the first label token. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String PatternGroupNameForLabelTokenOne = "labeltokenone"; + + /// + /// Represents the group name for the third label token. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String PatternGroupNameForLabelTokenThree = "labeltokenthree"; + + /// + /// Represents the group name for the second label token. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String PatternGroupNameForLabelTokenTwo = "labeltokentwo"; + + /// + /// Represents the group name for the second label. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String PatternGroupNameForLabelTwo = "labeltwo"; + + /// + /// Represents the group name for the message type token. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String PatternGroupNameForMessageTypeToken = "messagetypetoken"; + + /// + /// Represents the group name for the full prefix. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String PatternGroupNameForPrefix = "prefix"; + + /// + /// Represents the group name for the prefix delimiter. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String PatternGroupNameForPrefixDelimiter = "prefixdelimiter"; + + /// + /// Represents the group name for the prefix token. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String PatternGroupNameForPrefixToken = "prefixtoken"; + + /// + /// Represents the first label for the current . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private String LabelOneValue; + + /// + /// Represents the third label for the current . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private String LabelThreeValue; + + /// + /// Represents the second label for the current . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private String LabelTwoValue; + + /// + /// Represents the message type for the current . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private String MessageTypeValue; + + /// + /// Represents the prefix for the current . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private String PrefixValue; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/MessagingFacade.cs b/src/RapidField.SolidInstruments.Messaging/MessagingFacade.cs index 52cf94d4..ca8521d0 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessagingFacade.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessagingFacade.cs @@ -82,7 +82,7 @@ protected virtual String InitializeApplicationIdentity() /// /// A unique textual identifier. /// - protected virtual String InitializeIdentifier() => new String(new ZBase32Encoding().GetChars(Guid.NewGuid().ToByteArray().ComputeThirtyTwoBitHash().ToByteArray())); + protected virtual String InitializeIdentifier() => new ZBase32Encoding().GetString(Guid.NewGuid().ToByteArray().ComputeThirtyTwoBitHash().ToByteArray()); /// /// Gets the name or value that uniquely identifies the application in which the current diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessageQueue.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessageQueue.cs index b24e2f59..5e5c04b6 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessageQueue.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessageQueue.cs @@ -5,7 +5,6 @@ using RapidField.SolidInstruments.Core; using RapidField.SolidInstruments.Core.ArgumentValidation; using RapidField.SolidInstruments.Core.Concurrency; -using RapidField.SolidInstruments.Core.Extensions; using RapidField.SolidInstruments.Serialization; using System; using System.Collections.Concurrent; @@ -58,7 +57,7 @@ internal DurableMessageQueue(IDurableMessageQueuePersistenceProxy persistencePro /// [DebuggerHidden] internal DurableMessageQueue(Guid identifier, IDurableMessageQueuePersistenceProxy persistenceProxy) - : this(identifier, $"{identifier.ToSerializedString()}", persistenceProxy) + : this(identifier, new MessagingEntityPath(), persistenceProxy) { return; } @@ -70,14 +69,11 @@ internal DurableMessageQueue(Guid identifier, IDurableMessageQueuePersistencePro /// A unique identifier for the queue. /// /// - /// The unique textual path that identifies the queue. + /// A unique textual path that identifies the queue. /// /// /// An object that manages thread-safe persistence for the queue. /// - /// - /// is empty. - /// /// /// is -or- is /// . @@ -86,7 +82,7 @@ internal DurableMessageQueue(Guid identifier, IDurableMessageQueuePersistencePro /// is equal to . /// [DebuggerHidden] - internal DurableMessageQueue(Guid identifier, String path, IDurableMessageQueuePersistenceProxy persistenceProxy) + internal DurableMessageQueue(Guid identifier, IMessagingEntityPath path, IDurableMessageQueuePersistenceProxy persistenceProxy) : this(identifier, path, persistenceProxy, DurableMessageQueueOperationalState.Ready) { return; @@ -99,7 +95,7 @@ internal DurableMessageQueue(Guid identifier, String path, IDurableMessageQueueP /// A unique identifier for the queue. /// /// - /// The unique textual path that identifies the queue. + /// A unique textual path that identifies the queue. /// /// /// An object that manages thread-safe persistence for the queue. @@ -107,9 +103,6 @@ internal DurableMessageQueue(Guid identifier, String path, IDurableMessageQueueP /// /// The operational state of the queue. The default value is . /// - /// - /// is empty. - /// /// /// is -or- is /// . @@ -119,7 +112,7 @@ internal DurableMessageQueue(Guid identifier, String path, IDurableMessageQueueP /// to . /// [DebuggerHidden] - internal DurableMessageQueue(Guid identifier, String path, IDurableMessageQueuePersistenceProxy persistenceProxy, DurableMessageQueueOperationalState operationalState) + internal DurableMessageQueue(Guid identifier, IMessagingEntityPath path, IDurableMessageQueuePersistenceProxy persistenceProxy, DurableMessageQueueOperationalState operationalState) : this(identifier, path, persistenceProxy, operationalState, DefaultMessageBodySerializationFormat) { return; @@ -132,7 +125,7 @@ internal DurableMessageQueue(Guid identifier, String path, IDurableMessageQueueP /// A unique identifier for the queue. /// /// - /// The unique textual path that identifies the queue. + /// A unique textual path that identifies the queue. /// /// /// An object that manages thread-safe persistence for the queue. @@ -144,9 +137,6 @@ internal DurableMessageQueue(Guid identifier, String path, IDurableMessageQueueP /// The format that is used to serialize enqueued message bodies. The default value is /// . /// - /// - /// is empty. - /// /// /// is -or- is /// . @@ -157,7 +147,7 @@ internal DurableMessageQueue(Guid identifier, String path, IDurableMessageQueueP /// is equal to . /// [DebuggerHidden] - internal DurableMessageQueue(Guid identifier, String path, IDurableMessageQueuePersistenceProxy persistenceProxy, DurableMessageQueueOperationalState operationalState, SerializationFormat messageBodySerializationFormat) + internal DurableMessageQueue(Guid identifier, IMessagingEntityPath path, IDurableMessageQueuePersistenceProxy persistenceProxy, DurableMessageQueueOperationalState operationalState, SerializationFormat messageBodySerializationFormat) : this(identifier, path, persistenceProxy, operationalState, messageBodySerializationFormat, DefaultMessageLockExpirationThreshold) { return; @@ -170,7 +160,7 @@ internal DurableMessageQueue(Guid identifier, String path, IDurableMessageQueueP /// A unique identifier for the queue. /// /// - /// The unique textual path that identifies the queue. + /// A unique textual path that identifies the queue. /// /// /// An object that manages thread-safe persistence for the queue. @@ -186,9 +176,6 @@ internal DurableMessageQueue(Guid identifier, String path, IDurableMessageQueueP /// The length of time that a locked message is held before abandoning the associated token and making the message available /// for processing. The default value is three minutes. /// - /// - /// is empty. - /// /// /// is -or- is /// . @@ -200,7 +187,7 @@ internal DurableMessageQueue(Guid identifier, String path, IDurableMessageQueueP /// is less than eight seconds. /// [DebuggerHidden] - internal DurableMessageQueue(Guid identifier, String path, IDurableMessageQueuePersistenceProxy persistenceProxy, DurableMessageQueueOperationalState operationalState, SerializationFormat messageBodySerializationFormat, TimeSpan messageLockExpirationThreshold) + internal DurableMessageQueue(Guid identifier, IMessagingEntityPath path, IDurableMessageQueuePersistenceProxy persistenceProxy, DurableMessageQueueOperationalState operationalState, SerializationFormat messageBodySerializationFormat, TimeSpan messageLockExpirationThreshold) : this(identifier, path, persistenceProxy, operationalState, messageBodySerializationFormat, messageLockExpirationThreshold, DefaultEnqueueTimeoutThreshold) { return; @@ -213,7 +200,7 @@ internal DurableMessageQueue(Guid identifier, String path, IDurableMessageQueueP /// A unique identifier for the queue. /// /// - /// The unique textual path that identifies the queue. + /// A unique textual path that identifies the queue. /// /// /// An object that manages thread-safe persistence for the queue. @@ -233,9 +220,6 @@ internal DurableMessageQueue(Guid identifier, String path, IDurableMessageQueueP /// The maximum length of time to wait for a message to be enqueued before raising an exception. The default value is eight /// seconds. /// - /// - /// is empty. - /// /// /// is -or- is /// . @@ -248,7 +232,7 @@ internal DurableMessageQueue(Guid identifier, String path, IDurableMessageQueueP /// is less than two seconds. /// [DebuggerHidden] - internal DurableMessageQueue(Guid identifier, String path, IDurableMessageQueuePersistenceProxy persistenceProxy, DurableMessageQueueOperationalState operationalState, SerializationFormat messageBodySerializationFormat, TimeSpan messageLockExpirationThreshold, TimeSpan enqueueTimeoutThreshold) + internal DurableMessageQueue(Guid identifier, IMessagingEntityPath path, IDurableMessageQueuePersistenceProxy persistenceProxy, DurableMessageQueueOperationalState operationalState, SerializationFormat messageBodySerializationFormat, TimeSpan messageLockExpirationThreshold, TimeSpan enqueueTimeoutThreshold) : this(identifier, path, persistenceProxy, operationalState, messageBodySerializationFormat, messageLockExpirationThreshold, enqueueTimeoutThreshold, Array.Empty(), new Dictionary()) { return; @@ -261,7 +245,7 @@ internal DurableMessageQueue(Guid identifier, String path, IDurableMessageQueueP /// A unique identifier for the queue. /// /// - /// The unique textual path that identifies the queue. + /// A unique textual path that identifies the queue. /// /// /// An object that manages thread-safe persistence for the queue. @@ -287,9 +271,6 @@ internal DurableMessageQueue(Guid identifier, String path, IDurableMessageQueueP /// /// The underlying first-in first-out collection that contains messages that are locked for processing. /// - /// - /// is empty. - /// /// /// is -or- is /// -or- is -or- is @@ -303,7 +284,7 @@ internal DurableMessageQueue(Guid identifier, String path, IDurableMessageQueueP /// is less than two seconds. /// [DebuggerHidden] - private DurableMessageQueue(Guid identifier, String path, IDurableMessageQueuePersistenceProxy persistenceProxy, DurableMessageQueueOperationalState operationalState, SerializationFormat messageBodySerializationFormat, TimeSpan messageLockExpirationThreshold, TimeSpan enqueueTimeoutThreshold, IEnumerable messages, IDictionary lockedMessages) + private DurableMessageQueue(Guid identifier, IMessagingEntityPath path, IDurableMessageQueuePersistenceProxy persistenceProxy, DurableMessageQueueOperationalState operationalState, SerializationFormat messageBodySerializationFormat, TimeSpan messageLockExpirationThreshold, TimeSpan enqueueTimeoutThreshold, IEnumerable messages, IDictionary lockedMessages) : base() { EnqueueTimeoutThreshold = enqueueTimeoutThreshold.RejectIf().IsLessThan(EnqueueTimeoutThresholdFloor, nameof(enqueueTimeoutThreshold)); @@ -313,7 +294,7 @@ private DurableMessageQueue(Guid identifier, String path, IDurableMessageQueuePe MessageBodySerializationFormat = messageBodySerializationFormat.RejectIf().IsEqualToValue(SerializationFormat.Unspecified, nameof(messageBodySerializationFormat)); MessageLockExpirationThreshold = messageLockExpirationThreshold.RejectIf().IsLessThan(MessageLockExpirationThresholdFloor, nameof(messageLockExpirationThreshold)); OperationalState = operationalState.RejectIf().IsEqualToValue(DurableMessageQueueOperationalState.Unspecified, nameof(operationalState)); - Path = path.RejectIf().IsNullOrEmpty(nameof(path)); + Path = path.RejectIf().IsNull(nameof(path)).TargetArgument; PersistenceProxy = persistenceProxy.RejectIf().IsNull(nameof(persistenceProxy)).TargetArgument; } @@ -894,9 +875,9 @@ public DurableMessageQueueOperationalState OperationalState } /// - /// Gets the unique textual path that identifies the current . + /// Gets a unique textual path that identifies the current . /// - public String Path + public IMessagingEntityPath Path { get; internal set; diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessageQueueSnapshot.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessageQueueSnapshot.cs index 388f1476..1c908b19 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessageQueueSnapshot.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessageQueueSnapshot.cs @@ -41,7 +41,7 @@ internal DurableMessageQueueSnapshot(DurableMessageQueue messageQueue) MessageLockExpirationThreshold = messageQueue.MessageLockExpirationThreshold; Messages = new List(messageQueue.Messages.ToArray()); OperationalState = messageQueue.OperationalState; - Path = messageQueue.Path; + Path = messageQueue.Path.ToString(); TimeStamp = Core.TimeStamp.Current; } @@ -117,7 +117,7 @@ public DurableMessageQueueOperationalState OperationalState } /// - /// Gets or sets the unique textual path that identifies the associated . + /// Gets or sets a unique textual path that identifies the associated . /// [DataMember] public String Path diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageQueue.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageQueue.cs index ef58f502..2851fac9 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageQueue.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageQueue.cs @@ -210,9 +210,9 @@ DurableMessageQueueOperationalState OperationalState } /// - /// Gets the unique textual path that identifies the current . + /// Gets a unique textual path that identifies the current . /// - String Path + IMessagingEntityPath Path { get; } diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/CascadingSymmetricKeyTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/CascadingSymmetricKeyTests.cs index 6e5db345..f391bae6 100644 --- a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/CascadingSymmetricKeyTests.cs +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/CascadingSymmetricKeyTests.cs @@ -216,10 +216,10 @@ public void New_ShouldReturnValidKey_ForValidArguments() public void StressTest_ShouldProduceDesiredResults() { // Arrange. - var testCount = 30; - var repititionCount = 30; + var testCount = 1; + var repititionCount = 3; var concurrencyControlMode = ConcurrencyControlMode.ProcessorCountSemaphore; - var blockTimeoutThreshold = TimeSpan.FromSeconds(10); + var blockTimeoutThreshold = TimeSpan.FromSeconds(5); using (var stateControl = ConcurrencyControl.New(concurrencyControlMode, blockTimeoutThreshold)) { @@ -232,7 +232,7 @@ public void StressTest_ShouldProduceDesiredResults() for (var repititionIndex = 0; repititionIndex < repititionCount; repititionIndex++) { // Assert. - controlToken.AttachTask(Task.Factory.StartNew(() => ToBuffer_ShouldBeReversible(target))); + controlToken.AttachTask(() => ToBuffer_ShouldBeReversible(target)); } } } diff --git a/test/RapidField.SolidInstruments.Messaging.UnitTests/MessagingEntityPathTests.cs b/test/RapidField.SolidInstruments.Messaging.UnitTests/MessagingEntityPathTests.cs new file mode 100644 index 00000000..0465ef86 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.UnitTests/MessagingEntityPathTests.cs @@ -0,0 +1,179 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RapidField.SolidInstruments.Serialization; +using System; +using System.Linq; + +namespace RapidField.SolidInstruments.Messaging.UnitTests +{ + [TestClass] + public class MessagingEntityPathTests + { + [TestMethod] + public void CompareTo_ShouldProduceDesiredResults() + { + // Arrange. + var targetOne = MessagingEntityPath.Parse("Foo-SimulatedMessage_LabelOne"); + var targetTwo = MessagingEntityPath.Parse("Foo-SimulatedMessage_LabelOne"); + var targetThree = MessagingEntityPath.Parse("Foo-SimulatedMessage_LabelOne_LabelTwo"); + var targetFour = MessagingEntityPath.Parse("Zip-SimulatedMessage_LabelOne"); + + // Act. + var resultOne = targetOne.CompareTo(targetTwo) == 0; + var resultTwo = targetTwo.CompareTo(targetThree) == -1; + var resultThree = targetTwo < targetOne; + var resultFour = targetThree > targetOne; + var resultFive = targetFour <= targetThree; + var resultSix = targetTwo >= targetOne; + + // Assert. + resultOne.Should().BeTrue(); + resultTwo.Should().BeTrue(); + resultThree.Should().BeFalse(); + resultFour.Should().BeTrue(); + resultFive.Should().BeFalse(); + resultSix.Should().BeTrue(); + } + + [TestMethod] + public void Parse_ShouldProduceDesiredResults_ForValidPathStrings() + { + // Arrange. + var validPathStrings = new String[] + { + "0", + "0-1", + "0-1_a", + "0-1_a_b", + "0-1_a_b_c", + "MessageType", + "Prefix-MessageType", + "0Prefix1-0MessageType1", + "Prefix-MessageType_LabelOne", + "0Prefix1-0MessageType1_0LabelOne1", + "Prefix-MessageType_LabelOne_LabelTwo", + "0Prefix1-0MessageType1_0LabelOne1_0LabelTwo1", + "Prefix-MessageType_LabelOne_LabelTwo_LabelThree", + "0Prefix1-0MessageType1_0LabelOne1_0LabelTwo1_0LabelThree1", + }; + + // Act. + var target = validPathStrings.Select(value => MessagingEntityPath.Parse(value)).ToArray(); + + for (var i = 0; i < validPathStrings.Length; i++) + { + // Assert. + validPathStrings[i].Should().Be(target[i].ToString()); + } + } + + [TestMethod] + public void Parse_ShouldRaiseFormatException_ForInvalidPathStrings() + { + // Arrange. + var target = (MessagingEntityPath)null; + var invalidVersionStrings = new String[] + { + "0-", + "-0-1", + "_0-1_a", + "0-1_a_b_", + "0_-1_a_b_c", + "Message__Type", + "Prefix_-MessageType", + "0Prefix1-_0MessageType1", + "Prefix-MessageType_LabelOne$", + "0+Prefix1-0MessageType1_0LabelOne1", + "Prefix+MessageType_LabelOne_LabelTwo", + "0Prefix111111111111111111111111111111111111111111111111111111111111111111111111-0MessageType1_0LabelOne1_0LabelTwo1", + "Prefix-MessageType_LabelOne_LabelTwo_LabelThree111111111111111111111111111111111111111111111111111111111111111111111111", + "0Prefix1-0MessageType1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111_0LabelOne1_0LabelTwo1_0LabelThree1", + }; + + for (var i = 0; i < invalidVersionStrings.Length; i++) + { + // Act. + var action = new Action(() => + { + target = MessagingEntityPath.Parse(invalidVersionStrings[i]); + }); + + // Assert. + action.Should().Throw(); + } + } + + [TestMethod] + public void ShouldBeSerializable_UsingBinaryFormat() + { + // Arrange. + var format = SerializationFormat.Binary; + + // Assert. + ShouldBeSerializable(format); + } + + [TestMethod] + public void ShouldBeSerializable_UsingCompressedJsonFormat() + { + // Arrange. + var format = SerializationFormat.CompressedJson; + + // Assert. + ShouldBeSerializable(format); + } + + [TestMethod] + public void ShouldBeSerializable_UsingCompressedXmlFormat() + { + // Arrange. + var format = SerializationFormat.CompressedXml; + + // Assert. + ShouldBeSerializable(format); + } + + [TestMethod] + public void ShouldBeSerializable_UsingJsonFormat() + { + // Arrange. + var format = SerializationFormat.Json; + + // Assert. + ShouldBeSerializable(format); + } + + [TestMethod] + public void ShouldBeSerializable_UsingXmlFormat() + { + // Arrange. + var format = SerializationFormat.Xml; + + // Assert. + ShouldBeSerializable(format); + } + + private static void ShouldBeSerializable(SerializationFormat format) + { + // Arrange. + var messageType = typeof(SimulatedMessage); + var prefix = "Foo"; + var labelOne = "LabelOne"; + var labelTwo = "LabelTwo"; + var labelThree = "LabelThree"; + var target = new MessagingEntityPath(messageType, prefix, labelOne, labelTwo, labelThree); + var serializer = new DynamicSerializer(format); + + // Act. + var serializedTarget = serializer.Serialize(target); + var deserializedResult = serializer.Deserialize(serializedTarget); + + // Assert. + deserializedResult.Should().Be(target); + } + } +} \ No newline at end of file From 8c823bb38d8611c495c34fefe2bc06460f04ae54 Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Thu, 12 Mar 2020 22:12:28 -0500 Subject: [PATCH 14/55] Refactoring and extending in-memory messaging primitives. --- en-US_User.dic | 2 + .../IMessagingClientFactory.cs | 67 +- .../MessagingClientFactory.cs | 118 ++- ...pidField.SolidInstruments.Messaging.csproj | 3 - .../DurableMessageQueue.cs | 943 ------------------ .../DurableMessageQueueFactory.cs | 51 - ...DurableMessageQueuePersistenceException.cs | 70 -- .../DurableMessageQueueSnapshot.cs | 139 --- .../IDurableMessageQueueFactory.cs | 15 - .../IDurableMessageQueuePersistenceProxy.cs | 76 -- .../IDurableMessageTransport.cs | 39 - .../TransportPrimitives/IMessageQueue.cs | 38 + .../TransportPrimitives/IMessageTopic.cs | 110 ++ .../TransportPrimitives/IMessageTransport.cs | 408 ++++++++ .../IMessageTransportConnection.cs | 43 + ...bleMessageQueue.cs => IMessagingEntity.cs} | 118 +-- ...essageLockToken.cs => MessageLockToken.cs} | 98 +- .../TransportPrimitives/MessageQueue.cs | 349 +++++++ .../TransportPrimitives/MessageTopic.cs | 457 +++++++++ ...ssageTransportConnectionClosedException.cs | 51 + ....cs => MessageTransportConnectionState.cs} | 23 +- .../TransportPrimitives/MessagingEntity.cs | 840 ++++++++++++++++ ....cs => MessagingEntityOperationalState.cs} | 16 +- ...{DurableMessage.cs => PrimitiveMessage.cs} | 24 +- .../DuplexSemaphoreControlTests.cs | 2 +- .../ProcessorCountSemaphoreControlTests.cs | 2 +- .../SingleThreadLockControlTests.cs | 2 +- ...TokenTests.cs => MessageLockTokenTests.cs} | 6 +- ...ssageTests.cs => PrimitiveMessageTests.cs} | 10 +- 29 files changed, 2577 insertions(+), 1543 deletions(-) delete mode 100644 src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessageQueue.cs delete mode 100644 src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessageQueueFactory.cs delete mode 100644 src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessageQueuePersistenceException.cs delete mode 100644 src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessageQueueSnapshot.cs delete mode 100644 src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageQueueFactory.cs delete mode 100644 src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageQueuePersistenceProxy.cs delete mode 100644 src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageTransport.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageQueue.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTopic.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransport.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransportConnection.cs rename src/RapidField.SolidInstruments.Messaging/TransportPrimitives/{IDurableMessageQueue.cs => IMessagingEntity.cs} (63%) rename src/RapidField.SolidInstruments.Messaging/TransportPrimitives/{DurableMessageLockToken.cs => MessageLockToken.cs} (64%) create mode 100644 src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageQueue.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTopic.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransportConnectionClosedException.cs rename src/RapidField.SolidInstruments.Messaging/TransportPrimitives/{IDurableMessageQueueOperation.cs => MessageTransportConnectionState.cs} (57%) create mode 100644 src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessagingEntity.cs rename src/RapidField.SolidInstruments.Messaging/TransportPrimitives/{DurableMessageQueueOperationalState.cs => MessagingEntityOperationalState.cs} (69%) rename src/RapidField.SolidInstruments.Messaging/TransportPrimitives/{DurableMessage.cs => PrimitiveMessage.cs} (90%) rename test/RapidField.SolidInstruments.Messaging.UnitTests/TransportPrimitives/{DurableMessageLockTokenTests.cs => MessageLockTokenTests.cs} (92%) rename test/RapidField.SolidInstruments.Messaging.UnitTests/TransportPrimitives/{DurableMessageTests.cs => PrimitiveMessageTests.cs} (89%) diff --git a/en-US_User.dic b/en-US_User.dic index 6c4f066d..1d611e4d 100644 --- a/en-US_User.dic +++ b/en-US_User.dic @@ -60,6 +60,7 @@ Decrypts deliverables dependabot dequeue +dequeued deserialization Deserialize deserialized @@ -188,6 +189,7 @@ releasenotes reportable Repos requeue +requeued requeues reveived rhnyj diff --git a/src/RapidField.SolidInstruments.Messaging/IMessagingClientFactory.cs b/src/RapidField.SolidInstruments.Messaging/IMessagingClientFactory.cs index a77fcab7..3b8401bb 100644 --- a/src/RapidField.SolidInstruments.Messaging/IMessagingClientFactory.cs +++ b/src/RapidField.SolidInstruments.Messaging/IMessagingClientFactory.cs @@ -20,9 +20,29 @@ namespace RapidField.SolidInstruments.Messaging /// /// The type of implementation-specific adapted messages. /// - public interface IMessagingClientFactory + public interface IMessagingClientFactory : IAsyncDisposable, IDisposable where TAdaptedMessage : class { + /// + /// Returns a queue entity path for the specified message type. + /// + /// + /// The type of the message. + /// + /// + /// An ordered collection of as many as three non-null, non-empty alphanumeric textual labels to append to the path, or + /// to omit path labels. The default value is . + /// + /// + /// A queue entity path for the specified message type. + /// + /// + /// contains one or more null or empty labels and/or labels with non-alphanumeric characters, + /// or contains more than three elements. + /// + IMessagingEntityPath GetQueuePath(IEnumerable pathLabels) + where TMessage : class; + /// /// Gets a shared, managed, implementation-specific message receiver for a type-defined queue. /// @@ -111,6 +131,51 @@ TSender GetQueueSender() TSender GetQueueSender(IEnumerable pathLabels) where TMessage : class; + /// + /// Returns a subscription name for the specified message type. + /// + /// + /// The type of the message. + /// + /// + /// A unique textual identifier for the message receiver, or if the receiver is unspecified. + /// + /// + /// The unique path for the entity. + /// + /// + /// A subscription name for the specified message type. + /// + /// + /// is empty. + /// + /// + /// is -or- is + /// . + /// + public String GetSubscriptionName(String receiverIdentifier, IMessagingEntityPath entityPath) + where TMessage : class; + + /// + /// Returns a topic entity path for the specified message type. + /// + /// + /// The type of the message. + /// + /// + /// An ordered collection of as many as three non-null, non-empty alphanumeric textual labels to append to the path, or + /// to omit path labels. The default value is . + /// + /// + /// A topic entity path for the specified message type. + /// + /// + /// contains one or more null or empty labels and/or labels with non-alphanumeric characters, + /// or contains more than three elements. + /// + IMessagingEntityPath GetTopicPath(IEnumerable pathLabels) + where TMessage : class; + /// /// Gets a shared, managed, implementation-specific message receiver for a type-defined topic. /// diff --git a/src/RapidField.SolidInstruments.Messaging/MessagingClientFactory.cs b/src/RapidField.SolidInstruments.Messaging/MessagingClientFactory.cs index 30488a05..680b8083 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessagingClientFactory.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessagingClientFactory.cs @@ -27,7 +27,7 @@ namespace RapidField.SolidInstruments.Messaging /// The type of implementation-specific adapted messages. /// /// - /// The type of the connection + /// The type of the connection. /// /// /// is the default implementation of @@ -52,6 +52,26 @@ protected MessagingClientFactory(TConnection connection) Connection = connection.RejectIf().IsNull(nameof(connection)); } + /// + /// Returns a queue entity path for the specified message type. + /// + /// + /// The type of the message. + /// + /// + /// An ordered collection of as many as three non-null, non-empty alphanumeric textual labels to append to the path, or + /// to omit path labels. The default value is . + /// + /// + /// A queue entity path for the specified message type. + /// + /// + /// contains one or more null or empty labels and/or labels with non-alphanumeric characters, + /// or contains more than three elements. + /// + public IMessagingEntityPath GetQueuePath(IEnumerable pathLabels) + where TMessage : class => GetEntityPath(EntityPathQueuePrefix, typeof(TMessage), pathLabels); + /// /// Gets a shared, managed, implementation-specific message receiver for a type-defined queue. /// @@ -140,6 +160,56 @@ public TSender GetQueueSender() public TSender GetQueueSender(IEnumerable pathLabels) where TMessage : class => GetMessageSender(MessagingEntityType.Queue, pathLabels); + /// + /// Returns a subscription name for the specified message type. + /// + /// + /// The type of the message. + /// + /// + /// A unique textual identifier for the message receiver, or if the receiver is unspecified. + /// + /// + /// The unique path for the entity. + /// + /// + /// A subscription name for the specified message type. + /// + /// + /// is empty. + /// + /// + /// is -or- is + /// . + /// + public String GetSubscriptionName(String receiverIdentifier, IMessagingEntityPath entityPath) + where TMessage : class + { + var prefix = SubscriptionNamePrefix.IsNullOrEmpty() ? String.Empty : $"{SubscriptionNamePrefix}{MessagingEntityPath.DelimitingCharacterForPrefix}"; + var suffix = $"{MessagingEntityPath.DelimitingCharacterForLabelToken}{new ZBase32Encoding().GetString(entityPath.RejectIf().IsNull(nameof(entityPath)).GetHashCode().ToByteArray())}"; + return $"{prefix}{receiverIdentifier.RejectIf().IsNullOrEmpty(nameof(receiverIdentifier))}{suffix}"; + } + + /// + /// Returns a topic entity path for the specified message type. + /// + /// + /// The type of the message. + /// + /// + /// An ordered collection of as many as three non-null, non-empty alphanumeric textual labels to append to the path, or + /// to omit path labels. The default value is . + /// + /// + /// A topic entity path for the specified message type. + /// + /// + /// contains one or more null or empty labels and/or labels with non-alphanumeric characters, + /// or contains more than three elements. + /// + public IMessagingEntityPath GetTopicPath(IEnumerable pathLabels) + where TMessage : class => GetEntityPath(EntityPathTopicPrefix, typeof(TMessage), pathLabels); + /// /// Gets a shared, managed, implementation-specific message receiver for a type-defined topic. /// @@ -408,9 +478,7 @@ private TReceiver GetMessageReceiver(MessagingEntityType entityType, S try { - var subscriptionNamePrefix = SubscriptionNamePrefix.IsNullOrEmpty() ? String.Empty : $"{SubscriptionNamePrefix}{MessagingEntityPath.DelimitingCharacterForPrefix}"; - var entityPathHashSuffix = $"{MessagingEntityPath.DelimitingCharacterForLabelToken}{new ZBase32Encoding().GetString(entityPath.GetHashCode().ToByteArray())}"; - var subscriptionName = receiverIdentifier.IsNullOrEmpty() ? null : $"{subscriptionNamePrefix}{receiverIdentifier}{entityPathHashSuffix}"; + var subscriptionName = receiverIdentifier.IsNullOrEmpty() ? null : GetSubscriptionName(receiverIdentifier, entityPath); receiver = CreateMessageReceiver(Connection, entityType, entityPath, subscriptionName); } catch (Exception exception) @@ -478,48 +546,6 @@ private TSender GetMessageSender(MessagingEntityType entityType, IEnum } } - /// - /// Returns a queue entity path for the specified message type. - /// - /// - /// The type of the message. - /// - /// - /// An ordered collection of as many as three non-null, non-empty alphanumeric textual labels to append to the path, or - /// to omit path labels. The default value is . - /// - /// - /// A queue entity path for the specified message type. - /// - /// - /// contains one or more null or empty labels and/or labels with non-alphanumeric characters, - /// or contains more than three elements. - /// - [DebuggerHidden] - private IMessagingEntityPath GetQueuePath(IEnumerable pathLabels) - where TMessage : class => GetEntityPath(EntityPathQueuePrefix, typeof(TMessage), pathLabels); - - /// - /// Returns a topic entity path for the specified message type. - /// - /// - /// The type of the message. - /// - /// - /// An ordered collection of as many as three non-null, non-empty alphanumeric textual labels to append to the path, or - /// to omit path labels. The default value is . - /// - /// - /// A topic entity path for the specified message type. - /// - /// - /// contains one or more null or empty labels and/or labels with non-alphanumeric characters, - /// or contains more than three elements. - /// - [DebuggerHidden] - private IMessagingEntityPath GetTopicPath(IEnumerable pathLabels) - where TMessage : class => GetEntityPath(EntityPathTopicPrefix, typeof(TMessage), pathLabels); - /// /// Gets an entity path prefix for queues. /// diff --git a/src/RapidField.SolidInstruments.Messaging/RapidField.SolidInstruments.Messaging.csproj b/src/RapidField.SolidInstruments.Messaging/RapidField.SolidInstruments.Messaging.csproj index 68e1ff71..4f58ebfc 100644 --- a/src/RapidField.SolidInstruments.Messaging/RapidField.SolidInstruments.Messaging.csproj +++ b/src/RapidField.SolidInstruments.Messaging/RapidField.SolidInstruments.Messaging.csproj @@ -47,7 +47,4 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessageQueue.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessageQueue.cs deleted file mode 100644 index 5e5c04b6..00000000 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessageQueue.cs +++ /dev/null @@ -1,943 +0,0 @@ -// ================================================================================================================================= -// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. -// ================================================================================================================================= - -using RapidField.SolidInstruments.Core; -using RapidField.SolidInstruments.Core.ArgumentValidation; -using RapidField.SolidInstruments.Core.Concurrency; -using RapidField.SolidInstruments.Serialization; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace RapidField.SolidInstruments.Messaging.TransportPrimitives -{ - /// - /// Represents a durable message queue. - /// - /// - /// is the default implementation of . - /// - public sealed class DurableMessageQueue : Instrument, IDurableMessageQueue - { - /// - /// Initializes a new instance of the class. - /// - /// - /// An object that manages thread-safe persistence for the queue. - /// - /// - /// is . - /// - [DebuggerHidden] - internal DurableMessageQueue(IDurableMessageQueuePersistenceProxy persistenceProxy) - : this(Guid.NewGuid(), persistenceProxy) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// A unique identifier for the queue. - /// - /// - /// An object that manages thread-safe persistence for the queue. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - [DebuggerHidden] - internal DurableMessageQueue(Guid identifier, IDurableMessageQueuePersistenceProxy persistenceProxy) - : this(identifier, new MessagingEntityPath(), persistenceProxy) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// A unique identifier for the queue. - /// - /// - /// A unique textual path that identifies the queue. - /// - /// - /// An object that manages thread-safe persistence for the queue. - /// - /// - /// is -or- is - /// . - /// - /// - /// is equal to . - /// - [DebuggerHidden] - internal DurableMessageQueue(Guid identifier, IMessagingEntityPath path, IDurableMessageQueuePersistenceProxy persistenceProxy) - : this(identifier, path, persistenceProxy, DurableMessageQueueOperationalState.Ready) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// A unique identifier for the queue. - /// - /// - /// A unique textual path that identifies the queue. - /// - /// - /// An object that manages thread-safe persistence for the queue. - /// - /// - /// The operational state of the queue. The default value is . - /// - /// - /// is -or- is - /// . - /// - /// - /// is equal to -or- is equal - /// to . - /// - [DebuggerHidden] - internal DurableMessageQueue(Guid identifier, IMessagingEntityPath path, IDurableMessageQueuePersistenceProxy persistenceProxy, DurableMessageQueueOperationalState operationalState) - : this(identifier, path, persistenceProxy, operationalState, DefaultMessageBodySerializationFormat) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// A unique identifier for the queue. - /// - /// - /// A unique textual path that identifies the queue. - /// - /// - /// An object that manages thread-safe persistence for the queue. - /// - /// - /// The operational state of the queue. The default value is . - /// - /// - /// The format that is used to serialize enqueued message bodies. The default value is - /// . - /// - /// - /// is -or- is - /// . - /// - /// - /// is equal to -or- is equal - /// to -or- - /// is equal to . - /// - [DebuggerHidden] - internal DurableMessageQueue(Guid identifier, IMessagingEntityPath path, IDurableMessageQueuePersistenceProxy persistenceProxy, DurableMessageQueueOperationalState operationalState, SerializationFormat messageBodySerializationFormat) - : this(identifier, path, persistenceProxy, operationalState, messageBodySerializationFormat, DefaultMessageLockExpirationThreshold) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// A unique identifier for the queue. - /// - /// - /// A unique textual path that identifies the queue. - /// - /// - /// An object that manages thread-safe persistence for the queue. - /// - /// - /// The operational state of the queue. The default value is . - /// - /// - /// The format that is used to serialize enqueued message bodies. The default value is - /// . - /// - /// - /// The length of time that a locked message is held before abandoning the associated token and making the message available - /// for processing. The default value is three minutes. - /// - /// - /// is -or- is - /// . - /// - /// - /// is equal to -or- is equal - /// to -or- - /// is equal to -or- - /// is less than eight seconds. - /// - [DebuggerHidden] - internal DurableMessageQueue(Guid identifier, IMessagingEntityPath path, IDurableMessageQueuePersistenceProxy persistenceProxy, DurableMessageQueueOperationalState operationalState, SerializationFormat messageBodySerializationFormat, TimeSpan messageLockExpirationThreshold) - : this(identifier, path, persistenceProxy, operationalState, messageBodySerializationFormat, messageLockExpirationThreshold, DefaultEnqueueTimeoutThreshold) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// A unique identifier for the queue. - /// - /// - /// A unique textual path that identifies the queue. - /// - /// - /// An object that manages thread-safe persistence for the queue. - /// - /// - /// The operational state of the queue. The default value is . - /// - /// - /// The format that is used to serialize enqueued message bodies. The default value is - /// . - /// - /// - /// The length of time that a locked message is held before abandoning the associated token and making the message available - /// for processing. The default value is three minutes. - /// - /// - /// The maximum length of time to wait for a message to be enqueued before raising an exception. The default value is eight - /// seconds. - /// - /// - /// is -or- is - /// . - /// - /// - /// is equal to -or- is equal - /// to -or- - /// is equal to -or- - /// is less than eight seconds -or- - /// is less than two seconds. - /// - [DebuggerHidden] - internal DurableMessageQueue(Guid identifier, IMessagingEntityPath path, IDurableMessageQueuePersistenceProxy persistenceProxy, DurableMessageQueueOperationalState operationalState, SerializationFormat messageBodySerializationFormat, TimeSpan messageLockExpirationThreshold, TimeSpan enqueueTimeoutThreshold) - : this(identifier, path, persistenceProxy, operationalState, messageBodySerializationFormat, messageLockExpirationThreshold, enqueueTimeoutThreshold, Array.Empty(), new Dictionary()) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// A unique identifier for the queue. - /// - /// - /// A unique textual path that identifies the queue. - /// - /// - /// An object that manages thread-safe persistence for the queue. - /// - /// - /// The operational state of the queue. The default value is . - /// - /// - /// The format that is used to serialize enqueued message bodies. The default value is - /// . - /// - /// - /// The length of time that a locked message is held before abandoning the associated token and making the message available - /// for processing. The default value is three minutes. - /// - /// - /// The maximum length of time to wait for a message to be enqueued before raising an exception. The default value is eight - /// seconds. - /// - /// - /// The underlying first-in first-out collection that contains enqueued messages. - /// - /// - /// The underlying first-in first-out collection that contains messages that are locked for processing. - /// - /// - /// is -or- is - /// -or- is -or- is - /// . - /// - /// - /// is equal to -or- is equal - /// to -or- - /// is equal to -or- - /// is less than eight seconds -or- - /// is less than two seconds. - /// - [DebuggerHidden] - private DurableMessageQueue(Guid identifier, IMessagingEntityPath path, IDurableMessageQueuePersistenceProxy persistenceProxy, DurableMessageQueueOperationalState operationalState, SerializationFormat messageBodySerializationFormat, TimeSpan messageLockExpirationThreshold, TimeSpan enqueueTimeoutThreshold, IEnumerable messages, IDictionary lockedMessages) - : base() - { - EnqueueTimeoutThreshold = enqueueTimeoutThreshold.RejectIf().IsLessThan(EnqueueTimeoutThresholdFloor, nameof(enqueueTimeoutThreshold)); - Identifier = identifier.RejectIf().IsEqualToValue(Guid.Empty, nameof(identifier)); - LockedMessages = new ConcurrentDictionary(lockedMessages.RejectIf().IsNull(nameof(lockedMessages)).TargetArgument); - Messages = new ConcurrentQueue(messages.RejectIf().IsNull(nameof(messages)).TargetArgument); - MessageBodySerializationFormat = messageBodySerializationFormat.RejectIf().IsEqualToValue(SerializationFormat.Unspecified, nameof(messageBodySerializationFormat)); - MessageLockExpirationThreshold = messageLockExpirationThreshold.RejectIf().IsLessThan(MessageLockExpirationThresholdFloor, nameof(messageLockExpirationThreshold)); - OperationalState = operationalState.RejectIf().IsEqualToValue(DurableMessageQueueOperationalState.Unspecified, nameof(operationalState)); - Path = path.RejectIf().IsNull(nameof(path)).TargetArgument; - PersistenceProxy = persistenceProxy.RejectIf().IsNull(nameof(persistenceProxy)).TargetArgument; - } - - /// - /// Asynchronously notifies the queue that a locked message was not processed and can be made available for processing by - /// other consumers. - /// - /// - /// A lock token corresponding to a message that was not processed. - /// - /// - /// A task representing the asynchronous operation. - /// - /// - /// does not reference an existing locked message. - /// - /// - /// is . - /// - /// - /// An exception was raised while attempting to persist the snapshot. - /// - /// - /// The object is disposed. - /// - /// - /// The operation timed out. - /// - public Task ConveyFailureAsync(DurableMessageLockToken lockToken) - { - RejectIfDisposed(); - - if (LockedMessages.TryRemove(lockToken.RejectIf().IsNull(nameof(lockToken)), out var message)) - { - return RequeueAsync(message); - } - - throw new ArgumentException("The specified lock token does not reference an existing locked message in the queue.", nameof(lockToken)); - } - - /// - /// Asynchronously notifies the queue that a locked message was processed successfully and can be destroyed permanently. - /// - /// - /// A lock token corresponding to a message that was processed successfully. - /// - /// - /// A task representing the asynchronous operation. - /// - /// - /// does not reference an existing locked message. - /// - /// - /// is . - /// - /// - /// An exception was raised while attempting to persist the snapshot. - /// - /// - /// The object is disposed. - /// - public Task ConveySuccessAsync(DurableMessageLockToken lockToken) - { - RejectIfDisposed(); - - if (LockedMessages.TryRemove(lockToken.RejectIf().IsNull(nameof(lockToken)), out _)) - { - return Task.CompletedTask; - } - - throw new ArgumentException("The specified lock token does not reference an existing locked message in the queue.", nameof(lockToken)); - } - - /// - /// Asynchronously and non-destructively returns the next available messages from the queue, if any, up to the specified - /// maximum count. - /// - /// - /// The maximum number of messages to read from the queue. - /// - /// - /// A task representing the asynchronous operation and containing the next available messages from the queue, or an empty - /// collection if no messages are available. - /// - /// - /// is less than zero. - /// - /// - /// An exception was raised while attempting to persist the snapshot. - /// - /// - /// The object is disposed. - /// - /// - /// The operation timed out. - /// - public Task> DequeueAsync(Int32 count) - { - RejectIfDisposed(); - - if (count.RejectIf().IsLessThan(0, nameof(count)) == 0) - { - return Task.FromResult>(Array.Empty()); - } - - return Task.Factory.StartNew>(() => - { - var messageList = new List(); - var requeueTasks = new List(); - - while (messageList.Count() < count && TryDequeue(out var message)) - { - var lockToken = new DurableMessageLockToken(Guid.NewGuid(), message.Identifier, TimeStamp.Current.Add(MessageLockExpirationThreshold)); - message.LockToken = lockToken; - - if (LockedMessages.TryAdd(lockToken, message)) - { - messageList.Add(message); - continue; - } - - requeueTasks.Add(RequeueAsync(message)); - } - - Task.WaitAll(requeueTasks.ToArray()); - return messageList; - }); - } - - /// - /// Asynchronously enqueues the specified message. - /// - /// - /// The message to enqueue. - /// - /// - /// A task representing the asynchronous operation. - /// - /// - /// is . - /// - /// - /// An exception was raised while attempting to persist the snapshot. - /// - /// - /// The object is disposed. - /// - /// - /// The operation timed out. - /// - public Task EnqueueAsync(IMessageBase message) - { - var lockToken = new DurableMessageLockToken(Guid.NewGuid(), message.RejectIf().IsNull(nameof(message)).TargetArgument.Identifier); - var durableMessage = new DurableMessage(message, lockToken, MessageBodySerializationFormat); - return EnqueueAsync(durableMessage); - } - - /// - /// Converts the value of the current to its equivalent string representation. - /// - /// - /// A string representation of the current . - /// - public sealed override String ToString() => $"{{ {nameof(Path)}: {Path} }}"; - - /// - /// Attempts to set the operational state of the current to - /// , or to - /// if the previous state was - /// . - /// - /// - /// True if the operational state was successfully set, otherwise false. - /// - public Boolean TryDisableDequeues() - { - switch (OperationalState) - { - case DurableMessageQueueOperationalState.DequeueOnly: - - OperationalState = DurableMessageQueueOperationalState.Paused; - return true; - - case DurableMessageQueueOperationalState.EnqueueOnly: - - return true; - - case DurableMessageQueueOperationalState.Paused: - - return true; - - case DurableMessageQueueOperationalState.Ready: - - OperationalState = DurableMessageQueueOperationalState.EnqueueOnly; - return true; - - default: - - return false; - } - } - - /// - /// Attempts to set the operational state of the current to - /// , or to - /// if the previous state was - /// . - /// - /// - /// True if the operational state was successfully set, otherwise false. - /// - public Boolean TryDisableEnqueues() - { - switch (OperationalState) - { - case DurableMessageQueueOperationalState.DequeueOnly: - - return true; - - case DurableMessageQueueOperationalState.EnqueueOnly: - - OperationalState = DurableMessageQueueOperationalState.Paused; - return true; - - case DurableMessageQueueOperationalState.Paused: - - return true; - - case DurableMessageQueueOperationalState.Ready: - - OperationalState = DurableMessageQueueOperationalState.DequeueOnly; - return true; - - default: - - return false; - } - } - - /// - /// Attempts to set the operational state of the current to - /// . - /// - /// - /// True if the operational state was successfully set, otherwise false. - /// - public Boolean TryPause() - { - switch (OperationalState) - { - case DurableMessageQueueOperationalState.DequeueOnly: - - OperationalState = DurableMessageQueueOperationalState.Paused; - return true; - - case DurableMessageQueueOperationalState.EnqueueOnly: - - OperationalState = DurableMessageQueueOperationalState.Paused; - return true; - - case DurableMessageQueueOperationalState.Paused: - - return true; - - case DurableMessageQueueOperationalState.Ready: - - OperationalState = DurableMessageQueueOperationalState.Paused; - return true; - - default: - - return false; - } - } - - /// - /// Attempts to set the operational state of the current to - /// . - /// - /// - /// True if the operational state was successfully set, otherwise false. - /// - public Boolean TryResume() - { - switch (OperationalState) - { - case DurableMessageQueueOperationalState.DequeueOnly: - - OperationalState = DurableMessageQueueOperationalState.Ready; - return true; - - case DurableMessageQueueOperationalState.EnqueueOnly: - - OperationalState = DurableMessageQueueOperationalState.Ready; - return true; - - case DurableMessageQueueOperationalState.Paused: - - OperationalState = DurableMessageQueueOperationalState.Ready; - return true; - - case DurableMessageQueueOperationalState.Ready: - - return true; - - default: - - return false; - } - } - - /// - /// Releases all resources consumed by the current . - /// - /// - /// A value indicating whether or not managed resources should be released. - /// - protected override void Dispose(Boolean disposing) - { - try - { - if (disposing) - { - OperationalState = DurableMessageQueueOperationalState.Disabled; - } - } - finally - { - base.Dispose(disposing); - } - } - - /// - /// Produces a serializable persistence snapshot of the current in a thread-safe manner. - /// - /// - /// A token that represents and manages contextual thread safety. - /// - /// - /// A serializable persistence snapshot of the current . - /// - [DebuggerHidden] - private DurableMessageQueueSnapshot CaptureSnapshot(ConcurrencyControlToken controlToken) => new DurableMessageQueueSnapshot(this); - - /// - /// Asynchronously produces a serializable persistence snapshot of the current in a - /// thread-safe manner. - /// - /// - /// A task representing the asynchronous operation and containing a serializable persistence snapshot of the current - /// . - /// - /// - /// The object is disposed. - /// - [DebuggerHidden] - private Task CaptureSnapshotAsync() - { - RejectIfDisposed(); - - using (var controlToken = StateControl.Enter()) - { - RejectIfDisposed(); - var snapshot = CaptureSnapshot(controlToken); - return Task.FromResult(snapshot); - } - } - - /// - /// Asynchronously enqueues the specified message. - /// - /// - /// The message to enqueue. - /// - /// - /// A task representing the asynchronous operation. - /// - /// - /// An exception was raised while attempting to persist the snapshot. - /// - /// - /// The object is disposed. - /// - /// - /// The operation timed out. - /// - [DebuggerHidden] - private Task EnqueueAsync(DurableMessage message) - { - if (TryEnqueue(message)) - { - return Task.CompletedTask; - } - - var stopwatch = new Stopwatch(); - stopwatch.Start(); - - return Task.Factory.StartNew(() => - { - try - { - while (stopwatch.Elapsed < EnqueueTimeoutThreshold) - { - RejectIfDisposed(); - Thread.Sleep(EnqueueDelayDuration); - - if (TryEnqueue(message)) - { - return; - } - } - } - finally - { - stopwatch.Stop(); - } - - throw new TimeoutException($"The timeout threshold duration was exceeded while waiting for queue availability (path: {Path})."); - }); - } - - /// - /// Asynchronously requeues a previously-enqueued message. - /// - /// - /// The message to requeue. - /// - /// - /// A task representing the asynchronous operation. - /// - /// - /// is . - /// - /// - /// An exception was raised while attempting to persist the snapshot. - /// - /// - /// The object is disposed. - /// - /// - /// The operation timed out. - /// - [DebuggerHidden] - private Task RequeueAsync(DurableMessage message) - { - var lockToken = new DurableMessageLockToken(Guid.NewGuid(), message.RejectIf().IsNull(nameof(message)).TargetArgument.Identifier); - message.LockToken = lockToken; - return EnqueueAsync(message); - } - - /// - /// Attempts to remove and return the next message in the queue. - /// - /// - /// The resulting message. - /// - /// - /// A value indicating whether or not the operation was successful. - /// - [DebuggerHidden] - private Boolean TryDequeue(out DurableMessage message) - { - if (IsDisposedOrDisposing) - { - message = null; - return false; - } - - switch (OperationalState) - { - case DurableMessageQueueOperationalState.DequeueOnly: - - return Messages.TryDequeue(out message); - - case DurableMessageQueueOperationalState.Ready: - - return Messages.TryDequeue(out message); - - default: - - message = null; - return false; - } - } - - /// - /// Attempts to enqueue the specified message. - /// - /// - /// The message to enqueue. - /// - /// - /// A value indicating whether or not the operation was successful. - /// - [DebuggerHidden] - private Boolean TryEnqueue(DurableMessage message) - { - if (IsDisposedOrDisposing) - { - return false; - } - else if (message is null) - { - return false; - } - - switch (OperationalState) - { - case DurableMessageQueueOperationalState.EnqueueOnly: - - Messages.Enqueue(message); - return true; - - case DurableMessageQueueOperationalState.Ready: - - Messages.Enqueue(message); - return true; - - default: - - return false; - } - } - - /// - /// Gets the number of messages in the current . - /// - public Int32 Depth => Messages.Count + LockedMessages.Count; - - /// - /// Gets the maximum length of time to wait for a message to be enqueued before raising an exception. - /// - public TimeSpan EnqueueTimeoutThreshold - { - get; - private set; - } - - /// - /// Gets a unique identifier for the current . - /// - public Guid Identifier - { - get; - } - - /// - /// Gets a value indicating whether or not the current is empty. - /// - public Boolean IsEmpty => Depth == 0; - - /// - /// Gets the format that is used to serialize enqueued message bodies. - /// - public SerializationFormat MessageBodySerializationFormat - { - get; - private set; - } - - /// - /// Gets the length of time that a locked message is held before abandoning the associated token and making the message - /// available for processing. - /// - public TimeSpan MessageLockExpirationThreshold - { - get; - private set; - } - - /// - /// Gets the operational state of the current . - /// - public DurableMessageQueueOperationalState OperationalState - { - get; - private set; - } - - /// - /// Gets a unique textual path that identifies the current . - /// - public IMessagingEntityPath Path - { - get; - internal set; - } - - /// - /// Represents the underlying first-in first-out collection that contains messages that are locked for processing. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - internal readonly ConcurrentDictionary LockedMessages; - - /// - /// Represents the underlying first-in first-out collection that contains enqueued messages. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - internal readonly ConcurrentQueue Messages; - - /// - /// Represents the default format that is used to serialize enqueued message bodies. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const SerializationFormat DefaultMessageBodySerializationFormat = DurableMessage.DefaultBodySerializationFormat; - - /// - /// Represents the default length of time to wait for a message to be enqueued before raising an exception. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private static readonly TimeSpan DefaultEnqueueTimeoutThreshold = TimeSpan.FromSeconds(11); - - /// - /// Represents the default length of time that a locked message is held before abandoning the associated token and making - /// the message available for processing. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private static readonly TimeSpan DefaultMessageLockExpirationThreshold = TimeSpan.FromMinutes(3); - - /// - /// Represents the length of time that waits between attempts when the queue is - /// unavailable. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private static readonly TimeSpan EnqueueDelayDuration = TimeSpan.FromMilliseconds(3); - - /// - /// Represents the minimum permissible length of time to wait for a message to be enqueued before raising an exception. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private static readonly TimeSpan EnqueueTimeoutThresholdFloor = TimeSpan.FromSeconds(2); - - /// - /// Represents the minimum permissible length of time that a locked message is held before abandoning the associated token - /// and making the message available for processing. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private static readonly TimeSpan MessageLockExpirationThresholdFloor = TimeSpan.FromSeconds(8); - - /// - /// Represents an object that manages thread-safe persistence for the queue. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly IDurableMessageQueuePersistenceProxy PersistenceProxy; - } -} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessageQueueFactory.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessageQueueFactory.cs deleted file mode 100644 index d1e585bb..00000000 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessageQueueFactory.cs +++ /dev/null @@ -1,51 +0,0 @@ -// ================================================================================================================================= -// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. -// ================================================================================================================================= - -using Microsoft.Extensions.Configuration; -using RapidField.SolidInstruments.ObjectComposition; -using System; - -namespace RapidField.SolidInstruments.Messaging.TransportPrimitives -{ - /// - /// Encapsulates creation of new instances that map to database entities. - /// - /// - /// is the default implementation of . - /// - public abstract class DurableMessageQueueFactory : ObjectFactory, IDurableMessageQueueFactory - { - /// - /// Initializes a new instance of the class. - /// - protected DurableMessageQueueFactory() - : base() - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// Configuration information for the application. - /// - /// - /// is . - /// - protected DurableMessageQueueFactory(IConfiguration applicationConfiguration) - : base(applicationConfiguration) - { - return; - } - - /// - /// Releases all resources consumed by the current . - /// - /// - /// A value indicating whether or not managed resources should be released. - /// - protected override void Dispose(Boolean disposing) => base.Dispose(disposing); - } -} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessageQueuePersistenceException.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessageQueuePersistenceException.cs deleted file mode 100644 index bc5f9e36..00000000 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessageQueuePersistenceException.cs +++ /dev/null @@ -1,70 +0,0 @@ -// ================================================================================================================================= -// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. -// ================================================================================================================================= - -using System; -using System.Diagnostics; - -namespace RapidField.SolidInstruments.Messaging.TransportPrimitives -{ - /// - /// Represents an exception that is raised when an is unable to perform a - /// state persistence operation. - /// - public sealed class DurableMessageQueuePersistenceException : Exception - { - /// - /// Initializes a new instance of the class. - /// - public DurableMessageQueuePersistenceException() - : base(DefaultMessage) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The error message that explains the reason for the exception. - /// - public DurableMessageQueuePersistenceException(String message) - : base(message) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The exception that is the cause of the current exception. - /// - public DurableMessageQueuePersistenceException(Exception innerException) - : base(DefaultMessage, innerException) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The error message that explains the reason for the exception. - /// - /// - /// The exception that is the cause of the current exception. - /// - public DurableMessageQueuePersistenceException(String message, Exception innerException) - : base(message, innerException) - { - return; - } - - /// - /// Represents the default message for the exception. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const String DefaultMessage = "A queue state persistence operation failed."; - } -} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessageQueueSnapshot.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessageQueueSnapshot.cs deleted file mode 100644 index 1c908b19..00000000 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessageQueueSnapshot.cs +++ /dev/null @@ -1,139 +0,0 @@ -// ================================================================================================================================= -// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. -// ================================================================================================================================= - -using RapidField.SolidInstruments.Serialization; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Runtime.Serialization; - -namespace RapidField.SolidInstruments.Messaging.TransportPrimitives -{ - /// - /// Represents a serializable persistence snapshot of a durable message queue. - /// - [DataContract] - public sealed class DurableMessageQueueSnapshot - { - /// - /// Initializes a new instance of the class. - /// - public DurableMessageQueueSnapshot() - { - LockedMessages = new Dictionary(); - Messages = new List(); - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated message queue. - /// - [DebuggerHidden] - internal DurableMessageQueueSnapshot(DurableMessageQueue messageQueue) - { - EnqueueTimeoutThreshold = messageQueue.EnqueueTimeoutThreshold; - Identifier = messageQueue.Identifier; - LockedMessages = new Dictionary(messageQueue.LockedMessages); - MessageBodySerializationFormat = messageQueue.MessageBodySerializationFormat; - MessageLockExpirationThreshold = messageQueue.MessageLockExpirationThreshold; - Messages = new List(messageQueue.Messages.ToArray()); - OperationalState = messageQueue.OperationalState; - Path = messageQueue.Path.ToString(); - TimeStamp = Core.TimeStamp.Current; - } - - /// - /// Gets or sets the maximum length of time to wait for a message to be enqueued before raising an exception. - /// - [DataMember] - public TimeSpan EnqueueTimeoutThreshold - { - get; - set; - } - - /// - /// Gets or sets a unique identifier for the associated . - /// - [DataMember] - public Guid Identifier - { - get; - set; - } - - /// - /// Gets the first-in first-out collection that contains messages that are locked for processing by the associated - /// . - /// - [DataMember] - public IDictionary LockedMessages - { - get; - } - - /// - /// Gets or sets the format that is used to serialize enqueued message bodies. - /// - [DataMember] - public SerializationFormat MessageBodySerializationFormat - { - get; - set; - } - - /// - /// Gets or sets the length of time that a locked message is held before abandoning the associated token and making the - /// message available for processing. - /// - [DataMember] - public TimeSpan MessageLockExpirationThreshold - { - get; - set; - } - - /// - /// Gets the first-in first-out collection that contains enqueued messages for the associated - /// . - /// - [DataMember] - public ICollection Messages - { - get; - } - - /// - /// Gets or sets the operational state of the associated . - /// - [DataMember] - public DurableMessageQueueOperationalState OperationalState - { - get; - set; - } - - /// - /// Gets or sets a unique textual path that identifies the associated . - /// - [DataMember] - public String Path - { - get; - set; - } - - /// - /// Gets or sets the date and time when the current was created. - /// - [DataMember] - public DateTime TimeStamp - { - get; - set; - } - } -} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageQueueFactory.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageQueueFactory.cs deleted file mode 100644 index 44a4dc90..00000000 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageQueueFactory.cs +++ /dev/null @@ -1,15 +0,0 @@ -// ================================================================================================================================= -// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. -// ================================================================================================================================= - -using RapidField.SolidInstruments.ObjectComposition; - -namespace RapidField.SolidInstruments.Messaging.TransportPrimitives -{ - /// - /// Encapsulates creation of new instances. - /// - public interface IDurableMessageQueueFactory : IObjectFactory - { - } -} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageQueuePersistenceProxy.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageQueuePersistenceProxy.cs deleted file mode 100644 index be4cc1a2..00000000 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageQueuePersistenceProxy.cs +++ /dev/null @@ -1,76 +0,0 @@ -// ================================================================================================================================= -// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. -// ================================================================================================================================= - -using System; -using System.Threading.Tasks; - -namespace RapidField.SolidInstruments.Messaging.TransportPrimitives -{ - /// - /// Manages persistent state for instances. - /// - public interface IDurableMessageQueuePersistenceProxy : IAsyncDisposable, IDisposable - { - /// - /// Asynchronously persists the specified operation record. - /// - /// - /// The type of the operation record to persist. - /// - /// - /// The operation record to persist. - /// - /// - /// A task representing the asynchronous operation. - /// - /// - /// An exception was raised while attempting to persist the operation record. - /// - /// - /// The object is disposed. - /// - Task PersistOperationRecordAsync(TOperation operation) - where TOperation : class, IDurableMessageQueueOperation; - - /// - /// Asynchronously persists a thread-safe snapshot of the associated queue and flattens the persistent operation records - /// preceding it. - /// - /// - /// The snapshot to persist. - /// - /// - /// A task representing the asynchronous operation. - /// - /// - /// An exception was raised while attempting to persist the snapshot. - /// - /// - /// The object is disposed. - /// - Task PersistSnapshotAsync(DurableMessageQueueSnapshot snapshot); - - /// - /// Asynchronously persists a thread-safe snapshot of the associated queue and, optionally, flattens the persistent - /// operation records preceding it. - /// - /// - /// The snapshot to persist. - /// - /// - /// A value indicating whether or not all persisted operation records preceding the snapshot state are destroyed. The - /// default value is . - /// - /// - /// A task representing the asynchronous operation. - /// - /// - /// An exception was raised while attempting to persist the snapshot. - /// - /// - /// The object is disposed. - /// - Task PersistSnapshotAsync(DurableMessageQueueSnapshot snapshot, Boolean flattenOperationRecords); - } -} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageTransport.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageTransport.cs deleted file mode 100644 index f60f8c2e..00000000 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageTransport.cs +++ /dev/null @@ -1,39 +0,0 @@ -// ================================================================================================================================= -// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. -// ================================================================================================================================= - -using RapidField.SolidInstruments.Core; -using System; -using System.Threading.Tasks; - -namespace RapidField.SolidInstruments.Messaging.TransportPrimitives -{ - /// - /// Supports message exchange for a collection of queues and topics. - /// - public interface IDurableMessageTransport : IInstrument - { - /// - /// Asynchronously creates a new queue. - /// - /// - /// A unique textual path that identifies the new queue. - /// - /// - /// A task representing the asynchronous operation. - /// - /// - /// A - /// - /// - /// is empty. - /// - /// - /// is . - /// - /// - /// The object is disposed. - /// - Task CreateQueueAsync(String path); - } -} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageQueue.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageQueue.cs new file mode 100644 index 00000000..f09cf36f --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageQueue.cs @@ -0,0 +1,38 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace RapidField.SolidInstruments.Messaging.TransportPrimitives +{ + /// + /// Represents a message queue. + /// + internal interface IMessageQueue : IMessagingEntity + { + /// + /// Asynchronously and non-destructively returns the next available messages from the current , + /// if any, up to the specified maximum count. + /// + /// + /// The maximum number of messages to read from the queue. + /// + /// + /// A task representing the asynchronous operation and containing the next available messages from the queue, or an empty + /// collection if no messages are available. + /// + /// + /// is less than zero. + /// + /// + /// The object is disposed. + /// + /// + /// The operation timed out. + /// + Task> DequeueAsync(Int32 count); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTopic.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTopic.cs new file mode 100644 index 00000000..83b4dc11 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTopic.cs @@ -0,0 +1,110 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace RapidField.SolidInstruments.Messaging.TransportPrimitives +{ + /// + /// Represents a message topic. + /// + internal interface IMessageTopic : IMessagingEntity + { + /// + /// Asynchronously creates a new subscription to the current . + /// + /// + /// The unique name of the subscription. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// A subscription with the specified name already exists. + /// + /// + /// The object is disposed. + /// + Task CreateSubscriptionAsync(String subscriptionName); + + /// + /// Asynchronously and non-destructively returns the next available messages from the current , + /// if any, up to the specified maximum count. + /// + /// + /// The unique name of the subscription. + /// + /// + /// The maximum number of messages to read from the topic. + /// + /// + /// A task representing the asynchronous operation and containing the next available messages from the topic, or an empty + /// collection if no messages are available. + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// is less than zero. + /// + /// + /// The object is disposed. + /// + /// + /// The operation timed out. + /// + Task> DequeueAsync(String subscriptionName, Int32 count); + + /// + /// Asynchronously destroys the specified subscription to the current . + /// + /// + /// The unique name of the subscription. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// A subscription with the specified name does not exist. + /// + /// + /// The object is disposed. + /// + Task DestroySubscriptionAsync(String subscriptionName); + + /// + /// Gets the number of subscriptions to the current . + /// + Int32 SubscriptionCount + { + get; + } + + /// + /// Gets the unique names of the subscriptions to the current . + /// + IEnumerable SubscriptionNames + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransport.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransport.cs new file mode 100644 index 00000000..b8d1365e --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransport.cs @@ -0,0 +1,408 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Serialization; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace RapidField.SolidInstruments.Messaging.TransportPrimitives +{ + /// + /// Supports message exchange for a collection of queues and topics. + /// + internal interface IMessageTransport : IInstrument + { + /// + /// Opens and returns a new to the current . + /// + /// + /// A new to the current . + /// + /// + /// The object is disposed. + /// + IMessageTransportConnection CreateConnection(); + + /// + /// Asynchronously creates a new queue. + /// + /// + /// A unique textual path that identifies the new queue. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// An entity with the specified path already exists. + /// + Task CreateQueueAsync(IMessagingEntityPath path); + + /// + /// Asynchronously creates a new queue. + /// + /// + /// A unique textual path that identifies the new queue. + /// + /// + /// The length of time that a locked message is held before abandoning the associated token and making the message available + /// for processing. The default value is three minutes. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// is less than eight seconds. + /// + /// + /// The object is disposed. + /// + /// + /// An entity with the specified path already exists. + /// + Task CreateQueueAsync(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold); + + /// + /// Asynchronously creates a new queue. + /// + /// + /// A unique textual path that identifies the new queue. + /// + /// + /// The length of time that a locked message is held before abandoning the associated token and making the message available + /// for processing. The default value is three minutes. + /// + /// + /// The maximum length of time to wait for a message to be enqueued before raising an exception. The default value is eight + /// seconds. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// is less than eight seconds -or- + /// is less than two seconds. + /// + /// + /// The object is disposed. + /// + /// + /// An entity with the specified path already exists. + /// + Task CreateQueueAsync(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold, TimeSpan enqueueTimeoutThreshold); + + /// + /// Asynchronously creates a new topic. + /// + /// + /// A unique textual path that identifies the new topic. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// An entity with the specified path already exists. + /// + Task CreateTopicAsync(IMessagingEntityPath path); + + /// + /// Asynchronously creates a new topic. + /// + /// + /// A unique textual path that identifies the new topic. + /// + /// + /// The length of time that a locked message is held before abandoning the associated token and making the message available + /// for processing. The default value is three minutes. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// is less than eight seconds. + /// + /// + /// The object is disposed. + /// + /// + /// An entity with the specified path already exists. + /// + Task CreateTopicAsync(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold); + + /// + /// Asynchronously creates a new topic. + /// + /// + /// A unique textual path that identifies the new topic. + /// + /// + /// The length of time that a locked message is held before abandoning the associated token and making the message available + /// for processing. The default value is three minutes. + /// + /// + /// The maximum length of time to wait for a message to be enqueued before raising an exception. The default value is eight + /// seconds. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// is less than eight seconds -or- + /// is less than two seconds. + /// + /// + /// The object is disposed. + /// + /// + /// An entity with the specified path already exists. + /// + Task CreateTopicAsync(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold, TimeSpan enqueueTimeoutThreshold); + + /// + /// Asynchronously destroys the specified queue. + /// + /// + /// A unique textual path that identifies the queue. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// The specified entity does not exist. + /// + Task DestroyQueueAsync(IMessagingEntityPath path); + + /// + /// Asynchronously destroys the specified topic. + /// + /// + /// A unique textual path that identifies the topic. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// The specified entity does not exist. + /// + Task DestroyTopicAsync(IMessagingEntityPath path); + + /// + /// Returns a value indicating whether or not the specified queue exists. + /// + /// + /// A unique textual path that identifies the queue. + /// + /// + /// if the queue exists, otherwise . + /// + /// + /// The object is disposed. + /// + Boolean QueueExists(IMessagingEntityPath path); + + /// + /// Returns a value indicating whether or not the specified topic exists. + /// + /// + /// A unique textual path that identifies the topic. + /// + /// + /// if the topic exists, otherwise . + /// + /// + /// The object is disposed. + /// + Boolean TopicExists(IMessagingEntityPath path); + + /// + /// Attempts to create a new queue. + /// + /// + /// A unique textual path that identifies the new queue. + /// + /// + /// if the queue was successfully created, otherwise . + /// + Boolean TryCreateQueue(IMessagingEntityPath path); + + /// + /// Attempts to create a new queue. + /// + /// + /// A unique textual path that identifies the new queue. + /// + /// + /// The length of time that a locked message is held before abandoning the associated token and making the message available + /// for processing. The default value is three minutes. + /// + /// + /// if the queue was successfully created, otherwise . + /// + Boolean TryCreateQueue(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold); + + /// + /// Attempts to create a new queue. + /// + /// + /// A unique textual path that identifies the new queue. + /// + /// + /// The length of time that a locked message is held before abandoning the associated token and making the message available + /// for processing. The default value is three minutes. + /// + /// + /// The maximum length of time to wait for a message to be enqueued before raising an exception. The default value is eight + /// seconds. + /// + /// + /// if the queue was successfully created, otherwise . + /// + Boolean TryCreateQueue(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold, TimeSpan enqueueTimeoutThreshold); + + /// + /// Attempts to create a new topic. + /// + /// + /// A unique textual path that identifies the new topic. + /// + /// + /// if the topic was successfully created, otherwise . + /// + Boolean TryCreateTopic(IMessagingEntityPath path); + + /// + /// Attempts to create a new topic. + /// + /// + /// A unique textual path that identifies the new topic. + /// + /// + /// The length of time that a locked message is held before abandoning the associated token and making the message available + /// for processing. The default value is three minutes. + /// + /// + /// if the topic was successfully created, otherwise . + /// + Boolean TryCreateTopic(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold); + + /// + /// Attempts to create a new topic. + /// + /// + /// A unique textual path that identifies the new topic. + /// + /// + /// The length of time that a locked message is held before abandoning the associated token and making the message available + /// for processing. The default value is three minutes. + /// + /// + /// The maximum length of time to wait for a message to be enqueued before raising an exception. The default value is eight + /// seconds. + /// + /// + /// if the topic was successfully created, otherwise . + /// + Boolean TryCreateTopic(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold, TimeSpan enqueueTimeoutThreshold); + + /// + /// Attempts to destroy the specified queue. + /// + /// + /// A unique textual path that identifies the queue. + /// + /// + /// if the queue was successfully destroyed, otherwise . + /// + Boolean TryDestroyQueue(IMessagingEntityPath path); + + /// + /// Attempts to destroy the specified topic. + /// + /// + /// A unique textual path that identifies the topic. + /// + /// + /// if the topic was successfully destroyed, otherwise . + /// + Boolean TryDestroyTopic(IMessagingEntityPath path); + + /// + /// Gets a collection of active connections to the current . + /// + /// + /// The object is disposed. + /// + IEnumerable Connections + { + get; + } + + /// + /// Gets the format that is used to serialize enqueued message bodies. + /// + SerializationFormat MessageBodySerializationFormat + { + get; + } + + /// + /// Gets a collection of available queue paths for the current . + /// + /// + /// The object is disposed. + /// + IEnumerable QueuePaths + { + get; + } + + /// + /// Gets a collection of available topic paths for the current . + /// + /// + /// The object is disposed. + /// + IEnumerable TopicPaths + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransportConnection.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransportConnection.cs new file mode 100644 index 00000000..04d7a513 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransportConnection.cs @@ -0,0 +1,43 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.Messaging.TransportPrimitives +{ + /// + /// Represents a client connection to an . + /// + internal interface IMessageTransportConnection : IAsyncDisposable, IDisposable + { + /// + /// Closes the current as an idempotent operation. + /// + public void Close(); + + /// + /// Gets a value that uniquely identifies the current . + /// + Guid Identifier + { + get; + } + + /// + /// Gets the state of the current . + /// + MessageTransportConnectionState State + { + get; + } + + /// + /// Gets the associated . + /// + IMessageTransport Transport + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageQueue.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessagingEntity.cs similarity index 63% rename from src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageQueue.cs rename to src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessagingEntity.cs index 2851fac9..76f6a13e 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageQueue.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessagingEntity.cs @@ -10,13 +10,13 @@ namespace RapidField.SolidInstruments.Messaging.TransportPrimitives { /// - /// Represents a durable message queue. + /// Represents a messaging entity. /// - public interface IDurableMessageQueue : IAsyncDisposable, IDisposable + internal interface IMessagingEntity : IAsyncDisposable, IDisposable { /// - /// Asynchronously notifies the queue that a locked message was not processed and can be made available for processing by - /// other consumers. + /// Asynchronously notifies the current that a locked message was not processed and can be + /// made available for processing by other consumers. /// /// /// A lock token corresponding to a message that was not processed. @@ -24,14 +24,11 @@ public interface IDurableMessageQueue : IAsyncDisposable, IDisposable /// /// A task representing the asynchronous operation. /// - /// - /// does not reference an existing locked message. - /// /// /// is . /// - /// - /// An exception was raised while attempting to persist the snapshot. + /// + /// does not reference an existing locked message. /// /// /// The object is disposed. @@ -39,10 +36,11 @@ public interface IDurableMessageQueue : IAsyncDisposable, IDisposable /// /// The operation timed out. /// - Task ConveyFailureAsync(DurableMessageLockToken lockToken); + Task ConveyFailureAsync(MessageLockToken lockToken); /// - /// Asynchronously notifies the queue that a locked message was processed successfully and can be destroyed permanently. + /// Asynchronously notifies the current that a locked message was processed successfully and + /// can be destroyed permanently. /// /// /// A lock token corresponding to a message that was processed successfully. @@ -50,44 +48,16 @@ public interface IDurableMessageQueue : IAsyncDisposable, IDisposable /// /// A task representing the asynchronous operation. /// - /// - /// does not reference an existing locked message. - /// /// /// is . /// - /// - /// An exception was raised while attempting to persist the snapshot. - /// - /// - /// The object is disposed. - /// - Task ConveySuccessAsync(DurableMessageLockToken lockToken); - - /// - /// Asynchronously and non-destructively returns the next available messages from the queue, if any, up to the specified - /// maximum count. - /// - /// - /// The maximum number of messages to read from the queue. - /// - /// - /// A task representing the asynchronous operation and containing the next available messages from the queue, or an empty - /// collection if no messages are available. - /// - /// - /// is less than zero. - /// - /// - /// An exception was raised while attempting to persist the snapshot. + /// + /// does not reference an existing locked message. /// /// /// The object is disposed. /// - /// - /// The operation timed out. - /// - Task> DequeueAsync(Int32 count); + Task ConveySuccessAsync(MessageLockToken lockToken); /// /// Asynchronously enqueues the specified message. @@ -101,22 +71,18 @@ public interface IDurableMessageQueue : IAsyncDisposable, IDisposable /// /// is . /// - /// - /// An exception was raised while attempting to persist the snapshot. - /// /// /// The object is disposed. /// /// /// The operation timed out. /// - Task EnqueueAsync(IMessageBase message); + Task EnqueueAsync(PrimitiveMessage message); /// - /// Attempts to set the operational state of the current to - /// , or to - /// if the previous state was - /// . + /// Attempts to set the operational state of the current to + /// , or to + /// if the previous state was . /// /// /// True if the operational state was successfully set, otherwise false. @@ -124,10 +90,9 @@ public interface IDurableMessageQueue : IAsyncDisposable, IDisposable Boolean TryDisableDequeues(); /// - /// Attempts to set the operational state of the current to - /// , or to - /// if the previous state was - /// . + /// Attempts to set the operational state of the current to + /// , or to + /// if the previous state was . /// /// /// True if the operational state was successfully set, otherwise false. @@ -135,8 +100,8 @@ public interface IDurableMessageQueue : IAsyncDisposable, IDisposable Boolean TryDisableEnqueues(); /// - /// Attempts to set the operational state of the current to - /// . + /// Attempts to set the operational state of the current to + /// . /// /// /// True if the operational state was successfully set, otherwise false. @@ -144,8 +109,8 @@ public interface IDurableMessageQueue : IAsyncDisposable, IDisposable Boolean TryPause(); /// - /// Attempts to set the operational state of the current to - /// . + /// Attempts to set the operational state of the current to + /// . /// /// /// True if the operational state was successfully set, otherwise false. @@ -153,23 +118,23 @@ public interface IDurableMessageQueue : IAsyncDisposable, IDisposable Boolean TryResume(); /// - /// Gets the number of messages in the current . + /// Gets the maximum length of time to wait for a message to be enqueued before raising an exception. /// - Int32 Depth + TimeSpan EnqueueTimeoutThreshold { get; } /// - /// Gets the maximum length of time to wait for a message to be enqueued before raising an exception. + /// Gets the messaging entity type of the current . /// - TimeSpan EnqueueTimeoutThreshold + MessagingEntityType EntityType { get; } /// - /// Gets a unique identifier for the current . + /// Gets a unique identifier for the current . /// Guid Identifier { @@ -177,13 +142,24 @@ Guid Identifier } /// - /// Gets a value indicating whether or not the current is empty. + /// Gets a value indicating whether or not the current is empty. /// Boolean IsEmpty { get; } + /// + /// Gets a collection of exclusive processing locks for messages contained by the current . + /// + /// + /// The object is disposed. + /// + IEnumerable LockTokens + { + get; + } + /// /// Gets the format that is used to serialize enqueued message bodies. /// @@ -192,6 +168,14 @@ SerializationFormat MessageBodySerializationFormat get; } + /// + /// Gets the number of messages in the current . + /// + Int32 MessageCount + { + get; + } + /// /// Gets the length of time that a locked message is held before abandoning the associated token and making the message /// available for processing. @@ -202,15 +186,15 @@ TimeSpan MessageLockExpirationThreshold } /// - /// Gets the operational state of the current . + /// Gets the operational state of the current . /// - DurableMessageQueueOperationalState OperationalState + MessagingEntityOperationalState OperationalState { get; } /// - /// Gets a unique textual path that identifies the current . + /// Gets a unique textual path that identifies the current . /// IMessagingEntityPath Path { diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessageLockToken.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageLockToken.cs similarity index 64% rename from src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessageLockToken.cs rename to src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageLockToken.cs index ed0f88cb..809b9763 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessageLockToken.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageLockToken.cs @@ -11,23 +11,23 @@ namespace RapidField.SolidInstruments.Messaging.TransportPrimitives { /// - /// Represents an exclusive processing lock on a . + /// Represents an exclusive processing lock on a . /// [DataContract] - public sealed class DurableMessageLockToken : IComparable, IEquatable + public sealed class MessageLockToken : IComparable, IEquatable { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// [DebuggerHidden] - internal DurableMessageLockToken() + internal MessageLockToken() : this(default, default) { return; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// A unique identifier for the lock token. @@ -36,14 +36,14 @@ internal DurableMessageLockToken() /// The unique identifier for the associated, locked message. /// [DebuggerHidden] - internal DurableMessageLockToken(Guid identifier, Guid messageIdentifier) + internal MessageLockToken(Guid identifier, Guid messageIdentifier) : this(identifier, messageIdentifier, default) { return; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// A unique identifier for the lock token. @@ -55,7 +55,7 @@ internal DurableMessageLockToken(Guid identifier, Guid messageIdentifier) /// The date and time of expiration for the lock, after which the message will become available for processing. /// [DebuggerHidden] - internal DurableMessageLockToken(Guid identifier, Guid messageIdentifier, DateTime expirationDateTime) + internal MessageLockToken(Guid identifier, Guid messageIdentifier, DateTime expirationDateTime) { ExpirationDateTime = expirationDateTime; Identifier = identifier; @@ -63,63 +63,62 @@ internal DurableMessageLockToken(Guid identifier, Guid messageIdentifier, DateTi } /// - /// Determines whether or not two specified instances are not equal. + /// Determines whether or not two specified instances are not equal. /// /// - /// The first instance to compare. + /// The first instance to compare. /// /// - /// The second instance to compare. + /// The second instance to compare. /// /// /// A value indicating whether or not the specified instances are not equal. /// - public static Boolean operator !=(DurableMessageLockToken a, DurableMessageLockToken b) => (a == b) == false; + public static Boolean operator !=(MessageLockToken a, MessageLockToken b) => (a == b) == false; /// - /// Determines whether or not a specified instance is less than another specified - /// instance. + /// Determines whether or not a specified instance is less than another specified instance. /// /// - /// The first instance to compare. + /// The first instance to compare. /// /// - /// The second instance to compare. + /// The second instance to compare. /// /// /// if the second object is earlier than the first object, otherwise . /// - public static Boolean operator <(DurableMessageLockToken a, DurableMessageLockToken b) => a.CompareTo(b) == -1; + public static Boolean operator <(MessageLockToken a, MessageLockToken b) => a.CompareTo(b) == -1; /// - /// Determines whether or not a specified instance is less than or equal to another - /// supplied instance. + /// Determines whether or not a specified instance is less than or equal to another supplied + /// instance. /// /// - /// The first instance to compare. + /// The first instance to compare. /// /// - /// The second instance to compare. + /// The second instance to compare. /// /// /// if the second object is earlier than or equal to the first object, otherwise /// . /// - public static Boolean operator <=(DurableMessageLockToken a, DurableMessageLockToken b) => a.CompareTo(b) < 1; + public static Boolean operator <=(MessageLockToken a, MessageLockToken b) => a.CompareTo(b) < 1; /// - /// Determines whether or not two specified instances are equal. + /// Determines whether or not two specified instances are equal. /// /// - /// The first instance to compare. + /// The first instance to compare. /// /// - /// The second instance to compare. + /// The second instance to compare. /// /// /// A value indicating whether or not the specified instances are equal. /// - public static Boolean operator ==(DurableMessageLockToken a, DurableMessageLockToken b) + public static Boolean operator ==(MessageLockToken a, MessageLockToken b) { if (a is null && b is null) { @@ -134,48 +133,48 @@ internal DurableMessageLockToken(Guid identifier, Guid messageIdentifier, DateTi } /// - /// Determines whether or not a specified instance is greater than another specified + /// Determines whether or not a specified instance is greater than another specified /// instance. /// /// - /// The first instance to compare. + /// The first instance to compare. /// /// - /// The second instance to compare. + /// The second instance to compare. /// /// /// if the second object is later than the first object, otherwise . /// - public static Boolean operator >(DurableMessageLockToken a, DurableMessageLockToken b) => a.CompareTo(b) == 1; + public static Boolean operator >(MessageLockToken a, MessageLockToken b) => a.CompareTo(b) == 1; /// - /// Determines whether or not a specified instance is greater than or equal to - /// another supplied instance. + /// Determines whether or not a specified instance is greater than or equal to another + /// supplied instance. /// /// - /// The first instance to compare. + /// The first instance to compare. /// /// - /// The second instance to compare. + /// The second instance to compare. /// /// /// if the second object is later than or equal to the first object, otherwise /// . /// - public static Boolean operator >=(DurableMessageLockToken a, DurableMessageLockToken b) => a.CompareTo(b) > -1; + public static Boolean operator >=(MessageLockToken a, MessageLockToken b) => a.CompareTo(b) > -1; /// - /// Compares the current to the specified object and returns an indication of their - /// relative values. + /// Compares the current to the specified object and returns an indication of their relative + /// values. /// /// - /// The to compare to this instance. + /// The to compare to this instance. /// /// /// Negative one if this instance is earlier than the specified instance; one if this instance is later than the supplied /// instance; zero if they are equal. /// - public Int32 CompareTo(DurableMessageLockToken other) + public Int32 CompareTo(MessageLockToken other) { var identifierComparisonResult = Identifier.CompareTo(other.Identifier); @@ -195,8 +194,7 @@ public Int32 CompareTo(DurableMessageLockToken other) } /// - /// Determines whether or not the current is equal to the specified - /// . + /// Determines whether or not the current is equal to the specified . /// /// /// The to compare to this instance. @@ -210,24 +208,24 @@ public override Boolean Equals(Object obj) { return false; } - else if (obj is DurableMessageLockToken) + else if (obj is MessageLockToken) { - return Equals((DurableMessageLockToken)obj); + return Equals((MessageLockToken)obj); } return false; } /// - /// Determines whether or not two specified instances are equal. + /// Determines whether or not two specified instances are equal. /// /// - /// The to compare to this instance. + /// The to compare to this instance. /// /// /// A value indicating whether or not the specified instances are equal. /// - public Boolean Equals(DurableMessageLockToken other) + public Boolean Equals(MessageLockToken other) { if (ExpirationDateTime != other.ExpirationDateTime) { @@ -254,10 +252,10 @@ public Boolean Equals(DurableMessageLockToken other) public override Int32 GetHashCode() => ToByteArray().ComputeThirtyTwoBitHash(); /// - /// Converts the current to an array of bytes. + /// Converts the current to an array of bytes. /// /// - /// An array of bytes representing the current . + /// An array of bytes representing the current . /// public Byte[] ToByteArray() { @@ -269,10 +267,10 @@ public Byte[] ToByteArray() } /// - /// Converts the value of the current to its equivalent string representation. + /// Converts the value of the current to its equivalent string representation. /// /// - /// A string representation of the current . + /// A string representation of the current . /// public sealed override String ToString() => $"{{ {nameof(Identifier)}: {Identifier.ToSerializedString()}, {nameof(ExpirationDateTime)}: {ExpirationDateTime.ToSerializedString()} }}"; diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageQueue.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageQueue.cs new file mode 100644 index 00000000..57ec0c8a --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageQueue.cs @@ -0,0 +1,349 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Serialization; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading.Tasks; + +namespace RapidField.SolidInstruments.Messaging.TransportPrimitives +{ + /// + /// Represents a message queue. + /// + /// + /// is the default implementation of . + /// + internal sealed class MessageQueue : MessagingEntity, IMessageQueue + { + /// + /// Initializes a new instance of the class. + /// + [DebuggerHidden] + internal MessageQueue() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier for the queue. + /// + /// + /// is equal to . + /// + [DebuggerHidden] + internal MessageQueue(Guid identifier) + : base(identifier) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier for the queue. + /// + /// + /// A unique textual path that identifies the queue. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + [DebuggerHidden] + internal MessageQueue(Guid identifier, IMessagingEntityPath path) + : base(identifier, path) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier for the queue. + /// + /// + /// A unique textual path that identifies the queue. + /// + /// + /// The operational state of the queue. The default value is . + /// + /// + /// is . + /// + /// + /// is equal to -or- is equal + /// to . + /// + [DebuggerHidden] + internal MessageQueue(Guid identifier, IMessagingEntityPath path, MessagingEntityOperationalState operationalState) + : base(identifier, path, operationalState) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier for the queue. + /// + /// + /// A unique textual path that identifies the queue. + /// + /// + /// The operational state of the queue. The default value is . + /// + /// + /// The format that is used to serialize enqueued message bodies. The default value is + /// . + /// + /// + /// is . + /// + /// + /// is equal to -or- is equal + /// to -or- is + /// equal to . + /// + [DebuggerHidden] + internal MessageQueue(Guid identifier, IMessagingEntityPath path, MessagingEntityOperationalState operationalState, SerializationFormat messageBodySerializationFormat) + : base(identifier, path, operationalState, messageBodySerializationFormat) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier for the queue. + /// + /// + /// A unique textual path that identifies the queue. + /// + /// + /// The operational state of the queue. The default value is . + /// + /// + /// The format that is used to serialize enqueued message bodies. The default value is + /// . + /// + /// + /// The length of time that a locked message is held before abandoning the associated token and making the message available + /// for processing. The default value is three minutes. + /// + /// + /// is . + /// + /// + /// is equal to -or- is equal + /// to -or- is + /// equal to -or- is less + /// than eight seconds. + /// + [DebuggerHidden] + internal MessageQueue(Guid identifier, IMessagingEntityPath path, MessagingEntityOperationalState operationalState, SerializationFormat messageBodySerializationFormat, TimeSpan messageLockExpirationThreshold) + : base(identifier, path, operationalState, messageBodySerializationFormat, messageLockExpirationThreshold) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier for the queue. + /// + /// + /// A unique textual path that identifies the queue. + /// + /// + /// The operational state of the queue. The default value is . + /// + /// + /// The format that is used to serialize enqueued message bodies. The default value is + /// . + /// + /// + /// The length of time that a locked message is held before abandoning the associated token and making the message available + /// for processing. The default value is three minutes. + /// + /// + /// The maximum length of time to wait for a message to be enqueued before raising an exception. The default value is eight + /// seconds. + /// + /// + /// is . + /// + /// + /// is equal to -or- is equal + /// to -or- is + /// equal to -or- is less + /// than eight seconds -or- is less than two seconds. + /// + [DebuggerHidden] + internal MessageQueue(Guid identifier, IMessagingEntityPath path, MessagingEntityOperationalState operationalState, SerializationFormat messageBodySerializationFormat, TimeSpan messageLockExpirationThreshold, TimeSpan enqueueTimeoutThreshold) + : base(identifier, path, operationalState, messageBodySerializationFormat, messageLockExpirationThreshold, enqueueTimeoutThreshold) + { + return; + } + + /// + /// Asynchronously and non-destructively returns the next available messages from the queue, if any, up to the specified + /// maximum count. + /// + /// + /// The maximum number of messages to read from the queue. + /// + /// + /// A task representing the asynchronous operation and containing the next available messages from the queue, or an empty + /// collection if no messages are available. + /// + /// + /// is less than zero. + /// + /// + /// The object is disposed. + /// + /// + /// The operation timed out. + /// + public Task> DequeueAsync(Int32 count) => Task.Factory.StartNew(() => + { + return Dequeue(null, count); + }); + + /// + /// Attempts to add the specified message to a list of locked messages. + /// + /// + /// The unique name of the subscription, or if no subscription is specified. + /// + /// + /// The locked message. + /// + /// + /// if the message was added successfully, otherwise . + /// + protected internal sealed override Boolean TryAddLockedMessage(String subscriptionName, PrimitiveMessage message) => message?.LockToken is null ? false : LockedMessages.TryAdd(message.LockToken, message); + + /// + /// Attempts to remove and return the next message in the queue. + /// + /// + /// The unique name of the subscription, or if no subscription is specified. + /// + /// + /// The resulting message. + /// + /// + /// if the message was dequeued successfully, otherwise . + /// + protected internal sealed override Boolean TryDequeue(String subscriptionName, out PrimitiveMessage message) + { + if (Messages.TryDequeue(out var dequeuedMessage)) + { + message = dequeuedMessage; + return true; + } + + message = null; + return false; + } + + /// + /// Attempts to enqueue the specified message. + /// + /// + /// The message to enqueue. + /// + /// + /// if the message was enqueued successfully, otherwise . + /// + protected internal sealed override Boolean TryEnqueue(PrimitiveMessage message) + { + if (message is null) + { + return false; + } + + Messages.Enqueue(message); + return true; + } + + /// + /// Attempts to remove and return the locked message associated with the specified lock token. + /// + /// + /// A lock token corresponding to a locked message. + /// + /// + /// The resulting message. + /// + /// + /// if the message was removed successfully, otherwise . + /// + protected internal sealed override Boolean TryRemoveLockedMessage(MessageLockToken lockToken, out PrimitiveMessage message) + { + if (lockToken is null) + { + message = null; + return false; + } + + if (LockedMessages.TryRemove(lockToken, out var lockedMessage)) + { + message = lockedMessage; + return true; + } + + message = null; + return false; + } + + /// + /// Returns a collection of exclusive processing locks for messages contained by the current . + /// + /// + /// A collection of exclusive processing locks for messages contained by the current . + /// + protected override IEnumerable GetLockTokens() => LockedMessages.Keys; + + /// + /// Gets the number of messages in the current . + /// + /// + /// The number of messages in the current . + /// + protected sealed override Int32 GetMessageCount() => Messages.Count + LockedMessages.Count; + + /// + /// Gets the messaging entity type of the current . + /// + public override MessagingEntityType EntityType => MessagingEntityType.Queue; + + /// + /// Represents the underlying first-in first-out collection that contains messages that are locked for processing. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly ConcurrentDictionary LockedMessages = new ConcurrentDictionary(); + + /// + /// Represents the underlying first-in first-out collection that contains enqueued messages. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly ConcurrentQueue Messages = new ConcurrentQueue(); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTopic.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTopic.cs new file mode 100644 index 00000000..feffe7b5 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTopic.cs @@ -0,0 +1,457 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.Extensions; +using RapidField.SolidInstruments.Serialization; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; + +namespace RapidField.SolidInstruments.Messaging.TransportPrimitives +{ + /// + /// Represents a message topic. + /// + /// + /// is the default implementation of . + /// + internal sealed class MessageTopic : MessagingEntity, IMessageTopic + { + /// + /// Initializes a new instance of the class. + /// + [DebuggerHidden] + internal MessageTopic() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier for the topic. + /// + /// + /// is equal to . + /// + [DebuggerHidden] + internal MessageTopic(Guid identifier) + : base(identifier) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier for the topic. + /// + /// + /// A unique textual path that identifies the topic. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + [DebuggerHidden] + internal MessageTopic(Guid identifier, IMessagingEntityPath path) + : base(identifier, path) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier for the topic. + /// + /// + /// A unique textual path that identifies the topic. + /// + /// + /// The operational state of the topic. The default value is . + /// + /// + /// is . + /// + /// + /// is equal to -or- is equal + /// to . + /// + [DebuggerHidden] + internal MessageTopic(Guid identifier, IMessagingEntityPath path, MessagingEntityOperationalState operationalState) + : base(identifier, path, operationalState) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier for the topic. + /// + /// + /// A unique textual path that identifies the topic. + /// + /// + /// The operational state of the topic. The default value is . + /// + /// + /// The format that is used to serialize enqueued message bodies. The default value is + /// . + /// + /// + /// is . + /// + /// + /// is equal to -or- is equal + /// to -or- is + /// equal to . + /// + [DebuggerHidden] + internal MessageTopic(Guid identifier, IMessagingEntityPath path, MessagingEntityOperationalState operationalState, SerializationFormat messageBodySerializationFormat) + : base(identifier, path, operationalState, messageBodySerializationFormat) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier for the topic. + /// + /// + /// A unique textual path that identifies the topic. + /// + /// + /// The operational state of the topic. The default value is . + /// + /// + /// The format that is used to serialize enqueued message bodies. The default value is + /// . + /// + /// + /// The length of time that a locked message is held before abandoning the associated token and making the message available + /// for processing. The default value is three minutes. + /// + /// + /// is . + /// + /// + /// is equal to -or- is equal + /// to -or- is + /// equal to -or- is less + /// than eight seconds. + /// + [DebuggerHidden] + internal MessageTopic(Guid identifier, IMessagingEntityPath path, MessagingEntityOperationalState operationalState, SerializationFormat messageBodySerializationFormat, TimeSpan messageLockExpirationThreshold) + : base(identifier, path, operationalState, messageBodySerializationFormat, messageLockExpirationThreshold) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier for the topic. + /// + /// + /// A unique textual path that identifies the topic. + /// + /// + /// The operational state of the topic. The default value is . + /// + /// + /// The format that is used to serialize enqueued message bodies. The default value is + /// . + /// + /// + /// The length of time that a locked message is held before abandoning the associated token and making the message available + /// for processing. The default value is three minutes. + /// + /// + /// The maximum length of time to wait for a message to be enqueued before raising an exception. The default value is eight + /// seconds. + /// + /// + /// is . + /// + /// + /// is equal to -or- is equal + /// to -or- is + /// equal to -or- is less + /// than eight seconds -or- is less than two seconds. + /// + [DebuggerHidden] + internal MessageTopic(Guid identifier, IMessagingEntityPath path, MessagingEntityOperationalState operationalState, SerializationFormat messageBodySerializationFormat, TimeSpan messageLockExpirationThreshold, TimeSpan enqueueTimeoutThreshold) + : base(identifier, path, operationalState, messageBodySerializationFormat, messageLockExpirationThreshold, enqueueTimeoutThreshold) + { + return; + } + + /// + /// Asynchronously creates a new subscription to the current . + /// + /// + /// The unique name of the subscription. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// A subscription with the specified name already exists. + /// + /// + /// The object is disposed. + /// + public Task CreateSubscriptionAsync(String subscriptionName) => throw new NotImplementedException(); + + /// + /// Asynchronously and non-destructively returns the next available messages from the current , + /// if any, up to the specified maximum count. + /// + /// + /// The unique name of the subscription. + /// + /// + /// The maximum number of messages to read from the topic. + /// + /// + /// A task representing the asynchronous operation and containing the next available messages from the topic, or an empty + /// collection if no messages are available. + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// is less than zero. + /// + /// + /// The object is disposed. + /// + /// + /// The operation timed out. + /// + public Task> DequeueAsync(String subscriptionName, Int32 count) => throw new NotImplementedException(); + + /// + /// Asynchronously destroys the specified subscription to the current . + /// + /// + /// The unique name of the subscription. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// A subscription with the specified name does not exist. + /// + /// + /// The object is disposed. + /// + public Task DestroySubscriptionAsync(String subscriptionName) => throw new NotImplementedException(); + + /// + /// Attempts to add the specified message to a list of locked messages. + /// + /// + /// The unique name of the subscription, or if no subscription is specified. + /// + /// + /// The locked message. + /// + /// + /// if the message was added successfully, otherwise . + /// + protected internal sealed override Boolean TryAddLockedMessage(String subscriptionName, PrimitiveMessage message) + { + if (subscriptionName.IsNullOrEmpty()) + { + return false; + } + + if (message?.LockToken is null) + { + return false; + } + + if (SubscriptionQueues.TryGetValue(subscriptionName, out var queue)) + { + return queue?.TryAddLockedMessage(subscriptionName, message) ?? false; + } + + return false; + } + + /// + /// Attempts to remove and return the next message in the topic. + /// + /// + /// The unique name of the subscription, or if no subscription is specified. + /// + /// + /// The resulting message. + /// + /// + /// if the message was dequeued successfully, otherwise . + /// + protected internal sealed override Boolean TryDequeue(String subscriptionName, out PrimitiveMessage message) + { + if (subscriptionName.IsNullOrEmpty()) + { + message = null; + return false; + } + + if (SubscriptionQueues.TryGetValue(subscriptionName, out var queue)) + { + if (queue is null) + { + message = null; + return false; + } + + if (queue.TryDequeue(subscriptionName, out var dequeuedMessage)) + { + message = dequeuedMessage; + return true; + } + } + + message = null; + return false; + } + + /// + /// Attempts to enqueue the specified message. + /// + /// + /// The message to enqueue. + /// + /// + /// if the message was enqueued successfully, otherwise . + /// + protected internal sealed override Boolean TryEnqueue(PrimitiveMessage message) + { + if (message is null) + { + return false; + } + + foreach (var queue in SubscriptionQueues.Values) + { + if (queue.TryEnqueue(message) == false) + { + return false; + } + } + + return true; + } + + /// + /// Attempts to remove and return the locked message associated with the specified lock token. + /// + /// + /// A lock token corresponding to a locked message. + /// + /// + /// The resulting message. + /// + /// + /// if the message was removed successfully, otherwise . + /// + protected internal sealed override Boolean TryRemoveLockedMessage(MessageLockToken lockToken, out PrimitiveMessage message) + { + if (lockToken is null) + { + message = null; + return false; + } + + var queue = SubscriptionQueues.Values.FirstOrDefault(queue => queue.LockTokens.Contains(lockToken)); + + if (queue is null) + { + message = null; + return false; + } + + if (queue.TryRemoveLockedMessage(lockToken, out var lockedMessage)) + { + message = lockedMessage; + return true; + } + + message = null; + return false; + } + + /// + /// Returns a collection of exclusive processing locks for messages contained by the current . + /// + /// + /// A collection of exclusive processing locks for messages contained by the current . + /// + protected override IEnumerable GetLockTokens() => SubscriptionQueues.Values.SelectMany(queue => queue.LockTokens); + + /// + /// Gets the number of messages in the current . + /// + /// + /// The number of messages in the current . + /// + protected sealed override Int32 GetMessageCount() => SubscriptionQueues.Values.Select(queue => queue.MessageCount).Sum(); + + /// + /// Gets the messaging entity type of the current . + /// + public override MessagingEntityType EntityType => MessagingEntityType.Topic; + + /// + /// Gets the number of subscriptions to the current . + /// + public Int32 SubscriptionCount => SubscriptionQueues.Count; + + /// + /// Gets the unique names of the subscriptions to the current . + /// + public IEnumerable SubscriptionNames => SubscriptionQueues.Keys; + + /// + /// Represents a collection of queues for the subscriptions to the current , which are keyed by + /// subscription name. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly ConcurrentDictionary SubscriptionQueues = new ConcurrentDictionary(); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransportConnectionClosedException.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransportConnectionClosedException.cs new file mode 100644 index 00000000..fbeab719 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransportConnectionClosedException.cs @@ -0,0 +1,51 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.Messaging.TransportPrimitives +{ + /// + /// Represents an exception that is raised when an attempt is made to perform a messaging operation against a closed + /// . + /// + public class MessageTransportConnectionClosedException : MessagingException + { + /// + /// Initializes a new instance of the class. + /// + public MessageTransportConnectionClosedException() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The error message that explains the reason for the exception. + /// + public MessageTransportConnectionClosedException(String message) + : base(message) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The error message that explains the reason for the exception. + /// + /// + /// The exception that is the cause of the current exception. + /// + public MessageTransportConnectionClosedException(String message, Exception innerException) + : base(message, innerException) + { + return; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageQueueOperation.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransportConnectionState.cs similarity index 57% rename from src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageQueueOperation.cs rename to src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransportConnectionState.cs index c17dde56..de6aa6f7 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IDurableMessageQueueOperation.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransportConnectionState.cs @@ -7,24 +7,23 @@ namespace RapidField.SolidInstruments.Messaging.TransportPrimitives { /// - /// Represents an operation performed against an . + /// Represents the state of an . /// - public interface IDurableMessageQueueOperation + internal enum MessageTransportConnectionState : Int32 { /// - /// Gets an identifier for the most recent to which this operation applies. + /// The connection's state is not specified. /// - Guid SnapshotIdentifier - { - get; - } + Unspecified = 0, /// - /// Gets the date and time when the operation was recorded. + /// The connection is open and available for use. /// - DateTime TimeStamp - { - get; - } + Open = 1, + + /// + /// The connection is closed and unavailable for use. + /// + Closed = 2 } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessagingEntity.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessagingEntity.cs new file mode 100644 index 00000000..bbd5c271 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessagingEntity.cs @@ -0,0 +1,840 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Serialization; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace RapidField.SolidInstruments.Messaging.TransportPrimitives +{ + /// + /// Represents a messaging entity. + /// + /// + /// is the default implementation of . + /// + internal abstract class MessagingEntity : Instrument, IMessagingEntity + { + /// + /// Initializes a new instance of the class. + /// + protected MessagingEntity() + : this(Guid.NewGuid()) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier for the entity. + /// + /// + /// is equal to . + /// + protected MessagingEntity(Guid identifier) + : this(identifier, new MessagingEntityPath()) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier for the entity. + /// + /// + /// A unique textual path that identifies the entity. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + protected MessagingEntity(Guid identifier, IMessagingEntityPath path) + : this(identifier, path, MessagingEntityOperationalState.Ready) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier for the entity. + /// + /// + /// A unique textual path that identifies the entity. + /// + /// + /// The operational state of the entity. The default value is . + /// + /// + /// is . + /// + /// + /// is equal to -or- is equal + /// to . + /// + protected MessagingEntity(Guid identifier, IMessagingEntityPath path, MessagingEntityOperationalState operationalState) + : this(identifier, path, operationalState, DefaultMessageBodySerializationFormat) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier for the entity. + /// + /// + /// A unique textual path that identifies the entity. + /// + /// + /// The operational state of the entity. The default value is . + /// + /// + /// The format that is used to serialize enqueued message bodies. The default value is + /// . + /// + /// + /// is . + /// + /// + /// is equal to -or- is equal + /// to -or- is + /// equal to . + /// + protected MessagingEntity(Guid identifier, IMessagingEntityPath path, MessagingEntityOperationalState operationalState, SerializationFormat messageBodySerializationFormat) + : this(identifier, path, operationalState, messageBodySerializationFormat, DefaultMessageLockExpirationThreshold) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier for the entity. + /// + /// + /// A unique textual path that identifies the entity. + /// + /// + /// The operational state of the entity. The default value is . + /// + /// + /// The format that is used to serialize enqueued message bodies. The default value is + /// . + /// + /// + /// The length of time that a locked message is held before abandoning the associated token and making the message available + /// for processing. The default value is three minutes. + /// + /// + /// is . + /// + /// + /// is equal to -or- is equal + /// to -or- is + /// equal to -or- is less + /// than eight seconds. + /// + protected MessagingEntity(Guid identifier, IMessagingEntityPath path, MessagingEntityOperationalState operationalState, SerializationFormat messageBodySerializationFormat, TimeSpan messageLockExpirationThreshold) + : this(identifier, path, operationalState, messageBodySerializationFormat, messageLockExpirationThreshold, DefaultEnqueueTimeoutThreshold) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier for the entity. + /// + /// + /// A unique textual path that identifies the entity. + /// + /// + /// The operational state of the entity. The default value is . + /// + /// + /// The format that is used to serialize enqueued message bodies. The default value is + /// . + /// + /// + /// The length of time that a locked message is held before abandoning the associated token and making the message available + /// for processing. The default value is three minutes. + /// + /// + /// The maximum length of time to wait for a message to be enqueued before raising an exception. The default value is eight + /// seconds. + /// + /// + /// is . + /// + /// + /// is equal to -or- is equal + /// to -or- is + /// equal to -or- is less + /// than eight seconds -or- is less than two seconds. + /// + protected MessagingEntity(Guid identifier, IMessagingEntityPath path, MessagingEntityOperationalState operationalState, SerializationFormat messageBodySerializationFormat, TimeSpan messageLockExpirationThreshold, TimeSpan enqueueTimeoutThreshold) + : base() + { + EnqueueTimeoutThreshold = enqueueTimeoutThreshold.RejectIf().IsLessThan(EnqueueTimeoutThresholdFloor, nameof(enqueueTimeoutThreshold)); + Identifier = identifier.RejectIf().IsEqualToValue(Guid.Empty, nameof(identifier)); + MessageBodySerializationFormat = messageBodySerializationFormat.RejectIf().IsEqualToValue(SerializationFormat.Unspecified, nameof(messageBodySerializationFormat)); + MessageLockExpirationThreshold = messageLockExpirationThreshold.RejectIf().IsLessThan(MessageLockExpirationThresholdFloor, nameof(messageLockExpirationThreshold)); + OperationalState = operationalState.RejectIf().IsEqualToValue(MessagingEntityOperationalState.Unspecified, nameof(operationalState)); + Path = path.RejectIf().IsNull(nameof(path)).TargetArgument; + } + + /// + /// Asynchronously notifies the current that a locked message was not processed and can be made + /// available for processing by other consumers. + /// + /// + /// A lock token corresponding to a message that was not processed. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// does not reference an existing locked message. + /// + /// + /// The object is disposed. + /// + /// + /// The operation timed out. + /// + public Task ConveyFailureAsync(MessageLockToken lockToken) + { + RejectIfDisposed(); + + if (TryRemoveLockedMessage(lockToken.RejectIf().IsNull(nameof(lockToken)), out var message)) + { + return RequeueAsync(message); + } + + throw new InvalidOperationException("Failure conveyance was not successful. The specified lock token does not reference an existing locked message."); + } + + /// + /// Asynchronously notifies the current that a locked message was processed successfully and can + /// be destroyed permanently. + /// + /// + /// A lock token corresponding to a message that was processed successfully. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// does not reference an existing locked message. + /// + /// + /// The object is disposed. + /// + public Task ConveySuccessAsync(MessageLockToken lockToken) + { + RejectIfDisposed(); + + if (TryRemoveLockedMessage(lockToken.RejectIf().IsNull(nameof(lockToken)), out _)) + { + return Task.CompletedTask; + } + + throw new InvalidOperationException("Success conveyance was not successful. The specified lock token does not reference an existing locked message."); + } + + /// + /// Asynchronously enqueues the specified message. + /// + /// + /// The message to enqueue. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// The operation timed out. + /// + public Task EnqueueAsync(PrimitiveMessage message) + { + RejectIfDisposed(); + + if (CurrentStatePermitsEnqueue && TryEnqueue(message.RejectIf().IsNull(nameof(message)).TargetArgument)) + { + return Task.CompletedTask; + } + + var stopwatch = new Stopwatch(); + stopwatch.Start(); + + return Task.Factory.StartNew(() => + { + try + { + while (stopwatch.Elapsed < EnqueueTimeoutThreshold) + { + RejectIfDisposed(); + Thread.Sleep(EnqueueDelayDuration); + + if (CurrentStatePermitsEnqueue && TryEnqueue(message)) + { + return; + } + } + } + finally + { + stopwatch.Stop(); + } + + throw new TimeoutException($"The message could not be enqueued. The timeout threshold duration was exceeded while waiting for availability (path: {Path})."); + }); + } + + /// + /// Converts the value of the current to its equivalent string representation. + /// + /// + /// A string representation of the current . + /// + public sealed override String ToString() => $"{{ {nameof(Path)}: {Path} }}"; + + /// + /// Attempts to set the operational state of the current to + /// , or to + /// if the previous state was . + /// + /// + /// True if the operational state was successfully set, otherwise false. + /// + public Boolean TryDisableDequeues() + { + if (IsDisposedOrDisposing) + { + return false; + } + + using (var controlToken = StateControl.Enter()) + { + if (IsDisposedOrDisposing) + { + return false; + } + + switch (OperationalState) + { + case MessagingEntityOperationalState.DequeueOnly: + + OperationalState = MessagingEntityOperationalState.Paused; + return true; + + case MessagingEntityOperationalState.EnqueueOnly: + + return true; + + case MessagingEntityOperationalState.Paused: + + return true; + + case MessagingEntityOperationalState.Ready: + + OperationalState = MessagingEntityOperationalState.EnqueueOnly; + return true; + + default: + + return false; + } + } + } + + /// + /// Attempts to set the operational state of the current to + /// , or to + /// if the previous state was . + /// + /// + /// True if the operational state was successfully set, otherwise false. + /// + public Boolean TryDisableEnqueues() + { + if (IsDisposedOrDisposing) + { + return false; + } + + using (var controlToken = StateControl.Enter()) + { + if (IsDisposedOrDisposing) + { + return false; + } + + switch (OperationalState) + { + case MessagingEntityOperationalState.DequeueOnly: + + return true; + + case MessagingEntityOperationalState.EnqueueOnly: + + OperationalState = MessagingEntityOperationalState.Paused; + return true; + + case MessagingEntityOperationalState.Paused: + + return true; + + case MessagingEntityOperationalState.Ready: + + OperationalState = MessagingEntityOperationalState.DequeueOnly; + return true; + + default: + + return false; + } + } + } + + /// + /// Attempts to set the operational state of the current to + /// . + /// + /// + /// True if the operational state was successfully set, otherwise false. + /// + public Boolean TryPause() + { + if (IsDisposedOrDisposing) + { + return false; + } + + using (var controlToken = StateControl.Enter()) + { + if (IsDisposedOrDisposing) + { + return false; + } + + switch (OperationalState) + { + case MessagingEntityOperationalState.DequeueOnly: + + OperationalState = MessagingEntityOperationalState.Paused; + return true; + + case MessagingEntityOperationalState.EnqueueOnly: + + OperationalState = MessagingEntityOperationalState.Paused; + return true; + + case MessagingEntityOperationalState.Paused: + + return true; + + case MessagingEntityOperationalState.Ready: + + OperationalState = MessagingEntityOperationalState.Paused; + return true; + + default: + + return false; + } + } + } + + /// + /// Attempts to set the operational state of the current to + /// . + /// + /// + /// True if the operational state was successfully set, otherwise false. + /// + public Boolean TryResume() + { + if (IsDisposedOrDisposing) + { + return false; + } + + using (var controlToken = StateControl.Enter()) + { + if (IsDisposedOrDisposing) + { + return false; + } + + switch (OperationalState) + { + case MessagingEntityOperationalState.DequeueOnly: + + OperationalState = MessagingEntityOperationalState.Ready; + return true; + + case MessagingEntityOperationalState.EnqueueOnly: + + OperationalState = MessagingEntityOperationalState.Ready; + return true; + + case MessagingEntityOperationalState.Paused: + + OperationalState = MessagingEntityOperationalState.Ready; + return true; + + case MessagingEntityOperationalState.Ready: + + return true; + + default: + + return false; + } + } + } + + /// + /// Attempts to add the specified message to a list of locked messages. + /// + /// + /// The unique name of the subscription, or if no subscription is specified. + /// + /// + /// The locked message. + /// + /// + /// if the message was added successfully, otherwise . + /// + protected internal abstract Boolean TryAddLockedMessage(String subscriptionName, PrimitiveMessage message); + + /// + /// Attempts to remove and return the next message in the queue. + /// + /// + /// The unique name of the subscription, or if no subscription is specified. + /// + /// + /// The resulting message. + /// + /// + /// if the message was dequeued successfully, otherwise . + /// + protected internal abstract Boolean TryDequeue(String subscriptionName, out PrimitiveMessage message); + + /// + /// Attempts to enqueue the specified message. + /// + /// + /// The message to enqueue. + /// + /// + /// if the message was enqueued successfully, otherwise . + /// + protected internal abstract Boolean TryEnqueue(PrimitiveMessage message); + + /// + /// Attempts to remove and return the locked message associated with the specified lock token. + /// + /// + /// A lock token corresponding to a locked message. + /// + /// + /// The resulting message. + /// + /// + /// if the message was removed successfully, otherwise . + /// + protected internal abstract Boolean TryRemoveLockedMessage(MessageLockToken lockToken, out PrimitiveMessage message); + + /// + /// Non-destructively returns the next available messages from the current , if any, up to the + /// specified maximum count. + /// + /// + /// The unique name of the subscription, or if no subscription is specified. + /// + /// + /// The maximum number of messages to read from the entity. + /// + /// + /// The next available messages from the entity, or an empty collection if no messages are available. + /// + /// + /// is less than zero. + /// + /// + /// The object is disposed. + /// + /// + /// The operation timed out. + /// + protected IEnumerable Dequeue(String subscriptionName, Int32 count) + { + RejectIfDisposed(); + + if (count.RejectIf().IsLessThan(0, nameof(count)) == 0) + { + return Array.Empty(); + } + + var messageList = new List(); + var requeueTasks = new List(); + + while (messageList.Count() < count && CurrentStatePermitsDequeue && TryDequeue(subscriptionName, out var message)) + { + var lockToken = new MessageLockToken(Guid.NewGuid(), message.Identifier, TimeStamp.Current.Add(MessageLockExpirationThreshold)); + message.LockToken = lockToken; + + if (TryAddLockedMessage(subscriptionName, message)) + { + messageList.Add(message); + continue; + } + + requeueTasks.Add(RequeueAsync(message)); + } + + Task.WaitAll(requeueTasks.ToArray()); + return messageList; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) + { + try + { + if (disposing) + { + OperationalState = MessagingEntityOperationalState.Disabled; + } + } + finally + { + base.Dispose(disposing); + } + } + + /// + /// Returns a collection of exclusive processing locks for messages contained by the current . + /// + /// + /// A collection of exclusive processing locks for messages contained by the current . + /// + protected abstract IEnumerable GetLockTokens(); + + /// + /// Gets the number of messages in the current . + /// + /// + /// The number of messages in the current . + /// + protected abstract Int32 GetMessageCount(); + + /// + /// Asynchronously requeues a previously-enqueued message. + /// + /// + /// The message to requeue. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// The operation timed out. + /// + [DebuggerHidden] + private Task RequeueAsync(PrimitiveMessage message) + { + RejectIfDisposed(); + var lockToken = new MessageLockToken(Guid.NewGuid(), message.RejectIf().IsNull(nameof(message)).TargetArgument.Identifier); + message.LockToken = lockToken; + return EnqueueAsync(message); + } + + /// + /// Gets the maximum length of time to wait for a message to be enqueued before raising an exception. + /// + public TimeSpan EnqueueTimeoutThreshold + { + get; + private set; + } + + /// + /// Gets the messaging entity type of the current . + /// + public abstract MessagingEntityType EntityType + { + get; + } + + /// + /// Gets a unique identifier for the current . + /// + public Guid Identifier + { + get; + } + + /// + /// Gets a value indicating whether or not the current is empty. + /// + public Boolean IsEmpty => MessageCount == 0; + + /// + /// Gets a collection of exclusive processing locks for messages contained by the current . + /// + /// + /// The object is disposed. + /// + public IEnumerable LockTokens + { + get + { + RejectIfDisposed(); + var lockTokens = GetLockTokens(); + + foreach (var lockToken in lockTokens) + { + RejectIfDisposed(); + yield return lockToken; + } + } + } + + /// + /// Gets the format that is used to serialize enqueued message bodies. + /// + public SerializationFormat MessageBodySerializationFormat + { + get; + private set; + } + + /// + /// Gets the number of messages in the current . + /// + public Int32 MessageCount => GetMessageCount(); + + /// + /// Gets the length of time that a locked message is held before abandoning the associated token and making the message + /// available for processing. + /// + public TimeSpan MessageLockExpirationThreshold + { + get; + private set; + } + + /// + /// Gets the operational state of the current . + /// + public MessagingEntityOperationalState OperationalState + { + get; + private set; + } + + /// + /// Gets a unique textual path that identifies the current . + /// + public IMessagingEntityPath Path + { + get; + internal set; + } + + /// + /// Gets a value indicating whether or not the current operational state permits dequeue operations. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private Boolean CurrentStatePermitsDequeue => OperationalState == MessagingEntityOperationalState.DequeueOnly || OperationalState == MessagingEntityOperationalState.Ready; + + /// + /// Gets a value indicating whether or not the current operational state permits enqueue operations. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private Boolean CurrentStatePermitsEnqueue => OperationalState == MessagingEntityOperationalState.EnqueueOnly || OperationalState == MessagingEntityOperationalState.Ready; + + /// + /// Represents the default format that is used to serialize enqueued message bodies. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const SerializationFormat DefaultMessageBodySerializationFormat = PrimitiveMessage.DefaultBodySerializationFormat; + + /// + /// Represents the default length of time to wait for a message to be enqueued before raising an exception. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static readonly TimeSpan DefaultEnqueueTimeoutThreshold = TimeSpan.FromSeconds(11); + + /// + /// Represents the default length of time that a locked message is held before abandoning the associated token and making + /// the message available for processing. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static readonly TimeSpan DefaultMessageLockExpirationThreshold = TimeSpan.FromMinutes(3); + + /// + /// Represents the length of time that waits between attempts when the entity + /// is unavailable. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static readonly TimeSpan EnqueueDelayDuration = TimeSpan.FromMilliseconds(3); + + /// + /// Represents the minimum permissible length of time to wait for a message to be enqueued before raising an exception. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static readonly TimeSpan EnqueueTimeoutThresholdFloor = TimeSpan.FromSeconds(2); + + /// + /// Represents the minimum permissible length of time that a locked message is held before abandoning the associated token + /// and making the message available for processing. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static readonly TimeSpan MessageLockExpirationThresholdFloor = TimeSpan.FromSeconds(8); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessageQueueOperationalState.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessagingEntityOperationalState.cs similarity index 69% rename from src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessageQueueOperationalState.cs rename to src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessagingEntityOperationalState.cs index b1c3747a..d874fd2e 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessageQueueOperationalState.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessagingEntityOperationalState.cs @@ -8,43 +8,43 @@ namespace RapidField.SolidInstruments.Messaging.TransportPrimitives { /// - /// Represents the operational state of an . + /// Represents the operational state of an . /// [DataContract] - public enum DurableMessageQueueOperationalState : Int32 + internal enum MessagingEntityOperationalState : Int32 { /// - /// The queue's operational state is not specified. + /// The entity's operational state is not specified. /// [EnumMember] Unspecified = 0, /// - /// The queue is enabled for both enqueue and dequeue operations. + /// The entity is enabled for both enqueue and dequeue operations. /// [EnumMember] Ready = 1, /// - /// The queue is enabled for dequeue operations only. + /// The entity is enabled for dequeue operations only. /// [EnumMember] DequeueOnly = 2, /// - /// The queue is enabled for enqueue operations only. + /// The entity is enabled for enqueue operations only. /// [EnumMember] EnqueueOnly = 3, /// - /// The queue is temporarily disabled. + /// The entity is temporarily disabled. /// [EnumMember] Paused = 4, /// - /// The queue is permanently disabled. + /// entity is permanently disabled. /// [EnumMember] Disabled = 5 diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessage.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/PrimitiveMessage.cs similarity index 90% rename from src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessage.cs rename to src/RapidField.SolidInstruments.Messaging/TransportPrimitives/PrimitiveMessage.cs index 6f43450e..b6baab28 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/DurableMessage.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/PrimitiveMessage.cs @@ -15,23 +15,23 @@ namespace RapidField.SolidInstruments.Messaging.TransportPrimitives { /// - /// Represents a durable, serializable message. + /// Represents a serializable message. /// [DataContract] - public sealed class DurableMessage + public sealed class PrimitiveMessage { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// [DebuggerHidden] - internal DurableMessage() + internal PrimitiveMessage() : this(null, null) { return; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// The message body. @@ -40,14 +40,14 @@ internal DurableMessage() /// An exclusive lock key for the current message, which can be used to complete or abandon processing. /// [DebuggerHidden] - internal DurableMessage(IMessageBase body, DurableMessageLockToken lockToken) + internal PrimitiveMessage(IMessageBase body, MessageLockToken lockToken) : this(body, lockToken, DefaultBodySerializationFormat) { return; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// The message body. @@ -60,7 +60,7 @@ internal DurableMessage(IMessageBase body, DurableMessageLockToken lockToken) /// . /// [DebuggerHidden] - internal DurableMessage(IMessageBase body, DurableMessageLockToken lockToken, SerializationFormat bodySerializationFormat) + internal PrimitiveMessage(IMessageBase body, MessageLockToken lockToken, SerializationFormat bodySerializationFormat) { Body = body; BodyTypeName = body?.GetType().FullName; @@ -69,10 +69,10 @@ internal DurableMessage(IMessageBase body, DurableMessageLockToken lockToken, Se } /// - /// Converts the value of the current to its equivalent string representation. + /// Converts the value of the current to its equivalent string representation. /// /// - /// A string representation of the current . + /// A string representation of the current . /// public sealed override String ToString() => BodyTypeName.IsNullOrEmpty() ? $"{Identifier.ToSerializedString()}" : $"{Identifier.ToSerializedString()} | {BodyTypeName}"; @@ -187,7 +187,7 @@ public String BodyTypeName public Guid Identifier => Body.Identifier; /// - /// Gets instructions and contextual information relating to processing for the current . + /// Gets instructions and contextual information relating to processing for the current . /// [IgnoreDataMember] public MessageProcessingInformation ProcessingInformation => Body.ProcessingInformation; @@ -197,7 +197,7 @@ public String BodyTypeName /// if the message is not locked. /// [DataMember(Order = 4)] - internal DurableMessageLockToken LockToken + internal MessageLockToken LockToken { get; set; diff --git a/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/DuplexSemaphoreControlTests.cs b/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/DuplexSemaphoreControlTests.cs index dae1a6ed..00a10b0c 100644 --- a/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/DuplexSemaphoreControlTests.cs +++ b/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/DuplexSemaphoreControlTests.cs @@ -27,7 +27,7 @@ public void OperationLatency_ShouldBeLow() { // Arrange. var mode = ConcurrencyControlMode.DuplexSemaphore; - var latencyThresholdInTicks = 610; + var latencyThresholdInTicks = 987; // Assert. ConcurrencyControlTests.OperationLatency_ShouldBeLow(mode, PerformUsingPrimitive, latencyThresholdInTicks); diff --git a/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/ProcessorCountSemaphoreControlTests.cs b/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/ProcessorCountSemaphoreControlTests.cs index 75d1fc79..cbb5e6cd 100644 --- a/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/ProcessorCountSemaphoreControlTests.cs +++ b/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/ProcessorCountSemaphoreControlTests.cs @@ -27,7 +27,7 @@ public void OperationLatency_ShouldBeLow() { // Arrange. var mode = ConcurrencyControlMode.ProcessorCountSemaphore; - var latencyThresholdInTicks = 610; + var latencyThresholdInTicks = 987; // Assert. ConcurrencyControlTests.OperationLatency_ShouldBeLow(mode, PerformUsingPrimitive, latencyThresholdInTicks); diff --git a/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/SingleThreadLockControlTests.cs b/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/SingleThreadLockControlTests.cs index bef1a807..c2a5aafb 100644 --- a/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/SingleThreadLockControlTests.cs +++ b/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/SingleThreadLockControlTests.cs @@ -27,7 +27,7 @@ public void OperationLatency_ShouldBeLow() { // Arrange. var mode = ConcurrencyControlMode.SingleThreadLock; - var latencyThresholdInTicks = 610; + var latencyThresholdInTicks = 987; // Assert. ConcurrencyControlTests.OperationLatency_ShouldBeLow(mode, PerformUsingPrimitive, latencyThresholdInTicks); diff --git a/test/RapidField.SolidInstruments.Messaging.UnitTests/TransportPrimitives/DurableMessageLockTokenTests.cs b/test/RapidField.SolidInstruments.Messaging.UnitTests/TransportPrimitives/MessageLockTokenTests.cs similarity index 92% rename from test/RapidField.SolidInstruments.Messaging.UnitTests/TransportPrimitives/DurableMessageLockTokenTests.cs rename to test/RapidField.SolidInstruments.Messaging.UnitTests/TransportPrimitives/MessageLockTokenTests.cs index 2b11f973..f809fc5c 100644 --- a/test/RapidField.SolidInstruments.Messaging.UnitTests/TransportPrimitives/DurableMessageLockTokenTests.cs +++ b/test/RapidField.SolidInstruments.Messaging.UnitTests/TransportPrimitives/MessageLockTokenTests.cs @@ -11,7 +11,7 @@ namespace RapidField.SolidInstruments.Messaging.UnitTests.TransportPrimitives { [TestClass] - public class DurableMessageLockTokenTests + public class MessageLockTokenTests { [TestMethod] public void ShouldBeSerializable_UsingBinaryFormat() @@ -69,8 +69,8 @@ private static void ShouldBeSerializable(SerializationFormat format) var expirationDateTime = DateTime.UtcNow; var messageIdentifier = Guid.Parse("c1572900-0080-4460-a5ef-d43e3e651d7c"); var identifier = Guid.Parse("7f18e63b-4d27-46e4-b6d5-0926169044fd"); - var target = new DurableMessageLockToken(identifier, messageIdentifier, expirationDateTime); - var serializer = new DynamicSerializer(format); + var target = new MessageLockToken(identifier, messageIdentifier, expirationDateTime); + var serializer = new DynamicSerializer(format); // Act. var serializedTarget = serializer.Serialize(target); diff --git a/test/RapidField.SolidInstruments.Messaging.UnitTests/TransportPrimitives/DurableMessageTests.cs b/test/RapidField.SolidInstruments.Messaging.UnitTests/TransportPrimitives/PrimitiveMessageTests.cs similarity index 89% rename from test/RapidField.SolidInstruments.Messaging.UnitTests/TransportPrimitives/DurableMessageTests.cs rename to test/RapidField.SolidInstruments.Messaging.UnitTests/TransportPrimitives/PrimitiveMessageTests.cs index 2c2c5ea4..cf77d5a9 100644 --- a/test/RapidField.SolidInstruments.Messaging.UnitTests/TransportPrimitives/DurableMessageTests.cs +++ b/test/RapidField.SolidInstruments.Messaging.UnitTests/TransportPrimitives/PrimitiveMessageTests.cs @@ -11,7 +11,7 @@ namespace RapidField.SolidInstruments.Messaging.UnitTests.TransportPrimitives { [TestClass] - public class DurableMessageTests + public class PrimitiveMessageTests { [TestMethod] public void ShouldBeSerializable_UsingBinaryFormat() @@ -71,13 +71,13 @@ private static void ShouldBeSerializable(SerializationFormat format) var simulatedMessage = new SimulatedMessage(correlationIdentifier, testObject); var expirationDateTime = DateTime.UtcNow; var lockTokenIdentifier = Guid.Parse("037d702f-9b07-4686-a09c-29073ef29a85"); - var lockToken = new DurableMessageLockToken(lockTokenIdentifier, simulatedMessage.Identifier, expirationDateTime); + var lockToken = new MessageLockToken(lockTokenIdentifier, simulatedMessage.Identifier, expirationDateTime); var lockKey = Guid.Parse("c7692faa-6cdb-402a-ae65-2f2831e599b8"); - var durableMessage = new DurableMessage(simulatedMessage, lockToken); - var serializer = new DynamicSerializer(format); + var primitiveMessage = new PrimitiveMessage(simulatedMessage, lockToken); + var serializer = new DynamicSerializer(format); // Act. - var serializedTarget = serializer.Serialize(durableMessage); + var serializedTarget = serializer.Serialize(primitiveMessage); var deserializedResult = serializer.Deserialize(serializedTarget); // Assert. From a2784ae2f4f35febdd36260949ba7a68364745a8 Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Sat, 14 Mar 2020 19:19:55 -0500 Subject: [PATCH 15/55] Extending messaging transport objects. --- .../TransportPrimitives/IMessageTransport.cs | 52 +- .../IMessageTransportConnection.cs | 5 +- .../TransportPrimitives/MessageQueue.cs | 38 + .../TransportPrimitives/MessageTopic.cs | 85 +- .../TransportPrimitives/MessageTransport.cs | 738 ++++++++++++++++++ .../MessageTransportConnection.cs | 108 +++ .../MessageTransportConnectionState.cs | 2 +- .../TransportPrimitives/MessagingEntity.cs | 30 +- .../MessagingEntityOperationalState.cs | 8 - 9 files changed, 1028 insertions(+), 38 deletions(-) create mode 100644 src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransport.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransportConnection.cs diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransport.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransport.cs index b8d1365e..d6bf15c7 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransport.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransport.cs @@ -13,8 +13,19 @@ namespace RapidField.SolidInstruments.Messaging.TransportPrimitives /// /// Supports message exchange for a collection of queues and topics. /// - internal interface IMessageTransport : IInstrument + public interface IMessageTransport : IInstrument { + /// + /// Closes the specified connection as an idempotent operation. + /// + /// + /// The connection to close. + /// + /// + /// is . + /// + void CloseConnection(IMessageTransportConnection connection); + /// /// Opens and returns a new to the current . /// @@ -233,6 +244,9 @@ internal interface IMessageTransport : IInstrument /// /// if the queue exists, otherwise . /// + /// + /// is . + /// /// /// The object is disposed. /// @@ -247,6 +261,9 @@ internal interface IMessageTransport : IInstrument /// /// if the topic exists, otherwise . /// + /// + /// is . + /// /// /// The object is disposed. /// @@ -364,12 +381,17 @@ internal interface IMessageTransport : IInstrument /// Boolean TryDestroyTopic(IMessagingEntityPath path); + /// + /// Gets the number of active connections to the current . + /// + Int32 ConnectionCount + { + get; + } + /// /// Gets a collection of active connections to the current . /// - /// - /// The object is disposed. - /// IEnumerable Connections { get; @@ -383,23 +405,33 @@ SerializationFormat MessageBodySerializationFormat get; } + /// + /// Gets the number of queues within the current . + /// + Int32 QueueCount + { + get; + } + /// /// Gets a collection of available queue paths for the current . /// - /// - /// The object is disposed. - /// IEnumerable QueuePaths { get; } + /// + /// Gets the number of topics within the current . + /// + Int32 TopicCount + { + get; + } + /// /// Gets a collection of available topic paths for the current . /// - /// - /// The object is disposed. - /// IEnumerable TopicPaths { get; diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransportConnection.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransportConnection.cs index 04d7a513..abd649e2 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransportConnection.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransportConnection.cs @@ -9,7 +9,7 @@ namespace RapidField.SolidInstruments.Messaging.TransportPrimitives /// /// Represents a client connection to an . /// - internal interface IMessageTransportConnection : IAsyncDisposable, IDisposable + public interface IMessageTransportConnection : IAsyncDisposable, IDisposable { /// /// Closes the current as an idempotent operation. @@ -35,6 +35,9 @@ MessageTransportConnectionState State /// /// Gets the associated . /// + /// + /// The connection is closed. + /// IMessageTransport Transport { get; diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageQueue.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageQueue.cs index 57ec0c8a..6becf10c 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageQueue.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageQueue.cs @@ -7,6 +7,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; +using System.Threading; using System.Threading.Tasks; namespace RapidField.SolidInstruments.Messaging.TransportPrimitives @@ -313,6 +314,36 @@ protected internal sealed override Boolean TryRemoveLockedMessage(MessageLockTok return false; } + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) + { + try + { + if (disposing) + { + var disposalAttemptCount = 0; + + while (MessageCount > 0 && disposalAttemptCount < MaximumDisposalAttemptCount) + { + Thread.Sleep(EnqueueTimeoutThreshold); + disposalAttemptCount++; + } + + LockedMessages.Clear(); + Messages.Clear(); + } + } + finally + { + base.Dispose(disposing); + } + } + /// /// Returns a collection of exclusive processing locks for messages contained by the current . /// @@ -334,6 +365,13 @@ protected internal sealed override Boolean TryRemoveLockedMessage(MessageLockTok /// public override MessagingEntityType EntityType => MessagingEntityType.Queue; + /// + /// Represents the maximum number of times that instances will attempt to wait for natural + /// clearance before discarding outstanding messages during disposal. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Int32 MaximumDisposalAttemptCount = 3; + /// /// Represents the underlying first-in first-out collection that contains messages that are locked for processing. /// diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTopic.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTopic.cs index feffe7b5..1e76ba91 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTopic.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTopic.cs @@ -3,6 +3,8 @@ // ================================================================================================================================= using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Core.Concurrency; using RapidField.SolidInstruments.Core.Extensions; using RapidField.SolidInstruments.Serialization; using System; @@ -224,7 +226,22 @@ internal MessageTopic(Guid identifier, IMessagingEntityPath path, MessagingEntit /// /// The object is disposed. /// - public Task CreateSubscriptionAsync(String subscriptionName) => throw new NotImplementedException(); + public Task CreateSubscriptionAsync(String subscriptionName) => Task.Factory.StartNew(() => + { + RejectIfDisposed(); + + if (SubscriptionQueues.ContainsKey(subscriptionName.RejectIf().IsNullOrEmpty(nameof(subscriptionName))) == false) + { + var subscriptionQueue = new MessageQueue(Guid.NewGuid(), Path, OperationalState, MessageBodySerializationFormat, MessageLockExpirationThreshold, EnqueueTimeoutThreshold); + + if (SubscriptionQueues.TryAdd(subscriptionName, subscriptionQueue)) + { + return; + } + } + + throw new InvalidOperationException($"A subscription with the name \"{subscriptionName}\" already exists."); + }); /// /// Asynchronously and non-destructively returns the next available messages from the current , @@ -255,7 +272,10 @@ internal MessageTopic(Guid identifier, IMessagingEntityPath path, MessagingEntit /// /// The operation timed out. /// - public Task> DequeueAsync(String subscriptionName, Int32 count) => throw new NotImplementedException(); + public Task> DequeueAsync(String subscriptionName, Int32 count) => Task.Factory.StartNew(() => + { + return Dequeue(subscriptionName.RejectIf().IsNullOrEmpty(nameof(subscriptionName)), count); + }); /// /// Asynchronously destroys the specified subscription to the current . @@ -278,7 +298,25 @@ internal MessageTopic(Guid identifier, IMessagingEntityPath path, MessagingEntit /// /// The object is disposed. /// - public Task DestroySubscriptionAsync(String subscriptionName) => throw new NotImplementedException(); + public Task DestroySubscriptionAsync(String subscriptionName) => Task.Factory.StartNew(() => + { + RejectIfDisposed(); + + if (SubscriptionQueues.ContainsKey(subscriptionName.RejectIf().IsNullOrEmpty(nameof(subscriptionName)))) + { + if (SubscriptionQueues.TryRemove(subscriptionName, out var subscriptionQueue)) + { + if (subscriptionQueue.TryDisableEnqueues()) + { + subscriptionQueue.Dispose(); + } + + return; + } + } + + throw new InvalidOperationException($"A subscription with the name \"{subscriptionName}\" does not exist."); + }); /// /// Attempts to add the specified message to a list of locked messages. @@ -416,6 +454,47 @@ protected internal sealed override Boolean TryRemoveLockedMessage(MessageLockTok return false; } + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) + { + try + { + if (disposing) + { + while (SubscriptionCount > 0) + { + var subscriptionNames = SubscriptionQueues.Keys.ToArray(); + var subscriptionCount = subscriptionNames.Length; + var disposeQueueTasks = new List(subscriptionCount); + + for (var i = 0; i < subscriptionCount; i++) + { + var subscriptionName = subscriptionNames[i]; + + if (SubscriptionQueues.TryRemove(subscriptionName, out var subscriptionQueue)) + { + disposeQueueTasks.Add(Task.Factory.StartNew(() => subscriptionQueue.Dispose())); + } + } + + if (disposeQueueTasks.Any()) + { + Task.WaitAll(disposeQueueTasks.ToArray()); + } + } + } + } + finally + { + base.Dispose(disposing); + } + } + /// /// Returns a collection of exclusive processing locks for messages contained by the current . /// diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransport.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransport.cs new file mode 100644 index 00000000..fb843698 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransport.cs @@ -0,0 +1,738 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Serialization; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; + +namespace RapidField.SolidInstruments.Messaging.TransportPrimitives +{ + /// + /// Supports message exchange for a collection of queues and topics. + /// + /// + /// is the default implementation of . + /// + internal sealed class MessageTransport : Instrument, IMessageTransport + { + /// + /// Initializes a new instance of the class. + /// + [DebuggerHidden] + internal MessageTransport() + : this(PrimitiveMessage.DefaultBodySerializationFormat) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The format that is used to serialize enqueued message bodies. + /// + /// + /// is equal to . + /// + [DebuggerHidden] + internal MessageTransport(SerializationFormat messageBodySerializationFormat) + : base() + { + MessageBodySerializationFormat = messageBodySerializationFormat.RejectIf().IsEqualToValue(SerializationFormat.Unspecified, nameof(messageBodySerializationFormat)); + } + + /// + /// Closes the specified connection as an idempotent operation. + /// + /// + /// The connection to close. + /// + /// + /// is . + /// + public void CloseConnection(IMessageTransportConnection connection) + { + if (ConnectionDictionary.ContainsKey(connection.RejectIf().IsNull(nameof(connection)).TargetArgument.Identifier)) + { + if (ConnectionDictionary.TryRemove(connection.Identifier, out _)) + { + connection.Dispose(); + } + } + } + + /// + /// Opens and returns a new to the current . + /// + /// + /// A new to the current . + /// + /// + /// The object is disposed. + /// + public IMessageTransportConnection CreateConnection() + { + RejectIfDisposed(); + var connection = new MessageTransportConnection(this); + + if (ConnectionDictionary.TryAdd(connection.Identifier, connection)) + { + return connection; + } + + return connection; + } + + /// + /// Asynchronously creates a new queue. + /// + /// + /// A unique textual path that identifies the new queue. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// An entity with the specified path already exists. + /// + public Task CreateQueueAsync(IMessagingEntityPath path) => CreateQueueAsync(path, MessagingEntity.DefaultMessageLockExpirationThreshold); + + /// + /// Asynchronously creates a new queue. + /// + /// + /// A unique textual path that identifies the new queue. + /// + /// + /// The length of time that a locked message is held before abandoning the associated token and making the message available + /// for processing. The default value is three minutes. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// is less than eight seconds. + /// + /// + /// The object is disposed. + /// + /// + /// An entity with the specified path already exists. + /// + public Task CreateQueueAsync(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold) => CreateQueueAsync(path, messageLockExpirationThreshold, MessagingEntity.DefaultEnqueueTimeoutThreshold); + + /// + /// Asynchronously creates a new queue. + /// + /// + /// A unique textual path that identifies the new queue. + /// + /// + /// The length of time that a locked message is held before abandoning the associated token and making the message available + /// for processing. The default value is three minutes. + /// + /// + /// The maximum length of time to wait for a message to be enqueued before raising an exception. The default value is eight + /// seconds. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// is less than eight seconds -or- + /// is less than two seconds. + /// + /// + /// The object is disposed. + /// + /// + /// An entity with the specified path already exists. + /// + public Task CreateQueueAsync(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold, TimeSpan enqueueTimeoutThreshold) => Task.Factory.StartNew(() => + { + RejectIfDisposed(); + + if (TryCreateQueue(path.RejectIf().IsNull(nameof(path)).TargetArgument, messageLockExpirationThreshold.RejectIf().IsLessThan(MessagingEntity.MessageLockExpirationThresholdFloor, nameof(messageLockExpirationThreshold)), enqueueTimeoutThreshold.RejectIf().IsLessThan(MessagingEntity.EnqueueTimeoutThresholdFloor, nameof(enqueueTimeoutThreshold)))) + { + return; + } + + throw new InvalidOperationException($"The specified queue, \"{path}\", already exists"); + }); + + /// + /// Asynchronously creates a new topic. + /// + /// + /// A unique textual path that identifies the new topic. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// An entity with the specified path already exists. + /// + public Task CreateTopicAsync(IMessagingEntityPath path) => CreateTopicAsync(path, MessagingEntity.DefaultMessageLockExpirationThreshold); + + /// + /// Asynchronously creates a new topic. + /// + /// + /// A unique textual path that identifies the new topic. + /// + /// + /// The length of time that a locked message is held before abandoning the associated token and making the message available + /// for processing. The default value is three minutes. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// is less than eight seconds. + /// + /// + /// The object is disposed. + /// + /// + /// An entity with the specified path already exists. + /// + public Task CreateTopicAsync(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold) => CreateTopicAsync(path, messageLockExpirationThreshold, MessagingEntity.DefaultEnqueueTimeoutThreshold); + + /// + /// Asynchronously creates a new topic. + /// + /// + /// A unique textual path that identifies the new topic. + /// + /// + /// The length of time that a locked message is held before abandoning the associated token and making the message available + /// for processing. The default value is three minutes. + /// + /// + /// The maximum length of time to wait for a message to be enqueued before raising an exception. The default value is eight + /// seconds. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// is less than eight seconds -or- + /// is less than two seconds. + /// + /// + /// The object is disposed. + /// + /// + /// An entity with the specified path already exists. + /// + public Task CreateTopicAsync(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold, TimeSpan enqueueTimeoutThreshold) => Task.Factory.StartNew(() => + { + RejectIfDisposed(); + + if (TryCreateTopic(path.RejectIf().IsNull(nameof(path)).TargetArgument, messageLockExpirationThreshold.RejectIf().IsLessThan(MessagingEntity.MessageLockExpirationThresholdFloor, nameof(messageLockExpirationThreshold)), enqueueTimeoutThreshold.RejectIf().IsLessThan(MessagingEntity.EnqueueTimeoutThresholdFloor, nameof(enqueueTimeoutThreshold)))) + { + return; + } + + throw new InvalidOperationException($"The specified topic, \"{path}\", already exists"); + }); + + /// + /// Asynchronously destroys the specified queue. + /// + /// + /// A unique textual path that identifies the queue. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// The specified entity does not exist. + /// + public Task DestroyQueueAsync(IMessagingEntityPath path) => Task.Factory.StartNew(() => + { + RejectIfDisposed(); + + if (TryDestroyQueue(path.RejectIf().IsNull(nameof(path)).TargetArgument)) + { + return; + } + + throw new InvalidOperationException($"The specified queue, \"{path}\", does not exist."); + }); + + /// + /// Asynchronously destroys the specified topic. + /// + /// + /// A unique textual path that identifies the topic. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// The specified entity does not exist. + /// + public Task DestroyTopicAsync(IMessagingEntityPath path) => Task.Factory.StartNew(() => + { + RejectIfDisposed(); + + if (TryDestroyTopic(path.RejectIf().IsNull(nameof(path)).TargetArgument)) + { + return; + } + + throw new InvalidOperationException($"The specified topic, \"{path}\", does not exist."); + }); + + /// + /// Returns a value indicating whether or not the specified queue exists. + /// + /// + /// A unique textual path that identifies the queue. + /// + /// + /// if the queue exists, otherwise . + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public Boolean QueueExists(IMessagingEntityPath path) + { + RejectIfDisposed(); + return QueuePaths.Any(queuePath => queuePath == path.RejectIf().IsNull(nameof(path)).TargetArgument); + } + + /// + /// Returns a value indicating whether or not the specified topic exists. + /// + /// + /// A unique textual path that identifies the topic. + /// + /// + /// if the topic exists, otherwise . + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public Boolean TopicExists(IMessagingEntityPath path) => TopicPaths.Any(topciPath => topciPath == path.RejectIf().IsNull(nameof(path)).TargetArgument); + + /// + /// Attempts to create a new queue. + /// + /// + /// A unique textual path that identifies the new queue. + /// + /// + /// if the queue was successfully created, otherwise . + /// + public Boolean TryCreateQueue(IMessagingEntityPath path) => TryCreateQueue(path, MessagingEntity.DefaultMessageLockExpirationThreshold); + + /// + /// Attempts to create a new queue. + /// + /// + /// A unique textual path that identifies the new queue. + /// + /// + /// The length of time that a locked message is held before abandoning the associated token and making the message available + /// for processing. The default value is three minutes. + /// + /// + /// if the queue was successfully created, otherwise . + /// + public Boolean TryCreateQueue(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold) => TryCreateQueue(path, messageLockExpirationThreshold, MessagingEntity.DefaultEnqueueTimeoutThreshold); + + /// + /// Attempts to create a new queue. + /// + /// + /// A unique textual path that identifies the new queue. + /// + /// + /// The length of time that a locked message is held before abandoning the associated token and making the message available + /// for processing. The default value is three minutes. + /// + /// + /// The maximum length of time to wait for a message to be enqueued before raising an exception. The default value is eight + /// seconds. + /// + /// + /// if the queue was successfully created, otherwise . + /// + public Boolean TryCreateQueue(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold, TimeSpan enqueueTimeoutThreshold) + { + if (IsDisposedOrDisposing || path is null || messageLockExpirationThreshold < MessagingEntity.MessageLockExpirationThresholdFloor || enqueueTimeoutThreshold < MessagingEntity.EnqueueTimeoutThresholdFloor) + { + return false; + } + + using (var controlToken = StateControl.Enter()) + { + if (IsDisposedOrDisposing) + { + return false; + } + + var queue = new MessageQueue(Guid.NewGuid(), path, MessagingEntityOperationalState.Ready, MessageBodySerializationFormat, messageLockExpirationThreshold, enqueueTimeoutThreshold); + + if (QueueDictionary.TryAdd(path, queue)) + { + return true; + } + } + + return false; + } + + /// + /// Attempts to create a new topic. + /// + /// + /// A unique textual path that identifies the new topic. + /// + /// + /// if the topic was successfully created, otherwise . + /// + public Boolean TryCreateTopic(IMessagingEntityPath path) => TryCreateTopic(path, MessagingEntity.DefaultMessageLockExpirationThreshold); + + /// + /// Attempts to create a new topic. + /// + /// + /// A unique textual path that identifies the new topic. + /// + /// + /// The length of time that a locked message is held before abandoning the associated token and making the message available + /// for processing. The default value is three minutes. + /// + /// + /// if the topic was successfully created, otherwise . + /// + public Boolean TryCreateTopic(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold) => TryCreateTopic(path, messageLockExpirationThreshold, MessagingEntity.DefaultEnqueueTimeoutThreshold); + + /// + /// Attempts to create a new topic. + /// + /// + /// A unique textual path that identifies the new topic. + /// + /// + /// The length of time that a locked message is held before abandoning the associated token and making the message available + /// for processing. The default value is three minutes. + /// + /// + /// The maximum length of time to wait for a message to be enqueued before raising an exception. The default value is eight + /// seconds. + /// + /// + /// if the topic was successfully created, otherwise . + /// + public Boolean TryCreateTopic(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold, TimeSpan enqueueTimeoutThreshold) + { + if (IsDisposedOrDisposing || path is null || messageLockExpirationThreshold < MessagingEntity.MessageLockExpirationThresholdFloor || enqueueTimeoutThreshold < MessagingEntity.EnqueueTimeoutThresholdFloor) + { + return false; + } + + using (var controlToken = StateControl.Enter()) + { + if (IsDisposedOrDisposing) + { + return false; + } + + var topic = new MessageTopic(Guid.NewGuid(), path, MessagingEntityOperationalState.Ready, MessageBodySerializationFormat, messageLockExpirationThreshold, enqueueTimeoutThreshold); + + if (TopicDictionary.TryAdd(path, topic)) + { + return true; + } + } + + return false; + } + + /// + /// Attempts to destroy the specified queue. + /// + /// + /// A unique textual path that identifies the queue. + /// + /// + /// if the queue was successfully destroyed, otherwise . + /// + public Boolean TryDestroyQueue(IMessagingEntityPath path) + { + if (IsDisposedOrDisposing || path is null) + { + return false; + } + + using (var controlToken = StateControl.Enter()) + { + if (IsDisposedOrDisposing) + { + return false; + } + + if (QueueDictionary.TryRemove(path, out var queue)) + { + queue?.Dispose(); + return true; + } + } + + return false; + } + + /// + /// Attempts to destroy the specified topic. + /// + /// + /// A unique textual path that identifies the topic. + /// + /// + /// if the topic was successfully destroyed, otherwise . + /// + public Boolean TryDestroyTopic(IMessagingEntityPath path) + { + if (IsDisposedOrDisposing || path is null) + { + return false; + } + + using (var controlToken = StateControl.Enter()) + { + if (IsDisposedOrDisposing) + { + return false; + } + + if (TopicDictionary.TryRemove(path, out var topic)) + { + topic?.Dispose(); + return true; + } + } + + return false; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) + { + try + { + if (disposing) + { + while (TopicCount > 0 || QueueCount > 0 || ConnectionCount > 0) + { + DestroyAllTopics(); + DestroyAllQueues(); + DestroyAllConnections(); + } + } + } + finally + { + base.Dispose(disposing); + } + } + + /// + /// Closes and disposes of all connections to the current . + /// + [DebuggerHidden] + private void DestroyAllConnections() + { + var destroyConnectionTasks = new List(); + + while (ConnectionDictionary.Any()) + { + var connectionIdentifier = ConnectionDictionary.Keys.FirstOrDefault(); + + if (connectionIdentifier == default) + { + continue; + } + else if (ConnectionDictionary.TryRemove(connectionIdentifier, out var connection)) + { + destroyConnectionTasks.Add(Task.Factory.StartNew(() => + { + connection.Dispose(); + })); + } + } + + Task.WaitAll(destroyConnectionTasks.ToArray()); + } + + /// + /// Removes and disposes of all queues within the current . + /// + [DebuggerHidden] + private void DestroyAllQueues() + { + var destroyQueueTasks = new List(); + + while (QueueDictionary.Any()) + { + var queuePath = QueueDictionary.Keys.First(); + + if (queuePath is null) + { + continue; + } + else if (QueueDictionary.TryRemove(queuePath, out var queue)) + { + destroyQueueTasks.Add(Task.Factory.StartNew(() => + { + queue.Dispose(); + })); + } + } + + Task.WaitAll(destroyQueueTasks.ToArray()); + } + + /// + /// Removes and disposes of all topics within the current . + /// + [DebuggerHidden] + private void DestroyAllTopics() + { + var destroyTopicTasks = new List(); + + while (TopicDictionary.Any()) + { + var topicPath = TopicDictionary.Keys.First(); + + if (topicPath is null) + { + continue; + } + else if (TopicDictionary.TryRemove(topicPath, out var topic)) + { + destroyTopicTasks.Add(Task.Factory.StartNew(() => + { + topic.Dispose(); + })); + } + } + + Task.WaitAll(destroyTopicTasks.ToArray()); + } + + /// + /// Gets the number of active connections to the current . + /// + public Int32 ConnectionCount => Connections.Count(); + + /// + /// Gets a collection of active connections to the current . + /// + public IEnumerable Connections => ConnectionDictionary.Values; + + /// + /// Gets the format that is used to serialize enqueued message bodies. + /// + public SerializationFormat MessageBodySerializationFormat + { + get; + } + + /// + /// Gets the number of queues within the current . + /// + public Int32 QueueCount => QueuePaths.Count(); + + /// + /// Gets a collection of available queue paths for the current . + /// + public IEnumerable QueuePaths => QueueDictionary.Keys; + + /// + /// Gets the number of topics within the current . + /// + public Int32 TopicCount => TopicPaths.Count(); + + /// + /// Gets a collection of available topic paths for the current . + /// + public IEnumerable TopicPaths => TopicDictionary.Keys; + + /// + /// Represents a collection of active connections to the current , which are keyed by + /// identifier. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly ConcurrentDictionary ConnectionDictionary = new ConcurrentDictionary(); + + /// + /// Represents a collection of available queues for the current . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly ConcurrentDictionary QueueDictionary = new ConcurrentDictionary(); + + /// + /// Represents a collection of available topics for the current . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly ConcurrentDictionary TopicDictionary = new ConcurrentDictionary(); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransportConnection.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransportConnection.cs new file mode 100644 index 00000000..ad71ead9 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransportConnection.cs @@ -0,0 +1,108 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Core.Extensions; +using System; +using System.Diagnostics; +using System.Linq; + +namespace RapidField.SolidInstruments.Messaging.TransportPrimitives +{ + /// + /// Represents a client connection to an . + /// + /// + /// is the default implementation of . + /// + internal sealed class MessageTransportConnection : Instrument, IMessageTransportConnection + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated transport. + /// + /// + /// is . + /// + [DebuggerHidden] + internal MessageTransportConnection(IMessageTransport transport) + : base() + { + Identifier = Guid.NewGuid(); + State = MessageTransportConnectionState.Open; + TransportReference = transport.RejectIf().IsNull(nameof(transport)).TargetArgument; + } + + /// + /// Closes the current as an idempotent operation. + /// + public void Close() + { + if (State == MessageTransportConnectionState.Open) + { + State = MessageTransportConnectionState.Closed; + + if (TransportReference.Connections.Any(connection => connection.Identifier == Identifier)) + { + TransportReference.CloseConnection(this); + } + } + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) + { + try + { + if (disposing) + { + Close(); + } + } + finally + { + base.Dispose(disposing); + } + } + + /// + /// Gets a value that uniquely identifies the current . + /// + public Guid Identifier + { + get; + } + + /// + /// Gets the state of the current . + /// + public MessageTransportConnectionState State + { + get; + private set; + } + + /// + /// Gets the associated . + /// + /// + /// The connection is closed. + /// + public IMessageTransport Transport => State == MessageTransportConnectionState.Open ? TransportReference : throw new MessageTransportConnectionClosedException($"Connection {Identifier.ToSerializedString()} is closed."); + + /// + /// Represents the associated . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly IMessageTransport TransportReference; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransportConnectionState.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransportConnectionState.cs index de6aa6f7..6895e10f 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransportConnectionState.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransportConnectionState.cs @@ -9,7 +9,7 @@ namespace RapidField.SolidInstruments.Messaging.TransportPrimitives /// /// Represents the state of an . /// - internal enum MessageTransportConnectionState : Int32 + public enum MessageTransportConnectionState : Int32 { /// /// The connection's state is not specified. diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessagingEntity.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessagingEntity.cs index bbd5c271..a6e41913 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessagingEntity.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessagingEntity.cs @@ -799,42 +799,42 @@ public IMessagingEntityPath Path private Boolean CurrentStatePermitsEnqueue => OperationalState == MessagingEntityOperationalState.EnqueueOnly || OperationalState == MessagingEntityOperationalState.Ready; /// - /// Represents the default format that is used to serialize enqueued message bodies. + /// Represents the default length of time to wait for a message to be enqueued before raising an exception. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const SerializationFormat DefaultMessageBodySerializationFormat = PrimitiveMessage.DefaultBodySerializationFormat; + internal static readonly TimeSpan DefaultEnqueueTimeoutThreshold = TimeSpan.FromSeconds(11); /// - /// Represents the default length of time to wait for a message to be enqueued before raising an exception. + /// Represents the default length of time that a locked message is held before abandoning the associated token and making + /// the message available for processing. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private static readonly TimeSpan DefaultEnqueueTimeoutThreshold = TimeSpan.FromSeconds(11); + internal static readonly TimeSpan DefaultMessageLockExpirationThreshold = TimeSpan.FromMinutes(3); /// - /// Represents the default length of time that a locked message is held before abandoning the associated token and making - /// the message available for processing. + /// Represents the minimum permissible length of time to wait for a message to be enqueued before raising an exception. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private static readonly TimeSpan DefaultMessageLockExpirationThreshold = TimeSpan.FromMinutes(3); + internal static readonly TimeSpan EnqueueTimeoutThresholdFloor = TimeSpan.FromSeconds(2); /// - /// Represents the length of time that waits between attempts when the entity - /// is unavailable. + /// Represents the minimum permissible length of time that a locked message is held before abandoning the associated token + /// and making the message available for processing. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private static readonly TimeSpan EnqueueDelayDuration = TimeSpan.FromMilliseconds(3); + internal static readonly TimeSpan MessageLockExpirationThresholdFloor = TimeSpan.FromSeconds(8); /// - /// Represents the minimum permissible length of time to wait for a message to be enqueued before raising an exception. + /// Represents the default format that is used to serialize enqueued message bodies. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private static readonly TimeSpan EnqueueTimeoutThresholdFloor = TimeSpan.FromSeconds(2); + private const SerializationFormat DefaultMessageBodySerializationFormat = PrimitiveMessage.DefaultBodySerializationFormat; /// - /// Represents the minimum permissible length of time that a locked message is held before abandoning the associated token - /// and making the message available for processing. + /// Represents the length of time that waits between attempts when the entity + /// is unavailable. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private static readonly TimeSpan MessageLockExpirationThresholdFloor = TimeSpan.FromSeconds(8); + private static readonly TimeSpan EnqueueDelayDuration = TimeSpan.FromMilliseconds(3); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessagingEntityOperationalState.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessagingEntityOperationalState.cs index d874fd2e..69c0ea7c 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessagingEntityOperationalState.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessagingEntityOperationalState.cs @@ -3,50 +3,42 @@ // ================================================================================================================================= using System; -using System.Runtime.Serialization; namespace RapidField.SolidInstruments.Messaging.TransportPrimitives { /// /// Represents the operational state of an . /// - [DataContract] internal enum MessagingEntityOperationalState : Int32 { /// /// The entity's operational state is not specified. /// - [EnumMember] Unspecified = 0, /// /// The entity is enabled for both enqueue and dequeue operations. /// - [EnumMember] Ready = 1, /// /// The entity is enabled for dequeue operations only. /// - [EnumMember] DequeueOnly = 2, /// /// The entity is enabled for enqueue operations only. /// - [EnumMember] EnqueueOnly = 3, /// /// The entity is temporarily disabled. /// - [EnumMember] Paused = 4, /// /// entity is permanently disabled. /// - [EnumMember] Disabled = 5 } } \ No newline at end of file From dbc1d29195901ec363dbbea81f5959af2628012a Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Sun, 15 Mar 2020 22:29:07 -0500 Subject: [PATCH 16/55] More extensions to messaging primitives. --- .../AzureServiceBusClientFactory.cs | 25 +- .../TransportPrimitives/IMessageQueue.cs | 2 +- .../IMessageQueueClient.cs | 13 + .../IMessageSubscriptionClient.cs | 22 ++ .../TransportPrimitives/IMessageTopic.cs | 24 +- .../IMessageTopicClient.cs | 13 + .../TransportPrimitives/IMessageTransport.cs | 223 +++++++++++ .../IMessageTransportConnection.cs | 51 ++- .../TransportPrimitives/IMessagingEntity.cs | 2 +- .../IMessagingEntityClient.cs | 36 ++ .../IMessagingEntityReceiveClient.cs | 28 ++ .../IMessagingEntitySendClient.cs | 35 ++ .../TransportPrimitives/MessageQueueClient.cs | 55 +++ .../TransportPrimitives/MessageTopic.cs | 69 +++- .../TransportPrimitives/MessageTransport.cs | 350 +++++++++++++++++- .../MessageTransportConnection.cs | 156 ++++++++ .../MessagingEntityClient.cs | 238 ++++++++++++ .../MessagingEntityOperationalState.cs | 2 +- 18 files changed, 1313 insertions(+), 31 deletions(-) create mode 100644 src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageQueueClient.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageSubscriptionClient.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTopicClient.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessagingEntityClient.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessagingEntityReceiveClient.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessagingEntitySendClient.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageQueueClient.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessagingEntityClient.cs diff --git a/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusClientFactory.cs b/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusClientFactory.cs index 0bb860c2..e53aec58 100644 --- a/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusClientFactory.cs +++ b/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusClientFactory.cs @@ -185,9 +185,12 @@ private ITopicClient CreateTopicClient(ServiceBusConnection connection /// A task representing the asynchronous operation. /// [DebuggerHidden] - private Task EnsureQueueExistanceAsync(IMessagingEntityPath queuePath) => ManagementClient.QueueExistsAsync(queuePath.ToString()).ContinueWith(queueExistsTask => + private Task EnsureQueueExistanceAsync(IMessagingEntityPath queuePath) => ManagementClient.QueueExistsAsync(queuePath.ToString()).ContinueWith(async queueExistsTask => { - return queueExistsTask.Result ? Task.CompletedTask : ManagementClient.CreateQueueAsync(queuePath.ToString()); + if (queueExistsTask.Result == false) + { + await ManagementClient.CreateQueueAsync(queuePath.ToString()).ConfigureAwait(false); + } }); /// @@ -203,12 +206,15 @@ private Task EnsureQueueExistanceAsync(IMessagingEntityPath queuePath) => Manage /// A task representing the asynchronous operation. /// [DebuggerHidden] - private Task EnsureSubscriptionExistanceAsync(IMessagingEntityPath topicPath, String subscriptionName) => EnsureTopicExistanceAsync(topicPath).ContinueWith(ensureTopicExistenceTask => + private Task EnsureSubscriptionExistanceAsync(IMessagingEntityPath topicPath, String subscriptionName) => EnsureTopicExistanceAsync(topicPath).ContinueWith(async ensureTopicExistenceTask => { - return ManagementClient.SubscriptionExistsAsync(topicPath.ToString(), subscriptionName).ContinueWith(subscriptionExistsTask => + await ManagementClient.SubscriptionExistsAsync(topicPath.ToString(), subscriptionName).ContinueWith(async subscriptionExistsTask => { - return subscriptionExistsTask.Result ? Task.CompletedTask : ManagementClient.CreateSubscriptionAsync(topicPath.ToString(), subscriptionName); - }); + if (subscriptionExistsTask.Result == false) + { + await ManagementClient.CreateSubscriptionAsync(topicPath.ToString(), subscriptionName).ConfigureAwait(false); + } + }).ConfigureAwait(false); }); /// @@ -221,9 +227,12 @@ private Task EnsureSubscriptionExistanceAsync(IMessagingEntityPath topicPath, St /// A task representing the asynchronous operation. /// [DebuggerHidden] - private Task EnsureTopicExistanceAsync(IMessagingEntityPath topicPath) => ManagementClient.TopicExistsAsync(topicPath.ToString()).ContinueWith(topicExistsTask => + private Task EnsureTopicExistanceAsync(IMessagingEntityPath topicPath) => ManagementClient.TopicExistsAsync(topicPath.ToString()).ContinueWith(async topicExistsTask => { - return topicExistsTask.Result ? Task.CompletedTask : ManagementClient.CreateTopicAsync(topicPath.ToString()); + if (topicExistsTask.Result == false) + { + await ManagementClient.CreateTopicAsync(topicPath.ToString()).ConfigureAwait(false); + } }); /// diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageQueue.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageQueue.cs index f09cf36f..6104a709 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageQueue.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageQueue.cs @@ -11,7 +11,7 @@ namespace RapidField.SolidInstruments.Messaging.TransportPrimitives /// /// Represents a message queue. /// - internal interface IMessageQueue : IMessagingEntity + public interface IMessageQueue : IMessagingEntity { /// /// Asynchronously and non-destructively returns the next available messages from the current , diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageQueueClient.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageQueueClient.cs new file mode 100644 index 00000000..08b8e5eb --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageQueueClient.cs @@ -0,0 +1,13 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +namespace RapidField.SolidInstruments.Messaging.TransportPrimitives +{ + /// + /// Represents a client that interacts with an . + /// + public interface IMessageQueueClient : IMessagingEntityReceiveClient, IMessagingEntitySendClient + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageSubscriptionClient.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageSubscriptionClient.cs new file mode 100644 index 00000000..f7282136 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageSubscriptionClient.cs @@ -0,0 +1,22 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.Messaging.TransportPrimitives +{ + /// + /// Represents a client that interacts with an subscription. + /// + public interface IMessageSubscriptionClient : IMessagingEntityReceiveClient + { + /// + /// Gets the unique name of the associated subscription. + /// + String SubscriptionName + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTopic.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTopic.cs index 83b4dc11..5f67e133 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTopic.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTopic.cs @@ -12,7 +12,7 @@ namespace RapidField.SolidInstruments.Messaging.TransportPrimitives /// /// Represents a message topic. /// - internal interface IMessageTopic : IMessagingEntity + public interface IMessageTopic : IMessagingEntity { /// /// Asynchronously creates a new subscription to the current . @@ -91,6 +91,28 @@ internal interface IMessageTopic : IMessagingEntity /// Task DestroySubscriptionAsync(String subscriptionName); + /// + /// Attempts to create a new subscription to the current . + /// + /// + /// The unique name of the subscription. + /// + /// + /// if the subscription was successfully created, otherwise . + /// + Boolean TryCreateSubscription(String subscriptionName); + + /// + /// Attempts to destroy the specified subscription to the current . + /// + /// + /// The unique name of the subscription. + /// + /// + /// if the subscription was successfully destroyed, otherwise . + /// + Boolean TryDestroySubscription(String subscriptionName); + /// /// Gets the number of subscriptions to the current . /// diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTopicClient.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTopicClient.cs new file mode 100644 index 00000000..c77cd32c --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTopicClient.cs @@ -0,0 +1,13 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +namespace RapidField.SolidInstruments.Messaging.TransportPrimitives +{ + /// + /// Represents a client that interacts with an . + /// + public interface IMessageTopicClient : IMessagingEntitySendClient + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransport.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransport.cs index d6bf15c7..df760caf 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransport.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransport.cs @@ -116,6 +116,33 @@ public interface IMessageTransport : IInstrument /// Task CreateQueueAsync(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold, TimeSpan enqueueTimeoutThreshold); + /// + /// Asynchronously creates a new subscription. + /// + /// + /// A unique textual path that identifies the subscription topic. + /// + /// + /// The unique name of the subscription. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is empty. + /// + /// + /// is -or- is + /// . + /// + /// + /// The object is disposed. + /// + /// + /// The specified subscription already exists. + /// + Task CreateSubscriptionAsync(IMessagingEntityPath path, String subscriptionName); + /// /// Asynchronously creates a new topic. /// @@ -215,6 +242,33 @@ public interface IMessageTransport : IInstrument /// Task DestroyQueueAsync(IMessagingEntityPath path); + /// + /// Asynchronously destroys the specified subscription. + /// + /// + /// A unique textual path that identifies the subscription topic. + /// + /// + /// The unique name of the subscription. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is empty. + /// + /// + /// is -or- is + /// . + /// + /// + /// The object is disposed. + /// + /// + /// The specified subscription does not exist. + /// + Task DestroySubscriptionAsync(IMessagingEntityPath path, String subscriptionName); + /// /// Asynchronously destroys the specified topic. /// @@ -252,6 +306,147 @@ public interface IMessageTransport : IInstrument /// Boolean QueueExists(IMessagingEntityPath path); + /// + /// Asynchronously requests the specified number of messages from the specified queue. + /// + /// + /// A unique textual path that identifies the queue. + /// + /// + /// The maximum number of messages to read from the queue. + /// + /// + /// A task representing the asynchronous operation and containing the dequeued messages, if any. + /// + /// + /// is . + /// + /// + /// is less than zero. + /// + /// + /// The specified queue does not exist. + /// + /// + /// The object is disposed. + /// + /// + /// The operation timed out. + /// + Task> ReceiveFromQueueAsync(IMessagingEntityPath path, Int32 count); + + /// + /// Asynchronously requests the specified number of messages from the specified topic. + /// + /// + /// A unique textual path that identifies the topic. + /// + /// + /// The unique name of the subscription. + /// + /// + /// The maximum number of messages to read from the topic. + /// + /// + /// A task representing the asynchronous operation and containing the dequeued messages, if any. + /// + /// + /// is empty. + /// + /// + /// is -or- is + /// . + /// + /// + /// is less than zero. + /// + /// + /// The specified topic does not exist. + /// + /// + /// The object is disposed. + /// + /// + /// The operation timed out. + /// + Task> ReceiveFromTopicAsync(IMessagingEntityPath path, String subscriptionName, Int32 count); + + /// + /// Asynchronously sends the specified message to the specified queue. + /// + /// + /// A unique textual path that identifies the queue. + /// + /// + /// The message to send. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is -or- is . + /// + /// + /// The specified queue does not exist. + /// + /// + /// The object is disposed. + /// + /// + /// The operation timed out. + /// + Task SendToQueueAsync(IMessagingEntityPath path, PrimitiveMessage message); + + /// + /// Asynchronously sends the specified message to the specified topic. + /// + /// + /// A unique textual path that identifies the topic. + /// + /// + /// The message to send. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is -or- is . + /// + /// + /// The specified topic does not exist. + /// + /// + /// The object is disposed. + /// + /// + /// The operation timed out. + /// + Task SendToTopicAsync(IMessagingEntityPath path, PrimitiveMessage message); + + /// + /// Returns a value indicating whether or not the specified subscription exists. + /// + /// + /// A unique textual path that identifies the subscription topic. + /// + /// + /// The unique name of the subscription. + /// + /// + /// if the subscription exists, otherwise . + /// + /// + /// is empty. + /// + /// + /// is -or- is + /// . + /// + /// + /// The object is disposed. + /// + Boolean SubscriptionExists(IMessagingEntityPath path, String subscriptionName); + /// /// Returns a value indicating whether or not the specified topic exists. /// @@ -314,6 +509,20 @@ public interface IMessageTransport : IInstrument /// Boolean TryCreateQueue(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold, TimeSpan enqueueTimeoutThreshold); + /// + /// Attempts to create a new subscription. + /// + /// + /// A unique textual path that identifies the subscription topic. + /// + /// + /// The unique name of the subscription. + /// + /// + /// if the subscription was successfully created, otherwise . + /// + Boolean TryCreateSubscription(IMessagingEntityPath path, String subscriptionName); + /// /// Attempts to create a new topic. /// @@ -370,6 +579,20 @@ public interface IMessageTransport : IInstrument /// Boolean TryDestroyQueue(IMessagingEntityPath path); + /// + /// Attempts to destroy the specified subscription. + /// + /// + /// A unique textual path that identifies the subscription topic. + /// + /// + /// The unique name of the subscription. + /// + /// + /// if the subscription was successfully destroyed, otherwise . + /// + Boolean TryDestroySubscription(IMessagingEntityPath path, String subscriptionName); + /// /// Attempts to destroy the specified topic. /// diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransportConnection.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransportConnection.cs index abd649e2..078c7347 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransportConnection.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransportConnection.cs @@ -2,6 +2,7 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= +using RapidField.SolidInstruments.Core; using System; namespace RapidField.SolidInstruments.Messaging.TransportPrimitives @@ -14,7 +15,55 @@ public interface IMessageTransportConnection : IAsyncDisposable, IDisposable /// /// Closes the current as an idempotent operation. /// - public void Close(); + void Close(); + + /// + /// Registers the specified message handler for the specified queue. + /// + /// + /// A unique textual path that identifies the queue. + /// + /// + /// An action to perform upon message receipt. + /// + /// + /// is -or- is + /// . + /// + /// + /// The specified queue does not exist. + /// + /// + /// The object is disposed. + /// + void RegisterQueueHandler(IMessagingEntityPath queuePath, Action handleMessageAction); + + /// + /// Registers the specified message handler for the specified topic subscription. + /// + /// + /// A unique textual path that identifies the topic. + /// + /// + /// The unique name of the subscription. + /// + /// + /// An action to perform upon message receipt. + /// + /// + /// is empty. + /// + /// + /// is -or- is + /// -or- is . + /// + /// + /// The specified subscription does not exist. + /// + /// + /// The object is disposed. + /// + void RegisterSubscriptionHandler(IMessagingEntityPath topicPath, String subscriptionName, Action handleMessageAction); /// /// Gets a value that uniquely identifies the current . diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessagingEntity.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessagingEntity.cs index 76f6a13e..fdc1bce3 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessagingEntity.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessagingEntity.cs @@ -12,7 +12,7 @@ namespace RapidField.SolidInstruments.Messaging.TransportPrimitives /// /// Represents a messaging entity. /// - internal interface IMessagingEntity : IAsyncDisposable, IDisposable + public interface IMessagingEntity : IAsyncDisposable, IDisposable { /// /// Asynchronously notifies the current that a locked message was not processed and can be diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessagingEntityClient.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessagingEntityClient.cs new file mode 100644 index 00000000..6db97a2d --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessagingEntityClient.cs @@ -0,0 +1,36 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +namespace RapidField.SolidInstruments.Messaging.TransportPrimitives +{ + /// + /// Represents a client that interacts with an . + /// + public interface IMessagingEntityClient + { + /// + /// Gets the client's connection to the associated entity's . + /// + IMessageTransportConnection Connection + { + get; + } + + /// + /// Gets the entity type of the associated . + /// + MessagingEntityType EntityType + { + get; + } + + /// + /// Gets the unique textual path for the messaging entity with which the client transacts. + /// + IMessagingEntityPath Path + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessagingEntityReceiveClient.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessagingEntityReceiveClient.cs new file mode 100644 index 00000000..13307ca7 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessagingEntityReceiveClient.cs @@ -0,0 +1,28 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.Messaging.TransportPrimitives +{ + /// + /// Represents a client that facilitates receive operations to an . + /// + public interface IMessagingEntityReceiveClient : IMessagingEntityClient + { + /// + /// Registers the specified message handler for the associated . + /// + /// + /// An action to perform upon message receipt. + /// + /// + /// is . + /// + /// + /// The connection is closed. + /// + void RegisterMessageHandler(Action handleMessageAction); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessagingEntitySendClient.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessagingEntitySendClient.cs new file mode 100644 index 00000000..c2898b06 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessagingEntitySendClient.cs @@ -0,0 +1,35 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Threading.Tasks; + +namespace RapidField.SolidInstruments.Messaging.TransportPrimitives +{ + /// + /// Represents a client that facilitates send operations to an . + /// + public interface IMessagingEntitySendClient : IMessagingEntityClient + { + /// + /// Asynchronously sends the specified message to the associated . + /// + /// + /// The message to send. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// The operation timed out. + /// + /// + /// The connection is closed. + /// + Task SendAsync(PrimitiveMessage message); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageQueueClient.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageQueueClient.cs new file mode 100644 index 00000000..2fa7bdd7 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageQueueClient.cs @@ -0,0 +1,55 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Diagnostics; +using System.Threading.Tasks; + +namespace RapidField.SolidInstruments.Messaging.TransportPrimitives +{ + /// + /// Represents a client that interacts with an . + /// + /// + /// is the default implementation of . + /// + internal sealed class MessageQueueClient : MessagingEntityClient, IMessageQueueClient + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The client's connection to the associated entity's transport. + /// + /// + /// The unique textual path for the messaging entity with which the client transacts. + /// + /// + /// is -or- is . + /// + [DebuggerHidden] + internal MessageQueueClient(IMessageTransportConnection connection, IMessagingEntityPath path) + : base(connection, path, MessagingEntityType.Queue) + { + return; + } + + /// + /// Registers the specified message handler for the associated . + /// + /// + /// The connection with which to register . + /// + /// + /// An action to perform upon message receipt. + /// + protected override void RegisterMessageHandler(IMessageTransportConnection connection, Action handleMessageAction) => Task.Factory.StartNew(async () => + { + await EnsureQueueExistanceAsync(Path).ContinueWith(ensureQueueExistenceTask => + { + connection.RegisterQueueHandler(Path, handleMessageAction); + }).ConfigureAwait(false); + }).Wait(); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTopic.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTopic.cs index 1e76ba91..4d64b6bc 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTopic.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTopic.cs @@ -4,7 +4,6 @@ using RapidField.SolidInstruments.Core; using RapidField.SolidInstruments.Core.ArgumentValidation; -using RapidField.SolidInstruments.Core.Concurrency; using RapidField.SolidInstruments.Core.Extensions; using RapidField.SolidInstruments.Serialization; using System; @@ -230,14 +229,9 @@ public Task CreateSubscriptionAsync(String subscriptionName) => Task.Factory.Sta { RejectIfDisposed(); - if (SubscriptionQueues.ContainsKey(subscriptionName.RejectIf().IsNullOrEmpty(nameof(subscriptionName))) == false) + if (TryCreateSubscription(subscriptionName.RejectIf().IsNullOrEmpty(nameof(subscriptionName)))) { - var subscriptionQueue = new MessageQueue(Guid.NewGuid(), Path, OperationalState, MessageBodySerializationFormat, MessageLockExpirationThreshold, EnqueueTimeoutThreshold); - - if (SubscriptionQueues.TryAdd(subscriptionName, subscriptionQueue)) - { - return; - } + return; } throw new InvalidOperationException($"A subscription with the name \"{subscriptionName}\" already exists."); @@ -302,7 +296,58 @@ public Task DestroySubscriptionAsync(String subscriptionName) => Task.Factory.St { RejectIfDisposed(); - if (SubscriptionQueues.ContainsKey(subscriptionName.RejectIf().IsNullOrEmpty(nameof(subscriptionName)))) + if (TryDestroySubscription(subscriptionName.RejectIf().IsNullOrEmpty(nameof(subscriptionName)))) + { + return; + } + + throw new InvalidOperationException($"A subscription with the name \"{subscriptionName}\" does not exist."); + }); + + /// + /// Attempts to create a new subscription to the current . + /// + /// + /// The unique name of the subscription. + /// + /// + /// if the subscription was successfully created, otherwise . + /// + public Boolean TryCreateSubscription(String subscriptionName) + { + if (IsDisposedOrDisposing || subscriptionName.IsNullOrEmpty()) + { + return false; + } + else if (SubscriptionQueues.ContainsKey(subscriptionName) == false) + { + var subscriptionQueue = new MessageQueue(Guid.NewGuid(), Path, OperationalState, MessageBodySerializationFormat, MessageLockExpirationThreshold, EnqueueTimeoutThreshold); + + if (SubscriptionQueues.TryAdd(subscriptionName, subscriptionQueue)) + { + return true; + } + } + + return false; + } + + /// + /// Attempts to destroy the specified subscription to the current . + /// + /// + /// The unique name of the subscription. + /// + /// + /// if the subscription was successfully destroyed, otherwise . + /// + public Boolean TryDestroySubscription(String subscriptionName) + { + if (IsDisposedOrDisposing || subscriptionName.IsNullOrEmpty()) + { + return false; + } + else if (SubscriptionQueues.ContainsKey(subscriptionName)) { if (SubscriptionQueues.TryRemove(subscriptionName, out var subscriptionQueue)) { @@ -311,12 +356,12 @@ public Task DestroySubscriptionAsync(String subscriptionName) => Task.Factory.St subscriptionQueue.Dispose(); } - return; + return true; } } - throw new InvalidOperationException($"A subscription with the name \"{subscriptionName}\" does not exist."); - }); + return false; + } /// /// Attempts to add the specified message to a list of locked messages. diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransport.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransport.cs index fb843698..13cda6c5 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransport.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransport.cs @@ -179,6 +179,43 @@ public Task CreateQueueAsync(IMessagingEntityPath path, TimeSpan messageLockExpi throw new InvalidOperationException($"The specified queue, \"{path}\", already exists"); }); + /// + /// Asynchronously creates a new subscription. + /// + /// + /// A unique textual path that identifies the subscription topic. + /// + /// + /// The unique name of the subscription. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is empty. + /// + /// + /// is -or- is + /// . + /// + /// + /// The object is disposed. + /// + /// + /// The specified subscription already exists. + /// + public Task CreateSubscriptionAsync(IMessagingEntityPath path, String subscriptionName) => Task.Factory.StartNew(() => + { + RejectIfDisposed(); + + if (TryCreateSubscription(path.RejectIf().IsNull(nameof(path)).TargetArgument, subscriptionName.RejectIf().IsNullOrEmpty(nameof(subscriptionName)))) + { + return; + } + + throw new InvalidOperationException($"The specified subscription, \"{subscriptionName}\", already exists"); + }); + /// /// Asynchronously creates a new topic. /// @@ -265,7 +302,7 @@ public Task CreateTopicAsync(IMessagingEntityPath path, TimeSpan messageLockExpi return; } - throw new InvalidOperationException($"The specified topic, \"{path}\", already exists"); + throw new InvalidOperationException($"Failed to create topic. The specified topic, \"{path}\", already exists"); }); /// @@ -284,7 +321,7 @@ public Task CreateTopicAsync(IMessagingEntityPath path, TimeSpan messageLockExpi /// The object is disposed. /// /// - /// The specified entity does not exist. + /// The specified queue does not exist. /// public Task DestroyQueueAsync(IMessagingEntityPath path) => Task.Factory.StartNew(() => { @@ -295,7 +332,44 @@ public Task DestroyQueueAsync(IMessagingEntityPath path) => Task.Factory.StartNe return; } - throw new InvalidOperationException($"The specified queue, \"{path}\", does not exist."); + throw new InvalidOperationException($"Failed to destroy queue. The specified queue, \"{path}\", does not exist."); + }); + + /// + /// Asynchronously destroys the specified subscription. + /// + /// + /// A unique textual path that identifies the subscription topic. + /// + /// + /// The unique name of the subscription. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is empty. + /// + /// + /// is -or- is + /// . + /// + /// + /// The object is disposed. + /// + /// + /// The specified subscription does not exist. + /// + public Task DestroySubscriptionAsync(IMessagingEntityPath path, String subscriptionName) => Task.Factory.StartNew(() => + { + RejectIfDisposed(); + + if (TryDestroySubscription(path.RejectIf().IsNull(nameof(path)).TargetArgument, subscriptionName.RejectIf().IsNullOrEmpty(nameof(subscriptionName)))) + { + return; + } + + throw new InvalidOperationException($"Failed to destroy subscription. The specified subscription, \"{subscriptionName}\", does not exist."); }); /// @@ -314,7 +388,7 @@ public Task DestroyQueueAsync(IMessagingEntityPath path) => Task.Factory.StartNe /// The object is disposed. /// /// - /// The specified entity does not exist. + /// The specified topic does not exist. /// public Task DestroyTopicAsync(IMessagingEntityPath path) => Task.Factory.StartNew(() => { @@ -325,7 +399,7 @@ public Task DestroyTopicAsync(IMessagingEntityPath path) => Task.Factory.StartNe return; } - throw new InvalidOperationException($"The specified topic, \"{path}\", does not exist."); + throw new InvalidOperationException($"Failed to destroy topic. The specified topic, \"{path}\", does not exist."); }); /// @@ -349,6 +423,189 @@ public Boolean QueueExists(IMessagingEntityPath path) return QueuePaths.Any(queuePath => queuePath == path.RejectIf().IsNull(nameof(path)).TargetArgument); } + /// + /// Asynchronously requests the specified number of messages from the specified queue. + /// + /// + /// A unique textual path that identifies the queue. + /// + /// + /// The maximum number of messages to read from the queue. + /// + /// + /// A task representing the asynchronous operation and containing the dequeued messages, if any. + /// + /// + /// is . + /// + /// + /// is less than zero. + /// + /// + /// The specified queue does not exist. + /// + /// + /// The object is disposed. + /// + /// + /// The operation timed out. + /// + public Task> ReceiveFromQueueAsync(IMessagingEntityPath path, Int32 count) + { + if (QueueDictionary.TryGetValue(path.RejectIf().IsNull(nameof(path)).TargetArgument, out var queue)) + { + return queue.DequeueAsync(count); + } + + throw new InvalidOperationException($"Failed to receive message(s). The specified queue, \"{path}\", does not exist."); + } + + /// + /// Asynchronously requests the specified number of messages from the specified topic. + /// + /// + /// A unique textual path that identifies the topic. + /// + /// + /// The unique name of the subscription. + /// + /// + /// The maximum number of messages to read from the topic. + /// + /// + /// A task representing the asynchronous operation and containing the dequeued messages, if any. + /// + /// + /// is empty. + /// + /// + /// is -or- is + /// . + /// + /// + /// is less than zero. + /// + /// + /// The specified topic does not exist. + /// + /// + /// The object is disposed. + /// + /// + /// The operation timed out. + /// + public Task> ReceiveFromTopicAsync(IMessagingEntityPath path, String subscriptionName, Int32 count) + { + if (TopicDictionary.TryGetValue(path.RejectIf().IsNull(nameof(path)).TargetArgument, out var topic)) + { + return topic.DequeueAsync(subscriptionName, count); + } + + throw new InvalidOperationException($"Failed to receive message(s). The specified topic, \"{path}\", does not exist."); + } + + /// + /// Asynchronously sends the specified message to the specified queue. + /// + /// + /// A unique textual path that identifies the queue. + /// + /// + /// The message to send. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is -or- is . + /// + /// + /// The specified queue does not exist. + /// + /// + /// The object is disposed. + /// + /// + /// The operation timed out. + /// + public Task SendToQueueAsync(IMessagingEntityPath path, PrimitiveMessage message) + { + if (QueueDictionary.TryGetValue(path.RejectIf().IsNull(nameof(path)).TargetArgument, out var queue)) + { + return queue.EnqueueAsync(message); + } + + throw new InvalidOperationException($"Failed to send message. The specified queue, \"{path}\", does not exist."); + } + + /// + /// Asynchronously sends the specified message to the specified topic. + /// + /// + /// A unique textual path that identifies the topic. + /// + /// + /// The message to send. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is -or- is . + /// + /// + /// The specified topic does not exist. + /// + /// + /// The object is disposed. + /// + /// + /// The operation timed out. + /// + public Task SendToTopicAsync(IMessagingEntityPath path, PrimitiveMessage message) + { + if (TopicDictionary.TryGetValue(path.RejectIf().IsNull(nameof(path)).TargetArgument, out var topic)) + { + return topic.EnqueueAsync(message); + } + + throw new InvalidOperationException($"Failed to send message. The specified topic, \"{path}\", does not exist."); + } + + /// + /// Returns a value indicating whether or not the specified subscription exists. + /// + /// + /// A unique textual path that identifies the subscription topic. + /// + /// + /// The unique name of the subscription. + /// + /// + /// if the subscription exists, otherwise . + /// + /// + /// is empty. + /// + /// + /// is -or- is + /// . + /// + /// + /// The object is disposed. + /// + public Boolean SubscriptionExists(IMessagingEntityPath path, String subscriptionName) + { + RejectIfDisposed(); + + if (TopicDictionary.TryGetValue(path.RejectIf().IsNull(nameof(path)).TargetArgument, out var topic)) + { + return topic.SubscriptionNames.Contains(subscriptionName.RejectIf().IsNullOrEmpty(nameof(subscriptionName))); + } + + return false; + } + /// /// Returns a value indicating whether or not the specified topic exists. /// @@ -364,7 +621,11 @@ public Boolean QueueExists(IMessagingEntityPath path) /// /// The object is disposed. /// - public Boolean TopicExists(IMessagingEntityPath path) => TopicPaths.Any(topciPath => topciPath == path.RejectIf().IsNull(nameof(path)).TargetArgument); + public Boolean TopicExists(IMessagingEntityPath path) + { + RejectIfDisposed(); + return TopicPaths.Any(topciPath => topciPath == path.RejectIf().IsNull(nameof(path)).TargetArgument); + } /// /// Attempts to create a new queue. @@ -434,6 +695,48 @@ public Boolean TryCreateQueue(IMessagingEntityPath path, TimeSpan messageLockExp return false; } + /// + /// Attempts to create a new subscription. + /// + /// + /// A unique textual path that identifies the subscription topic. + /// + /// + /// The unique name of the subscription. + /// + /// + /// if the subscription was successfully created, otherwise . + /// + public Boolean TryCreateSubscription(IMessagingEntityPath path, String subscriptionName) + { + if (IsDisposedOrDisposing || path is null || subscriptionName is null) + { + return false; + } + + using (var controlToken = StateControl.Enter()) + { + if (IsDisposedOrDisposing) + { + return false; + } + else if (TopicExists(path) == false) + { + if (TryCreateTopic(path) == false) + { + return false; + } + } + + if (TopicDictionary.TryGetValue(path, out var topic)) + { + return topic.TryCreateSubscription(subscriptionName); + } + } + + return false; + } + /// /// Attempts to create a new topic. /// @@ -535,6 +838,41 @@ public Boolean TryDestroyQueue(IMessagingEntityPath path) return false; } + /// + /// Attempts to destroy the specified subscription. + /// + /// + /// A unique textual path that identifies the subscription topic. + /// + /// + /// The unique name of the subscription. + /// + /// + /// if the subscription was successfully destroyed, otherwise . + /// + public Boolean TryDestroySubscription(IMessagingEntityPath path, String subscriptionName) + { + if (IsDisposedOrDisposing || path is null || subscriptionName is null) + { + return false; + } + + using (var controlToken = StateControl.Enter()) + { + if (IsDisposedOrDisposing) + { + return false; + } + + if (TopicDictionary.TryGetValue(path, out var topic)) + { + return topic.TryDestroySubscription(subscriptionName); + } + } + + return false; + } + /// /// Attempts to destroy the specified topic. /// diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransportConnection.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransportConnection.cs index ad71ead9..643e17b3 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransportConnection.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransportConnection.cs @@ -6,6 +6,7 @@ using RapidField.SolidInstruments.Core.ArgumentValidation; using RapidField.SolidInstruments.Core.Extensions; using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -32,6 +33,7 @@ internal sealed class MessageTransportConnection : Instrument, IMessageTransport internal MessageTransportConnection(IMessageTransport transport) : base() { + Handlers = new List(); Identifier = Guid.NewGuid(); State = MessageTransportConnectionState.Open; TransportReference = transport.RejectIf().IsNull(nameof(transport)).TargetArgument; @@ -53,6 +55,76 @@ public void Close() } } + /// + /// Registers the specified message handler for the specified queue. + /// + /// + /// A unique textual path that identifies the queue. + /// + /// + /// An action to perform upon message receipt. + /// + /// + /// is -or- is + /// . + /// + /// + /// The specified queue does not exist. + /// + /// + /// The object is disposed. + /// + public void RegisterQueueHandler(IMessagingEntityPath queuePath, Action handleMessageAction) + { + RejectIfDisposed(); + + if (Transport.QueueExists(queuePath)) + { + Handlers.Add(new Handler(queuePath, MessagingEntityType.Queue, null, handleMessageAction)); + return; + } + + throw new InvalidOperationException($"Failed to register queue handler. The specified queue, \"{queuePath}\", does not exist."); + } + + /// + /// Registers the specified message handler for the specified topic subscription. + /// + /// + /// A unique textual path that identifies the topic. + /// + /// + /// The unique name of the subscription. + /// + /// + /// An action to perform upon message receipt. + /// + /// + /// is empty. + /// + /// + /// is -or- is + /// -or- is . + /// + /// + /// The specified subscription does not exist. + /// + /// + /// The object is disposed. + /// + public void RegisterSubscriptionHandler(IMessagingEntityPath topicPath, String subscriptionName, Action handleMessageAction) + { + RejectIfDisposed(); + + if (Transport.SubscriptionExists(topicPath, subscriptionName)) + { + Handlers.Add(new Handler(topicPath, MessagingEntityType.Topic, subscriptionName, handleMessageAction)); + return; + } + + throw new InvalidOperationException($"Failed to register subscription handler. The specified subscription, \"{subscriptionName}\", does not exist."); + } + /// /// Releases all resources consumed by the current . /// @@ -99,10 +171,94 @@ public MessageTransportConnectionState State /// public IMessageTransport Transport => State == MessageTransportConnectionState.Open ? TransportReference : throw new MessageTransportConnectionClosedException($"Connection {Identifier.ToSerializedString()} is closed."); + /// + /// Represents a collection of actions that is performed upon message receipt from specific entities. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly ICollection Handlers; + /// /// Represents the associated . /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly IMessageTransport TransportReference; + + /// + /// Represents an action that is performed upon message receipt from a specific entity. + /// + private sealed class Handler + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique textual path that identifies the associated entity. + /// + /// + /// The entity type of the associated entity. + /// + /// + /// The unique name of the associated subscription, or if the entity is a queue. + /// + /// + /// An action to perform upon message receipt. + /// + /// + /// is empty and is equal to + /// . + /// + /// + /// is -or- is + /// -or- is and + /// is equal to . + /// + /// + /// is equal to . + /// + [DebuggerHidden] + internal Handler(IMessagingEntityPath path, MessagingEntityType entityType, String subscriptionName, Action handleMessageAction) + { + EntityType = entityType.RejectIf().IsEqualToValue(MessagingEntityType.Unspecified, nameof(entityType)); + HandleMessageAction = handleMessageAction.RejectIf().IsNull(nameof(handleMessageAction)); + Path = path.RejectIf().IsNull(nameof(path)).TargetArgument; + SubscriptionName = entityType == MessagingEntityType.Queue ? null : subscriptionName.RejectIf().IsNullOrEmpty(nameof(subscriptionName)); + } + + /// + /// Gets the entity type of the associated . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal MessagingEntityType EntityType + { + get; + } + + /// + /// Gets an action to perform upon message receipt. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal Action HandleMessageAction + { + get; + } + + /// + /// Gets the unique textual path for the messaging entity. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal IMessagingEntityPath Path + { + get; + } + + /// + /// Gets the unique name of the associated subscription, or if the entity is a queue. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal String SubscriptionName + { + get; + } + } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessagingEntityClient.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessagingEntityClient.cs new file mode 100644 index 00000000..135a01f9 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessagingEntityClient.cs @@ -0,0 +1,238 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using System; +using System.Threading.Tasks; + +namespace RapidField.SolidInstruments.Messaging.TransportPrimitives +{ + /// + /// Represents a client that interacts with an . + /// + /// + /// is the default implementation of . + /// + internal abstract class MessagingEntityClient : IMessagingEntityReceiveClient, IMessagingEntitySendClient + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The client's connection to the associated entity's transport. + /// + /// + /// The unique textual path for the messaging entity with which the client transacts. + /// + /// + /// The entity type of the associated entity. + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + protected MessagingEntityClient(IMessageTransportConnection connection, IMessagingEntityPath path, MessagingEntityType entityType) + { + Connection = connection.RejectIf().IsNull(nameof(connection)).TargetArgument; + EntityType = entityType.RejectIf().IsEqualToValue(MessagingEntityType.Unspecified, nameof(entityType)); + Path = path.RejectIf().IsNull(nameof(path)).TargetArgument; + } + + /// + /// Registers the specified message handler for the associated . + /// + /// + /// An action to perform upon message receipt. + /// + /// + /// is . + /// + /// + /// The connection is closed. + /// + public void RegisterMessageHandler(Action handleMessageAction) + { + try + { + RegisterMessageHandler(Connection, handleMessageAction.RejectIf().IsNull(nameof(handleMessageAction))); + } + catch (ObjectDisposedException exception) + { + throw new MessageTransportConnectionClosedException("Failed to register message handler. The connection is closed.", exception); + } + } + + /// + /// Asynchronously sends the specified message to the associated . + /// + /// + /// The message to send. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// The operation timed out. + /// + /// + /// The connection is closed. + /// + public Task SendAsync(PrimitiveMessage message) => EntityType switch + { + MessagingEntityType.Queue => EnsureQueueExistanceAsync(Path).ContinueWith(async ensureQueueExistenceTask => + { + await Connection.Transport.SendToQueueAsync(Path, message).ConfigureAwait(false); + }), + MessagingEntityType.Topic => EnsureTopicExistanceAsync(Path).ContinueWith(async ensureTopicExistenceTask => + { + await Connection.Transport.SendToTopicAsync(Path, message).ConfigureAwait(false); + }), + _ => throw new UnsupportedSpecificationException($"The specified messaging entity type, {EntityType}, is not supported.") + }; + + /// + /// Asynchronously creates the specified queue if it does not exist. + /// + /// + /// The queue entity path. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// The transport connection is closed. + /// + protected Task EnsureQueueExistanceAsync(IMessagingEntityPath queuePath) + { + try + { + if (Connection.Transport.QueueExists(queuePath)) + { + return Task.CompletedTask; + } + + return Connection.Transport.CreateQueueAsync(queuePath); + } + catch (ObjectDisposedException exception) + { + throw new MessageTransportConnectionClosedException("Failed to ensure queue existence. The connection is closed.", exception); + } + } + + /// + /// Asynchronously creates the specified subscription if it does not exist. + /// + /// + /// The topic entity path for the subscription. + /// + /// + /// The name of the subscription. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is empty. + /// + /// + /// is -or- is + /// . + /// + /// + /// The transport connection is closed. + /// + protected Task EnsureSubscriptionExistanceAsync(IMessagingEntityPath topicPath, String subscriptionName) => EnsureTopicExistanceAsync(topicPath).ContinueWith(async ensureTopicExistenceTask => + { + try + { + if (Connection.Transport.SubscriptionExists(topicPath, subscriptionName)) + { + return; + } + + await Connection.Transport.CreateSubscriptionAsync(topicPath, subscriptionName).ConfigureAwait(false); + } + catch (ObjectDisposedException exception) + { + throw new MessageTransportConnectionClosedException("Failed to ensure subscription existence. The connection is closed.", exception); + } + }); + + /// + /// Asynchronously creates the specified topic if it does not exist. + /// + /// + /// The topic entity path. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// The transport connection is closed. + /// + protected Task EnsureTopicExistanceAsync(IMessagingEntityPath topicPath) + { + try + { + if (Connection.Transport.TopicExists(topicPath)) + { + return Task.CompletedTask; + } + + return Connection.Transport.CreateTopicAsync(topicPath); + } + catch (ObjectDisposedException exception) + { + throw new MessageTransportConnectionClosedException("Failed to ensure topic existence. The connection is closed.", exception); + } + } + + /// + /// Registers the specified message handler for the associated . + /// + /// + /// The connection with which to register . + /// + /// + /// An action to perform upon message receipt. + /// + protected abstract void RegisterMessageHandler(IMessageTransportConnection connection, Action handleMessageAction); + + /// + /// Gets the client's connection to the associated entity's . + /// + public IMessageTransportConnection Connection + { + get; + } + + /// + /// Gets the entity type of the associated . + /// + public MessagingEntityType EntityType + { + get; + } + + /// + /// Gets the unique textual path for the messaging entity with which the client transacts. + /// + public IMessagingEntityPath Path + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessagingEntityOperationalState.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessagingEntityOperationalState.cs index 69c0ea7c..9558e8c9 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessagingEntityOperationalState.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessagingEntityOperationalState.cs @@ -9,7 +9,7 @@ namespace RapidField.SolidInstruments.Messaging.TransportPrimitives /// /// Represents the operational state of an . /// - internal enum MessagingEntityOperationalState : Int32 + public enum MessagingEntityOperationalState : Int32 { /// /// The entity's operational state is not specified. From a7bb74db5c2f39ddd03d794f17914aa0aa7a601c Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Sat, 21 Mar 2020 11:58:42 -0500 Subject: [PATCH 17/55] Rounding out in-memory messaging classes. --- .../IMessageTransportConnection.cs | 3 + .../MessageSubscriptionClient.cs | 72 ++++ .../TransportPrimitives/MessageTopicClient.cs | 48 +++ .../TransportPrimitives/MessageTransport.cs | 49 ++- .../MessageTransportConnection.cs | 342 +++++++++++++++++- 5 files changed, 502 insertions(+), 12 deletions(-) create mode 100644 src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageSubscriptionClient.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTopicClient.cs diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransportConnection.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransportConnection.cs index 078c7347..9ad13553 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransportConnection.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransportConnection.cs @@ -15,6 +15,9 @@ public interface IMessageTransportConnection : IAsyncDisposable, IDisposable /// /// Closes the current as an idempotent operation. /// + /// + /// An exception was raised while closing the transport connection. + /// void Close(); /// diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageSubscriptionClient.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageSubscriptionClient.cs new file mode 100644 index 00000000..611e5838 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageSubscriptionClient.cs @@ -0,0 +1,72 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using System; +using System.Diagnostics; +using System.Threading.Tasks; + +namespace RapidField.SolidInstruments.Messaging.TransportPrimitives +{ + /// + /// Represents a client that interacts with an subscription. + /// + /// + /// is the default implementation of . + /// + internal sealed class MessageSubscriptionClient : MessagingEntityClient, IMessageSubscriptionClient + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The client's connection to the associated entity's transport. + /// + /// + /// The unique textual path for the messaging entity with which the client transacts. + /// + /// + /// The unique name of the associated subscription. + /// + /// + /// is empty. + /// + /// + /// is -or- is -or- + /// is . + /// + [DebuggerHidden] + internal MessageSubscriptionClient(IMessageTransportConnection connection, IMessagingEntityPath path, String subscriptionName) + : base(connection, path, MessagingEntityType.Topic) + { + SubscriptionName = subscriptionName.RejectIf().IsNullOrEmpty(nameof(subscriptionName)); + } + + /// + /// Registers the specified message handler for the associated . + /// + /// + /// The connection with which to register . + /// + /// + /// An action to perform upon message receipt. + /// + protected override void RegisterMessageHandler(IMessageTransportConnection connection, Action handleMessageAction) => Task.Factory.StartNew(async () => + { + await EnsureSubscriptionExistanceAsync(Path, SubscriptionName).ContinueWith(ensureSubscriptionExistenceTask => + { + connection.RegisterSubscriptionHandler(Path, SubscriptionName, handleMessageAction); + }).ConfigureAwait(false); + }).Wait(); + + /// + /// Gets the unique name of the associated subscription. + /// + public String SubscriptionName + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTopicClient.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTopicClient.cs new file mode 100644 index 00000000..825ef426 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTopicClient.cs @@ -0,0 +1,48 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Diagnostics; + +namespace RapidField.SolidInstruments.Messaging.TransportPrimitives +{ + /// + /// Represents a client that interacts with an . + /// + /// + /// is the default implementation of . + /// + internal sealed class MessageTopicClient : MessagingEntityClient, IMessageTopicClient + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The client's connection to the associated entity's transport. + /// + /// + /// The unique textual path for the messaging entity with which the client transacts. + /// + /// + /// is -or- is . + /// + [DebuggerHidden] + internal MessageTopicClient(IMessageTransportConnection connection, IMessagingEntityPath path) + : base(connection, path, MessagingEntityType.Topic) + { + return; + } + + /// + /// Registers the specified message handler for the associated . + /// + /// + /// The connection with which to register . + /// + /// + /// An action to perform upon message receipt. + /// + protected override void RegisterMessageHandler(IMessageTransportConnection connection, Action handleMessageAction) => throw new NotImplementedException("Message handlers cannot be registered with a topic. Message handlers for topics should be registered using a subscription client."); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransport.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransport.cs index 13cda6c5..b0d4ab64 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransport.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransport.cs @@ -4,12 +4,14 @@ using RapidField.SolidInstruments.Core; using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Core.Extensions; using RapidField.SolidInstruments.Serialization; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading; using System.Threading.Tasks; namespace RapidField.SolidInstruments.Messaging.TransportPrimitives @@ -22,16 +24,6 @@ namespace RapidField.SolidInstruments.Messaging.TransportPrimitives /// internal sealed class MessageTransport : Instrument, IMessageTransport { - /// - /// Initializes a new instance of the class. - /// - [DebuggerHidden] - internal MessageTransport() - : this(PrimitiveMessage.DefaultBodySerializationFormat) - { - return; - } - /// /// Initializes a new instance of the class. /// @@ -48,6 +40,16 @@ internal MessageTransport(SerializationFormat messageBodySerializationFormat) MessageBodySerializationFormat = messageBodySerializationFormat.RejectIf().IsEqualToValue(SerializationFormat.Unspecified, nameof(messageBodySerializationFormat)); } + /// + /// Initializes a new instance of the class. + /// + [DebuggerHidden] + private MessageTransport() + : this(PrimitiveMessage.DefaultBodySerializationFormat) + { + return; + } + /// /// Closes the specified connection as an idempotent operation. /// @@ -932,6 +934,15 @@ protected override void Dispose(Boolean disposing) } } + /// + /// Initializes a new in-memory instance. + /// + /// + /// A new in-memory instance. + /// + [DebuggerHidden] + private static IMessageTransport InitializeInstance() => new MessageTransport(); + /// /// Closes and disposes of all connections to the current . /// @@ -1054,6 +1065,24 @@ public SerializationFormat MessageBodySerializationFormat /// public IEnumerable TopicPaths => TopicDictionary.Keys; + /// + /// Gets an in-memory instance. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal static IMessageTransport Instance => LazyInstance.Value; + + /// + /// Represents a finalizer for static members of the class. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static readonly StaticMemberFinalizer Finalizer = new StaticMemberFinalizer(LazyInstance.Dispose); + + /// + /// Represents a lazily-initialized in-memory instance. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static readonly Lazy LazyInstance = new Lazy(InitializeInstance, LazyThreadSafetyMode.ExecutionAndPublication); + /// /// Represents a collection of active connections to the current , which are keyed by /// identifier. diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransportConnection.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransportConnection.cs index 643e17b3..767a2914 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransportConnection.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransportConnection.cs @@ -9,6 +9,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading; +using System.Threading.Tasks; namespace RapidField.SolidInstruments.Messaging.TransportPrimitives { @@ -35,6 +37,7 @@ internal MessageTransportConnection(IMessageTransport transport) { Handlers = new List(); Identifier = Guid.NewGuid(); + LazyPollTimer = new Lazy(InitializePollTimer, LazyThreadSafetyMode.ExecutionAndPublication); State = MessageTransportConnectionState.Open; TransportReference = transport.RejectIf().IsNull(nameof(transport)).TargetArgument; } @@ -42,15 +45,27 @@ internal MessageTransportConnection(IMessageTransport transport) /// /// Closes the current as an idempotent operation. /// + /// + /// An exception was raised while closing the transport connection. + /// public void Close() { if (State == MessageTransportConnectionState.Open) { State = MessageTransportConnectionState.Closed; - if (TransportReference.Connections.Any(connection => connection.Identifier == Identifier)) + try + { + if (TransportReference.Connections.Any(connection => connection.Identifier == Identifier)) + { + TransportReference.CloseConnection(this); + } + + LazyPollTimer.Dispose(); + } + catch (Exception exception) { - TransportReference.CloseConnection(this); + throw new MessagingException("An exception was raised while closing a transport connection. See inner exception.", exception); } } } @@ -81,6 +96,7 @@ public void RegisterQueueHandler(IMessagingEntityPath queuePath, Action /// A value indicating whether or not managed resources should be released. /// + /// + /// An exception was raised while closing the transport connection. + /// protected override void Dispose(Boolean disposing) { try @@ -146,6 +166,300 @@ protected override void Dispose(Boolean disposing) } } + /// + /// Ensures (as an idempotent operation) that the current is actively polling the + /// transport. + /// + /// + /// The transport connection is in an invalid state. + /// + [DebuggerHidden] + private void BeginPolling() + { + if (PollTimer is null) + { + throw new InvalidOperationException("The transport connection is in an invalid state."); + } + } + + /// + /// Initializes a timer that is used to poll for receive operations. + /// + /// + /// A timer that is used to poll for receive operations. + /// + [DebuggerHidden] + private Timer InitializePollTimer() + { + var timerCallback = new TimerCallback((state) => Poll(state as ICollection)); + return new Timer(timerCallback, Handlers, TimeSpan.Zero, TimeSpan.FromSeconds(PollingIntervalInMilliseconds)); + } + + /// + /// Polls and performs message handling operations against received messages, if any. + /// + /// + /// A collection of actions that is performed upon message receipt from specific entities. + /// + /// + /// One or more message handling operations failed. + /// + [DebuggerHidden] + private void Poll(IEnumerable handlers) + { + if (handlers.Any()) + { + try + { + var pollQueuesTask = Task.Factory.StartNew(async () => await PollQueuesAsync(handlers.Where(handler => handler.EntityType == MessagingEntityType.Queue)).ConfigureAwait(false)); + var pollTopicsTask = Task.Factory.StartNew(async () => await PollTopicsAsync(handlers.Where(handler => handler.EntityType == MessagingEntityType.Topic)).ConfigureAwait(false)); + Task.WaitAll(pollQueuesTask, pollTopicsTask); + } + catch (AggregateException exception) + { + throw new MessagingException("One or more exceptions were raised while performing message handling operations. See inner exception(s).", exception); + } + } + } + + /// + /// Asynchronously polls the specified queue and performs messaging handling operations against received messages, if any. + /// + /// + /// A unique textual path that identifies the queue. + /// + /// + /// A collection of actions that are performed upon message receipt from the specified queue. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// An exception was raised while performing messaging handling operations. + /// + /// + /// The specified queue does not exist. + /// + /// + /// The transport connection is closed. + /// + /// + /// The object is disposed. + /// + /// + /// The operation timed out. + /// + [DebuggerHidden] + private async Task PollQueueAsync(IMessagingEntityPath queuePath, IEnumerable> handleMessageActions) + { + var messageBatch = await Transport.ReceiveFromQueueAsync(queuePath, MessageReceiptBatchSize).ConfigureAwait(false); + + if (messageBatch.Any()) + { + var handleMessageTasks = new List(); + + foreach (var message in messageBatch) + { + foreach (var handleMessageAction in handleMessageActions) + { + handleMessageTasks.Add(Task.Factory.StartNew(() => + { + handleMessageAction(message); + })); + } + } + + await Task.WhenAll(handleMessageTasks.ToArray()).ConfigureAwait(false); + } + } + + /// + /// Asynchronously polls available queues and performs message handling operations against received messages, if any. + /// + /// + /// A collection of actions that are performed upon message receipt from specific queues. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// An exception was raised while performing messaging handling operations. + /// + /// + /// One or more of the specified queues does not exist. + /// + /// + /// The transport connection is closed. + /// + /// + /// The object is disposed. + /// + /// + /// The operation timed out. + /// + [DebuggerHidden] + private async Task PollQueuesAsync(IEnumerable queueHandlers) + { + if (queueHandlers.Any()) + { + var handlerGroups = queueHandlers.GroupBy(handler => handler.Path); + var pollTasks = new List(); + + foreach (var handlerGroup in handlerGroups) + { + pollTasks.Add(Task.Factory.StartNew(async () => + { + await PollQueueAsync(handlerGroup.Key, handlerGroup.Select(handler => handler.HandleMessageAction)).ConfigureAwait(false); + })); + } + + await Task.WhenAll(pollTasks.ToArray()).ConfigureAwait(false); + } + } + + /// + /// Asynchronously polls the specified subscription and performs messaging handling operations against received messages, if + /// any. + /// + /// + /// A unique textual path that identifies the topic. + /// + /// + /// The unique name of the subscription. + /// + /// + /// A collection of actions that are performed upon message receipt from the specified subscription. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// An exception was raised while performing messaging handling operations. + /// + /// + /// The specified subscription does not exist. + /// + /// + /// The transport connection is closed. + /// + /// + /// The object is disposed. + /// + /// + /// The operation timed out. + /// + [DebuggerHidden] + private async Task PollSubscriptionAsync(IMessagingEntityPath topicPath, String subscriptionName, IEnumerable> handleMessageActions) + { + var messageBatch = await Transport.ReceiveFromTopicAsync(topicPath, subscriptionName, MessageReceiptBatchSize).ConfigureAwait(false); + + if (messageBatch.Any()) + { + var handleMessageTasks = new List(); + + foreach (var message in messageBatch) + { + foreach (var handleMessageAction in handleMessageActions) + { + handleMessageTasks.Add(Task.Factory.StartNew(() => + { + handleMessageAction(message); + })); + } + } + + await Task.WhenAll(handleMessageTasks.ToArray()).ConfigureAwait(false); + } + } + + /// + /// Asynchronously polls the specified topic and performs messaging handling operations against received messages, if any. + /// + /// + /// A unique textual path that identifies the topic. + /// + /// + /// A collection of actions that are performed upon message receipt from specific subscriptions. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// An exception was raised while performing messaging handling operations. + /// + /// + /// The specified topic does not exist. + /// + /// + /// The transport connection is closed. + /// + /// + /// The object is disposed. + /// + /// + /// The operation timed out. + /// + [DebuggerHidden] + private async Task PollTopicAsync(IMessagingEntityPath topicPath, IEnumerable subscriptionHandlers) + { + var handlerGroups = subscriptionHandlers.GroupBy(handler => handler.SubscriptionName); + var pollTasks = new List(); + + foreach (var handlerGroup in handlerGroups) + { + pollTasks.Add(Task.Factory.StartNew(async () => + { + await PollSubscriptionAsync(topicPath, handlerGroup.Key, handlerGroup.Select(handler => handler.HandleMessageAction)).ConfigureAwait(false); + })); + } + + await Task.WhenAll(pollTasks.ToArray()).ConfigureAwait(false); + } + + /// + /// Asynchronously polls available topics and performs message handling operations against received messages, if any. + /// + /// + /// A collection of actions that are performed upon message receipt from specific topics. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// An exception was raised while performing messaging handling operations. + /// + /// + /// One or more of the specified topics does not exist. + /// + /// + /// The transport connection is closed. + /// + /// + /// The object is disposed. + /// + /// + /// The operation timed out. + /// + [DebuggerHidden] + private async Task PollTopicsAsync(IEnumerable subscriptionHandlers) + { + if (subscriptionHandlers.Any()) + { + var handlerGroups = subscriptionHandlers.GroupBy(handler => handler.Path); + var pollTasks = new List(); + + foreach (var handlerGroup in handlerGroups) + { + pollTasks.Add(Task.Factory.StartNew(async () => + { + await PollTopicAsync(handlerGroup.Key, handlerGroup).ConfigureAwait(false); + })); + } + + await Task.WhenAll(pollTasks.ToArray()).ConfigureAwait(false); + } + } + /// /// Gets a value that uniquely identifies the current . /// @@ -171,12 +485,36 @@ public MessageTransportConnectionState State /// public IMessageTransport Transport => State == MessageTransportConnectionState.Open ? TransportReference : throw new MessageTransportConnectionClosedException($"Connection {Identifier.ToSerializedString()} is closed."); + /// + /// Gets a timer that is used to poll for receive operations. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private Timer PollTimer => LazyPollTimer.Value; + + /// + /// Represents the maximum number of messages to dequeue from each entity during a single polling permutation. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Int32 MessageReceiptBatchSize = 34; + + /// + /// Represents the interval, in milliseconds, at which is polled for receive operations. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Int32 PollingIntervalInMilliseconds = 233; + /// /// Represents a collection of actions that is performed upon message receipt from specific entities. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly ICollection Handlers; + /// + /// Represents a lazily-initialized timer that is used to poll for receive operations. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly Lazy LazyPollTimer; + /// /// Represents the associated . /// From 513e94d9bda8d3b4cc0c41b050f24d49483230a8 Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Sat, 21 Mar 2020 19:40:20 -0500 Subject: [PATCH 18/55] Adding in-memory messaging abstraction types. --- .../InMemoryClientFactory.cs | 246 ++++++++++++++++++ .../InMemoryListeningFacade.cs | 82 ++++++ .../InMemoryMessageAdapter.cs | 75 ++++++ .../InMemoryRequestingFacade.cs | 38 +++ .../InMemoryTransmittingFacade.cs | 61 +++++ .../TransportPrimitives/IMessageTransport.cs | 106 ++++++++ .../IMessagingEntityReceiveClient.cs | 46 ++++ .../TransportPrimitives/MessageTransport.cs | 146 +++++++++++ .../MessagingEntityClient.cs | 55 ++++ 9 files changed, 855 insertions(+) create mode 100644 src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryClientFactory.cs create mode 100644 src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryListeningFacade.cs create mode 100644 src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryMessageAdapter.cs create mode 100644 src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryRequestingFacade.cs create mode 100644 src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryTransmittingFacade.cs diff --git a/src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryClientFactory.cs b/src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryClientFactory.cs new file mode 100644 index 00000000..b3088101 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryClientFactory.cs @@ -0,0 +1,246 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Messaging.TransportPrimitives; +using System; +using System.Diagnostics; +using System.Threading.Tasks; + +namespace RapidField.SolidInstruments.Messaging.InMemory +{ + /// + /// Represents an appliance that manages in-memory messaging clients. + /// + public sealed class InMemoryClientFactory : MessagingClientFactory + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A connection that governs interaction with messaging entities. + /// + /// + /// is . + /// + public InMemoryClientFactory(IMessageTransportConnection connection) + : base(connection) + { + Transport = connection.Transport; + } + + /// + /// Creates a new implementation-specific client that facilitates receive operations. + /// + /// + /// The type of the message that the client handles. + /// + /// + /// A connection that governs interaction with messaging entities. + /// + /// + /// The type of the entity. + /// + /// + /// The unique path for the entity. + /// + /// + /// The unique name of the subscription. + /// + /// + /// A new implementation-specific client that facilitates receive operations. + /// + protected sealed override IMessagingEntityReceiveClient CreateMessageReceiver(IMessageTransportConnection connection, MessagingEntityType entityType, IMessagingEntityPath entityPath, String subscriptionName) => entityType switch + { + MessagingEntityType.Queue => CreateQueueClient(connection, entityPath), + MessagingEntityType.Topic => CreateSubscriptionClient(connection, entityPath, subscriptionName), + _ => throw new UnsupportedSpecificationException($"The specified entity type, {entityType}, is not supported.") + }; + + /// + /// Creates a new implementation-specific client that facilitates send operations. + /// + /// + /// The type of the message that the client handles. + /// + /// + /// A connection that governs interaction with messaging entities. + /// + /// + /// The type of the entity. + /// + /// + /// The unique path for the entity. + /// + /// + /// A new implementation-specific client that facilitates send operations. + /// + protected sealed override IMessagingEntitySendClient CreateMessageSender(IMessageTransportConnection connection, MessagingEntityType entityType, IMessagingEntityPath entityPath) => entityType switch + { + MessagingEntityType.Queue => CreateQueueClient(connection, entityPath), + MessagingEntityType.Topic => CreateTopicClient(connection, entityPath), + _ => throw new UnsupportedSpecificationException($"The specified entity type, {entityType}, is not supported.") + }; + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected sealed override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Creates a new for the specified message type. + /// + /// + /// The type of the message that the client handles. + /// + /// + /// A connection that governs interaction with messaging entities. + /// + /// + /// The unique path for the queue. + /// + /// + /// A new . + /// + /// + /// An exception was raised while creating the client. + /// + [DebuggerHidden] + private IMessageQueueClient CreateQueueClient(IMessageTransportConnection connection, IMessagingEntityPath queuePath) + where TMessage : class + { + EnsureQueueExistanceAsync(queuePath).Wait(); + return new MessageQueueClient(connection, queuePath); + } + + /// + /// Creates a new for the specified message type. + /// + /// + /// The type of the message that the client handles. + /// + /// + /// A connection that governs interaction with messaging entities. + /// + /// + /// A unique path for the topic. + /// + /// + /// A name for the subscription. + /// + /// + /// A new . + /// + /// + /// An exception was raised while creating the client. + /// + [DebuggerHidden] + private IMessageSubscriptionClient CreateSubscriptionClient(IMessageTransportConnection connection, IMessagingEntityPath topicPath, String subscriptionName) + where TMessage : class + { + EnsureSubscriptionExistanceAsync(topicPath, subscriptionName).Wait(); + return new MessageSubscriptionClient(connection, topicPath, subscriptionName); + } + + /// + /// Creates a new for the specified message type. + /// + /// + /// The type of the message that the client handles. + /// + /// + /// A connection that governs interaction with messaging entities. + /// + /// + /// A unique path for the topic. + /// + /// + /// A new . + /// + /// + /// An exception was raised while creating the client. + /// + [DebuggerHidden] + private IMessageTopicClient CreateTopicClient(IMessageTransportConnection connection, IMessagingEntityPath topicPath) + where TMessage : class + { + EnsureTopicExistanceAsync(topicPath).Wait(); + return new MessageTopicClient(connection, topicPath); + } + + /// + /// Asynchronously creates the specified in-memory queue if it does not exist. + /// + /// + /// The queue entity path. + /// + /// + /// A task representing the asynchronous operation. + /// + [DebuggerHidden] + private Task EnsureQueueExistanceAsync(IMessagingEntityPath queuePath) => Task.Factory.StartNew(async () => + { + if (Transport.QueueExists(queuePath)) + { + return; + } + + await Transport.CreateQueueAsync(queuePath).ConfigureAwait(false); + }); + + /// + /// Asynchronously creates the specified in-memory subscription if it does not exist. + /// + /// + /// The topic entity path for the subscription. + /// + /// + /// The name of the subscription. + /// + /// + /// A task representing the asynchronous operation. + /// + [DebuggerHidden] + private Task EnsureSubscriptionExistanceAsync(IMessagingEntityPath topicPath, String subscriptionName) => Task.Factory.StartNew(async () => + { + await EnsureTopicExistanceAsync(topicPath).ConfigureAwait(false); + + if (Transport.SubscriptionExists(topicPath, subscriptionName)) + { + return; + } + + await Transport.CreateSubscriptionAsync(topicPath, subscriptionName).ConfigureAwait(false); + }); + + /// + /// Asynchronously creates the specified in-memory topic if it does not exist. + /// + /// + /// The topic entity path. + /// + /// + /// A task representing the asynchronous operation. + /// + [DebuggerHidden] + private Task EnsureTopicExistanceAsync(IMessagingEntityPath topicPath) => Task.Factory.StartNew(async () => + { + if (Transport.TopicExists(topicPath)) + { + return; + } + + await Transport.CreateTopicAsync(topicPath).ConfigureAwait(false); + }); + + /// + /// Represents the transport for which the current creates clients. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly IMessageTransport Transport; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryListeningFacade.cs b/src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryListeningFacade.cs new file mode 100644 index 00000000..dea385ad --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryListeningFacade.cs @@ -0,0 +1,82 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Concurrency; +using RapidField.SolidInstruments.Messaging.TransportPrimitives; +using System; + +namespace RapidField.SolidInstruments.Messaging.InMemory +{ + /// + /// Facilitates listening operations for in-memory queues. + /// + public sealed class InMemoryListeningFacade : MessageListeningFacade + { + /// + /// Initializes a new instance of the class. + /// + /// + /// An implementation-specific messaging facade that is used to transmit response messages. + /// + /// + /// is . + /// + public InMemoryListeningFacade(InMemoryTransmittingFacade transmittingFacade) + : base(transmittingFacade) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Registers the specified message handler with the bus. + /// + /// + /// An action that handles a message. + /// + /// + /// An implementation-specific receive client. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + protected sealed override void RegisterMessageHandler(Action messageHandler, IMessagingEntityReceiveClient receiveClient, ConcurrencyControlToken controlToken) + { + var messageHandlerAction = new Action((message) => + { + var lockToken = (MessageLockToken)null; + + try + { + lockToken = message.LockToken; + + if (lockToken is null) + { + throw new MessageListeningException("The message cannot be processed because the lock token is invalid."); + } + else if (receiveClient.Connection.State == MessageTransportConnectionState.Closed) + { + throw new MessageListeningException("The message cannot be processed because the receive client is unavailable."); + } + + messageHandler(message); + receiveClient.ConveySuccessAsync(lockToken).Wait(); + } + catch + { + receiveClient.ConveyFailureAsync(lockToken).Wait(); + } + }); + + receiveClient.RegisterMessageHandler(messageHandlerAction); + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryMessageAdapter.cs b/src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryMessageAdapter.cs new file mode 100644 index 00000000..e6f65c43 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryMessageAdapter.cs @@ -0,0 +1,75 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Messaging.TransportPrimitives; +using RapidField.SolidInstruments.Serialization; +using System; + +namespace RapidField.SolidInstruments.Messaging.InMemory +{ + /// + /// Facilitates conversion of general-format messages to and from in-memory messages. + /// + public sealed class InMemoryMessageAdapter : MessageAdapter + { + /// + /// Initializes a new instance of the class. + /// + public InMemoryMessageAdapter() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// Gets the format that is used to serialize and deserialize messages. The default value is + /// . + /// + /// + /// is equal to . + /// + public InMemoryMessageAdapter(SerializationFormat messageSerializationFormat) + : base(messageSerializationFormat) + { + return; + } + + /// + /// Converts the specified general-format message to an implementation-specific message. + /// + /// + /// The type of the message. + /// + /// + /// A general-format message to convert. + /// + /// + /// A serializer that is used to serialize the message. + /// + /// + /// The implementation-specific message. + /// + protected sealed override PrimitiveMessage ConvertForward(TMessage message, ISerializer serializer) => new PrimitiveMessage(message, new MessageLockToken(Guid.NewGuid(), message.Identifier), MessageSerializationFormat); + + /// + /// Converts the specified implementation-specific message to a general-format message. + /// + /// + /// The type of the message. + /// + /// + /// An implementation-specific message to convert. + /// + /// + /// A serializer that is used to serialize the message. + /// + /// + /// The general-format message. + /// + protected sealed override TMessage ConvertReverse(PrimitiveMessage message, ISerializer serializer) => message.GetBody(); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryRequestingFacade.cs b/src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryRequestingFacade.cs new file mode 100644 index 00000000..efe48959 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryRequestingFacade.cs @@ -0,0 +1,38 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Messaging.TransportPrimitives; +using System; + +namespace RapidField.SolidInstruments.Messaging.InMemory +{ + /// + /// Facilitates requesting operations for in-memory. + /// + public sealed class InMemoryRequestingFacade : MessageRequestingFacade + { + /// + /// Initializes a new instance of the class. + /// + /// + /// An implementation-specific messaging facade that listens for request messages. + /// + /// + /// is . + /// + public InMemoryRequestingFacade(InMemoryListeningFacade listeningFacade) + : base(listeningFacade) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryTransmittingFacade.cs b/src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryTransmittingFacade.cs new file mode 100644 index 00000000..8febc15a --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryTransmittingFacade.cs @@ -0,0 +1,61 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Concurrency; +using RapidField.SolidInstruments.Messaging.TransportPrimitives; +using System; +using System.Threading.Tasks; + +namespace RapidField.SolidInstruments.Messaging.InMemory +{ + /// + /// Facilitates transmission operations for in-memory queues. + /// + public sealed class InMemoryTransmittingFacade : MessageTransmittingFacade + { + /// + /// Initializes a new instance of the class. + /// + /// + /// An appliance that creates manages implementation-specific messaging clients. + /// + /// + /// An appliance that facilitates implementation-specific message conversion. + /// + /// + /// is -or- is + /// . + /// + public InMemoryTransmittingFacade(IMessagingClientFactory clientFactory, IMessageAdapter messageAdapter) + : base(clientFactory, messageAdapter) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Asynchronously transmits the specified message to a bus. + /// + /// + /// The message to transmit. + /// + /// + /// An implementation-specific receive client. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + /// + /// A task representing the asynchronous operation. + /// + protected sealed override Task TransmitAsync(PrimitiveMessage message, IMessagingEntitySendClient sendClient, ConcurrencyControlToken controlToken) => sendClient.SendAsync(message); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransport.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransport.cs index df760caf..630bdca0 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransport.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransport.cs @@ -26,6 +26,112 @@ public interface IMessageTransport : IInstrument /// void CloseConnection(IMessageTransportConnection connection); + /// + /// Asynchronously notifies the specified queue that a locked message was not processed and can be made available for + /// processing by other consumers. + /// + /// + /// A lock token corresponding to a message that was not processed. + /// + /// + /// A unique textual path that identifies the queue. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is -or- is . + /// + /// + /// does not reference an existing locked message -or- does not + /// reference an existing queue. + /// + /// + /// The object is disposed. + /// + /// + /// The operation timed out. + /// + Task ConveyFailureToQueueAsync(MessageLockToken lockToken, IMessagingEntityPath path); + + /// + /// Asynchronously notifies the specified subscription that a locked message was not processed and can be made available for + /// processing by other consumers. + /// + /// + /// A lock token corresponding to a message that was not processed. + /// + /// + /// A unique textual path that identifies the subscription topic. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is -or- is . + /// + /// + /// does not reference an existing locked message -or- does not + /// reference an existing topic. + /// + /// + /// The object is disposed. + /// + /// + /// The operation timed out. + /// + Task ConveyFailureToSubscriptionAsync(MessageLockToken lockToken, IMessagingEntityPath path); + + /// + /// Asynchronously notifies the specified queue that a locked message was processed successfully and can be destroyed + /// permanently. + /// + /// + /// A lock token corresponding to a message that was processed successfully. + /// + /// + /// A unique textual path that identifies the queue. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is -or- is . + /// + /// + /// does not reference an existing locked message -or- does not + /// reference an existing queue. + /// + /// + /// The object is disposed. + /// + Task ConveySuccessToQueueAsync(MessageLockToken lockToken, IMessagingEntityPath path); + + /// + /// Asynchronously notifies the specified subscription that a locked message was processed successfully and can be destroyed + /// permanently. + /// + /// + /// A lock token corresponding to a message that was processed successfully. + /// + /// + /// A unique textual path that identifies the subscription topic. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is -or- is . + /// + /// + /// does not reference an existing locked message -or- does not + /// reference an existing topic. + /// + /// + /// The object is disposed. + /// + Task ConveySuccessToSubscriptionAsync(MessageLockToken lockToken, IMessagingEntityPath path); + /// /// Opens and returns a new to the current . /// diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessagingEntityReceiveClient.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessagingEntityReceiveClient.cs index 13307ca7..ab108b41 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessagingEntityReceiveClient.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessagingEntityReceiveClient.cs @@ -3,6 +3,7 @@ // ================================================================================================================================= using System; +using System.Threading.Tasks; namespace RapidField.SolidInstruments.Messaging.TransportPrimitives { @@ -11,6 +12,51 @@ namespace RapidField.SolidInstruments.Messaging.TransportPrimitives /// public interface IMessagingEntityReceiveClient : IMessagingEntityClient { + /// + /// Asynchronously notifies the associated that a locked message was not processed and can + /// be made available for processing by other consumers. + /// + /// + /// A lock token corresponding to a message that was not processed. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// does not reference an existing locked message. + /// + /// + /// The object is disposed. + /// + /// + /// The operation timed out. + /// + Task ConveyFailureAsync(MessageLockToken lockToken); + + /// + /// Asynchronously notifies the associated that a locked message was processed successfully + /// and can be destroyed permanently. + /// + /// + /// A lock token corresponding to a message that was processed successfully. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// does not reference an existing locked message. + /// + /// + /// The object is disposed. + /// + Task ConveySuccessAsync(MessageLockToken lockToken); + /// /// Registers the specified message handler for the associated . /// diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransport.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransport.cs index b0d4ab64..fa952cf9 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransport.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransport.cs @@ -70,6 +70,152 @@ public void CloseConnection(IMessageTransportConnection connection) } } + /// + /// Asynchronously notifies the specified queue that a locked message was not processed and can be made available for + /// processing by other consumers. + /// + /// + /// A lock token corresponding to a message that was not processed. + /// + /// + /// A unique textual path that identifies the queue. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is -or- is . + /// + /// + /// does not reference an existing locked message -or- does not + /// reference an existing queue. + /// + /// + /// The object is disposed. + /// + /// + /// The operation timed out. + /// + public Task ConveyFailureToQueueAsync(MessageLockToken lockToken, IMessagingEntityPath path) + { + RejectIfDisposed(); + + if (QueueDictionary.TryGetValue(path.RejectIf().IsNull(nameof(path)).TargetArgument, out var queue)) + { + return queue.ConveyFailureAsync(lockToken); + } + + throw new InvalidOperationException($"Failed to convey failure. The specified queue, \"{path}\", does not exist."); + } + + /// + /// Asynchronously notifies the specified subscription that a locked message was not processed and can be made available for + /// processing by other consumers. + /// + /// + /// A lock token corresponding to a message that was not processed. + /// + /// + /// A unique textual path that identifies the subscription topic. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is -or- is . + /// + /// + /// does not reference an existing locked message -or- does not + /// reference an existing topic. + /// + /// + /// The object is disposed. + /// + /// + /// The operation timed out. + /// + public Task ConveyFailureToSubscriptionAsync(MessageLockToken lockToken, IMessagingEntityPath path) + { + RejectIfDisposed(); + + if (TopicDictionary.TryGetValue(path.RejectIf().IsNull(nameof(path)).TargetArgument, out var topic)) + { + return topic.ConveyFailureAsync(lockToken); + } + + throw new InvalidOperationException($"Failed to convey failure. The specified topic, \"{path}\", does not exist."); + } + + /// + /// Asynchronously notifies the specified queue that a locked message was processed successfully and can be destroyed + /// permanently. + /// + /// + /// A lock token corresponding to a message that was processed successfully. + /// + /// + /// A unique textual path that identifies the queue. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is -or- is . + /// + /// + /// does not reference an existing locked message -or- does not + /// reference an existing queue. + /// + /// + /// The object is disposed. + /// + public Task ConveySuccessToQueueAsync(MessageLockToken lockToken, IMessagingEntityPath path) + { + RejectIfDisposed(); + + if (QueueDictionary.TryGetValue(path.RejectIf().IsNull(nameof(path)).TargetArgument, out var queue)) + { + return queue.ConveySuccessAsync(lockToken); + } + + throw new InvalidOperationException($"Failed to convey success. The specified queue, \"{path}\", does not exist."); + } + + /// + /// Asynchronously notifies the specified subscription that a locked message was processed successfully and can be destroyed + /// permanently. + /// + /// + /// A lock token corresponding to a message that was processed successfully. + /// + /// + /// A unique textual path that identifies the subscription topic. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is -or- is . + /// + /// + /// does not reference an existing locked message -or- does not + /// reference an existing topic. + /// + /// + /// The object is disposed. + /// + public Task ConveySuccessToSubscriptionAsync(MessageLockToken lockToken, IMessagingEntityPath path) + { + RejectIfDisposed(); + + if (TopicDictionary.TryGetValue(path.RejectIf().IsNull(nameof(path)).TargetArgument, out var topic)) + { + return topic.ConveySuccessAsync(lockToken); + } + + throw new InvalidOperationException($"Failed to convey failure. The specified topic, \"{path}\", does not exist."); + } + /// /// Opens and returns a new to the current . /// diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessagingEntityClient.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessagingEntityClient.cs index 135a01f9..de028c8c 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessagingEntityClient.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessagingEntityClient.cs @@ -42,6 +42,61 @@ protected MessagingEntityClient(IMessageTransportConnection connection, IMessagi Path = path.RejectIf().IsNull(nameof(path)).TargetArgument; } + /// + /// Asynchronously notifies the associated that a locked message was not processed and can + /// be made available for processing by other consumers. + /// + /// + /// A lock token corresponding to a message that was not processed. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// does not reference an existing locked message. + /// + /// + /// The object is disposed. + /// + /// + /// The operation timed out. + /// + public Task ConveyFailureAsync(MessageLockToken lockToken) => EntityType switch + { + MessagingEntityType.Queue => Connection.Transport.ConveyFailureToQueueAsync(lockToken, Path), + MessagingEntityType.Topic => Connection.Transport.ConveyFailureToSubscriptionAsync(lockToken, Path), + _ => throw new UnsupportedSpecificationException($"The specified messaging entity type, {EntityType}, is not supported.") + }; + + /// + /// Asynchronously notifies the associated that a locked message was processed successfully + /// and can be destroyed permanently. + /// + /// + /// A lock token corresponding to a message that was processed successfully. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// does not reference an existing locked message. + /// + /// + /// The object is disposed. + /// + public Task ConveySuccessAsync(MessageLockToken lockToken) => EntityType switch + { + MessagingEntityType.Queue => Connection.Transport.ConveySuccessToQueueAsync(lockToken, Path), + MessagingEntityType.Topic => Connection.Transport.ConveySuccessToSubscriptionAsync(lockToken, Path), + _ => throw new UnsupportedSpecificationException($"The specified messaging entity type, {EntityType}, is not supported.") + }; + /// /// Registers the specified message handler for the associated . /// From 96d1c5d17271ebf511efe7dbc063d8ae778e42f0 Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Sun, 22 Mar 2020 09:16:15 -0500 Subject: [PATCH 19/55] Adding tests for in-memory messaging abstractions. --- RapidField.SolidInstruments.sln | 7 ++++ .../AssemblyAttributes.cs | 1 + .../InMemoryClientFactoryTests.cs | 34 +++++++++++++++++++ .../README.md | 30 ++++++++++++++++ ...uments.Messaging.InMemory.UnitTests.csproj | 31 +++++++++++++++++ 5 files changed, 103 insertions(+) create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/InMemoryClientFactoryTests.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/README.md create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/RapidField.SolidInstruments.Messaging.InMemory.UnitTests.csproj diff --git a/RapidField.SolidInstruments.sln b/RapidField.SolidInstruments.sln index 1d90bbd7..e7e75953 100644 --- a/RapidField.SolidInstruments.sln +++ b/RapidField.SolidInstruments.sln @@ -341,6 +341,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ISSUE_TEMPLATE", "ISSUE_TEM .github\ISSUE_TEMPLATE\QUESTION.md = .github\ISSUE_TEMPLATE\QUESTION.md EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RapidField.SolidInstruments.Messaging.InMemory.UnitTests", "test\RapidField.SolidInstruments.Messaging.InMemory.UnitTests\RapidField.SolidInstruments.Messaging.InMemory.UnitTests.csproj", "{0E10878E-46FB-4512-BE1B-6A97FEDC0C58}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -511,6 +513,10 @@ Global {5DBE4E27-AA78-4276-BCF4-1CE1DD60555F}.Debug|Any CPU.Build.0 = Debug|Any CPU {5DBE4E27-AA78-4276-BCF4-1CE1DD60555F}.Release|Any CPU.ActiveCfg = Release|Any CPU {5DBE4E27-AA78-4276-BCF4-1CE1DD60555F}.Release|Any CPU.Build.0 = Release|Any CPU + {0E10878E-46FB-4512-BE1B-6A97FEDC0C58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0E10878E-46FB-4512-BE1B-6A97FEDC0C58}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0E10878E-46FB-4512-BE1B-6A97FEDC0C58}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0E10878E-46FB-4512-BE1B-6A97FEDC0C58}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -573,6 +579,7 @@ Global {0213A8C5-E32C-4DEE-B872-7E50E27F9B5A} = {265B737A-1B0A-4FAC-9A81-0EC3C19C8AFA} {1D0A68B1-B7B3-49C0-87AB-EC52A192D4A3} = {DD6DE5D2-E17E-4985-AD23-29F83799CAFC} {94B64CCC-D7FA-4781-9236-5227222368FC} = {1D0A68B1-B7B3-49C0-87AB-EC52A192D4A3} + {0E10878E-46FB-4512-BE1B-6A97FEDC0C58} = {5DAD6C24-954B-43EC-B7D9-62BC08CF7AC6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {834FCFB0-DA00-4ABD-9424-6FE1398A9F6E} diff --git a/src/RapidField.SolidInstruments.Messaging/AssemblyAttributes.cs b/src/RapidField.SolidInstruments.Messaging/AssemblyAttributes.cs index 98279c34..e91aa9a8 100644 --- a/src/RapidField.SolidInstruments.Messaging/AssemblyAttributes.cs +++ b/src/RapidField.SolidInstruments.Messaging/AssemblyAttributes.cs @@ -7,5 +7,6 @@ [assembly: DisablePrivateReflection()] [assembly: InternalsVisibleTo("RapidField.SolidInstruments.Messaging.AzureServiceBus")] [assembly: InternalsVisibleTo("RapidField.SolidInstruments.Messaging.InMemory")] +[assembly: InternalsVisibleTo("RapidField.SolidInstruments.Messaging.InMemory.UnitTests")] [assembly: InternalsVisibleTo("RapidField.SolidInstruments.Messaging.RabbitMq")] [assembly: InternalsVisibleTo("RapidField.SolidInstruments.Messaging.UnitTests")] \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/InMemoryClientFactoryTests.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/InMemoryClientFactoryTests.cs new file mode 100644 index 00000000..3fbb46be --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/InMemoryClientFactoryTests.cs @@ -0,0 +1,34 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RapidField.SolidInstruments.Messaging.TransportPrimitives; +using RapidField.SolidInstruments.Serialization; +using System; +using System.Collections.Generic; +using System.Text; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests +{ + [TestClass] + public class InMemoryClientFactoryTests + { + [TestMethod] + public void GetQueuePath_Should() + { + // Arrange. + var serializationFormat = SerializationFormat.CompressedJson; + + using (var transport = new MessageTransport(serializationFormat)) + { + using (var connection = transport.CreateConnection()) + { + using (var target = new InMemoryClientFactory(connection)) + { + } + } + } + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/README.md b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/README.md new file mode 100644 index 00000000..24cba9dd --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/README.md @@ -0,0 +1,30 @@ + + +[![Solid Instruments](../../SolidInstruments.Logo.Color.Transparent.500w.png)](../../README.md) +- - - + +# RapidField.SolidInstruments.Messaging.InMemory.UnitTests + +This document describes the purpose of the [`RapidField.SolidInstruments.Messaging.InMemory.UnitTests`]() project. + +## Purpose + +This project houses unit tests for the **Solid Instruments** [**In-Memory Messaging**](../../src/RapidField.SolidInstruments.Messaging.InMemory/README.md) APIs. + +## License + +[![License](https://img.shields.io/github/license/rapidfield/solid-instruments?style=flat&color=lightseagreen&label=license&logo=open-access&logoColor=lightgrey)](../../LICENSE.txt) + +**Solid Instruments** is [MIT-licensed](https://en.wikipedia.org/wiki/MIT_License). Review the [license terms](../../LICENSE.txt) for more information. + +
+ +- - - + +
+ +[![RapidField](../../RapidField.Logo.Color.Black.Transparent.200w.png)](https://www.rapidfield.com) + +###### Copyright (c) RapidField LLC. All rights reserved. "RapidField" and "Solid Instruments" are trademarks of RapidField LLC. \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/RapidField.SolidInstruments.Messaging.InMemory.UnitTests.csproj b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/RapidField.SolidInstruments.Messaging.InMemory.UnitTests.csproj new file mode 100644 index 00000000..e2eddfd6 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/RapidField.SolidInstruments.Messaging.InMemory.UnitTests.csproj @@ -0,0 +1,31 @@ + + + + + Solid Instruments contributors + RapidField + Copyright (c) RapidField LLC. All rights reserved. + Solid Instruments + Solid Instruments is a single-sourced, loosely-coupled collection of .NET libraries that help you rapidly fill feature gaps, overcome common and uncommon design challenges, and deliver stable, secure, high-performance software. + $(BuildVersion) + netcoreapp3.1 + latest + false + + + + + + + + + + + + + + + + \ No newline at end of file From 2c4aec224932607e1b2d0cd8d8a36f66a29dc6e7 Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Sat, 28 Mar 2020 17:22:52 -0500 Subject: [PATCH 20/55] Adding test types. --- .../Models/Customer.cs | 51 +++++++++++ .../Models/Product.cs | 84 +++++++++++++++++++ ...uments.Messaging.InMemory.UnitTests.csproj | 4 + 3 files changed, 139 insertions(+) create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Customer.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Product.cs diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Customer.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Customer.cs new file mode 100644 index 00000000..f594854f --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Customer.cs @@ -0,0 +1,51 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; +using System; +using System.Runtime.Serialization; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models +{ + /// + /// Represents a customer. + /// + [DataContract] + internal sealed class Customer : GlobalIdentityDomainModel + { + /// + /// Initializes a new instance of the class. + /// + public Customer() + : base() + { + Name = null; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A value that uniquely identifies the model. The default value is equal to the default instance of . + /// + /// + /// The name of the customer. + /// + public Customer(Guid identifier, String name) + : base(identifier) + { + Name = name; + } + + /// + /// Gets or sets the name of the current . + /// + [DataMember] + public String Name + { + get; + set; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Product.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Product.cs new file mode 100644 index 00000000..e4672a5d --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Product.cs @@ -0,0 +1,84 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; +using System; +using System.Runtime.Serialization; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models +{ + /// + /// Represents an item of merchandise. + /// + [DataContract] + internal sealed class Product : GlobalIdentityDomainModel + { + /// + /// Initializes a new instance of the class. + /// + public Product() + : base() + { + Name = null; + Price = null; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A value that uniquely identifies the model. The default value is equal to the default instance of . + /// + /// + /// The name of the product. + /// + public Product(Guid identifier, String name) + : base(identifier) + { + Name = name; + Price = null; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A value that uniquely identifies the model. The default value is equal to the default instance of . + /// + /// + /// The name of the product. + /// + /// + /// The price of the product. + /// + public Product(Guid identifier, String name, Decimal price) + : base(identifier) + { + Name = name; + Price = price; + } + + /// + /// Gets or sets the name of the current . + /// + [DataMember] + public String Name + { + get; + set; + } + + /// + /// Gets or sets the price of the current , or if the price is unknown. + /// + [DataMember] + public Decimal? Price + { + get; + set; + } + + internal static Product Widget => new Product(Guid.Parse("")) + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/RapidField.SolidInstruments.Messaging.InMemory.UnitTests.csproj b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/RapidField.SolidInstruments.Messaging.InMemory.UnitTests.csproj index e2eddfd6..96815b29 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/RapidField.SolidInstruments.Messaging.InMemory.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/RapidField.SolidInstruments.Messaging.InMemory.UnitTests.csproj @@ -28,4 +28,8 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in + + + + \ No newline at end of file From e46b565d6c135b2ffea6609b4d31652b70592cba Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Sat, 28 Mar 2020 17:24:47 -0500 Subject: [PATCH 21/55] Fixing an error. --- .../Models/Product.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Product.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Product.cs index e4672a5d..511f76bf 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Product.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Product.cs @@ -78,7 +78,5 @@ public Decimal? Price get; set; } - - internal static Product Widget => new Product(Guid.Parse("")) } } \ No newline at end of file From 5f280eb4e8287ac2333632b7075186d87a51f11b Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Tue, 7 Apr 2020 15:30:21 -0500 Subject: [PATCH 22/55] Adding test models. --- .../InMemoryClientFactoryTests.cs | 4 +- .../Models/CustomerOrder.cs | 82 +++++++++++++++++++ .../Models/Product.cs | 9 +- 3 files changed, 87 insertions(+), 8 deletions(-) create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/CustomerOrder.cs diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/InMemoryClientFactoryTests.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/InMemoryClientFactoryTests.cs index 3fbb46be..73d0a382 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/InMemoryClientFactoryTests.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/InMemoryClientFactoryTests.cs @@ -5,9 +5,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using RapidField.SolidInstruments.Messaging.TransportPrimitives; using RapidField.SolidInstruments.Serialization; -using System; -using System.Collections.Generic; -using System.Text; namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests { @@ -26,6 +23,7 @@ public void GetQueuePath_Should() { using (var target = new InMemoryClientFactory(connection)) { + // Act. } } } diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/CustomerOrder.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/CustomerOrder.cs new file mode 100644 index 00000000..c72d7810 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/CustomerOrder.cs @@ -0,0 +1,82 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models +{ + /// + /// Represents an order placed by a customer. + /// + [DataContract] + internal sealed class CustomerOrder : GlobalIdentityDomainModel + { + /// + /// Initializes a new instance of the class. + /// + public CustomerOrder() + : base() + { + Customer = null; + PlacementTimeStamp = default; + Products = new List(); + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A value that uniquely identifies the model. The default value is equal to the default instance of . + /// + /// + /// The customer that placed the order. + /// + /// + /// The date and time when the order was placed. + /// + /// + /// The constituent produces that are or were ordered by . + /// + public CustomerOrder(Guid identifier, Customer customer, DateTime placementTimeStamp, IEnumerable products) + : base(identifier) + { + Customer = customer; + PlacementTimeStamp = placementTimeStamp; + Products = new List(products ?? Array.Empty()); + } + + /// + /// Gets or sets the customer that placed the current . + /// + [DataMember] + public Customer Customer + { + get; + set; + } + + /// + /// Gets or sets the date and time when the current was placed. + /// + [DataMember] + public DateTime PlacementTimeStamp + { + get; + set; + } + + /// + /// Gets or sets the constituent products that are or were ordered by the associated . + /// + [DataMember] + public ICollection Products + { + get; + set; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Product.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Product.cs index 511f76bf..fc248216 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Product.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Product.cs @@ -34,10 +34,9 @@ public Product() /// The name of the product. /// public Product(Guid identifier, String name) - : base(identifier) + : this(identifier, name, null) { - Name = name; - Price = null; + return; } /// @@ -50,9 +49,9 @@ public Product(Guid identifier, String name) /// The name of the product. /// /// - /// The price of the product. + /// The price of the product, or if the price is unknown. /// - public Product(Guid identifier, String name, Decimal price) + public Product(Guid identifier, String name, Decimal? price) : base(identifier) { Name = name; From d91921b859c7323a169c245f11e40584fdfcb15e Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Tue, 7 Apr 2020 16:30:23 -0500 Subject: [PATCH 23/55] Housekeeping. --- CodeMaid.config | 2 +- .../PinnedBufferTests.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/CodeMaid.config b/CodeMaid.config index abc23feb..4e24f441 100644 --- a/CodeMaid.config +++ b/CodeMaid.config @@ -174,7 +174,7 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - \.Designer\.cs$||\.Designer\.vb$||\.resx$||\.min\.css$||\.min\.js$||\.ico$||\.jpg$||\.png$||\.vsspell$||\\wwwroot\\ + \.Designer\.cs$||\.Designer\.vb$||\.dll$||\.resx$||\.min\.css$||\.min\.js$||\.gitignore$||\.ico$||\.jpg$||\.png$||\.testcert$||\.vsspell$||\\wwwroot\\ diff --git a/test/RapidField.SolidInstruments.Collections.UnitTests/PinnedBufferTests.cs b/test/RapidField.SolidInstruments.Collections.UnitTests/PinnedBufferTests.cs index 5e3d83dc..fb1d2666 100644 --- a/test/RapidField.SolidInstruments.Collections.UnitTests/PinnedBufferTests.cs +++ b/test/RapidField.SolidInstruments.Collections.UnitTests/PinnedBufferTests.cs @@ -5,7 +5,6 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; -using System.Threading.Tasks; namespace RapidField.SolidInstruments.Collections.UnitTests { From 9a83c95b7e57987d5a35f46f82b18b8ee338ce19 Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Thu, 9 Apr 2020 11:54:06 -0500 Subject: [PATCH 24/55] Upgrading NuGet packages. --- ....SolidInstruments.Example.ServiceApplication.csproj | 4 ++-- ...ield.SolidInstruments.Example.WebApplication.csproj | 6 +++--- .../RapidField.SolidInstruments.Core.csproj | 2 +- ....SolidInstruments.DataAccess.EntityFramework.csproj | 6 +++--- ....SolidInstruments.InversionOfControl.Autofac.csproj | 6 +++--- ...dInstruments.InversionOfControl.DotNetNative.csproj | 6 +++--- ...pidField.SolidInstruments.InversionOfControl.csproj | 6 +++--- ...d.SolidInstruments.Messaging.AzureServiceBus.csproj | 2 +- ...apidField.SolidInstruments.ObjectComposition.csproj | 2 +- ...Field.SolidInstruments.Collections.UnitTests.csproj | 8 ++++---- ...apidField.SolidInstruments.Command.UnitTests.csproj | 10 +++++----- .../RapidField.SolidInstruments.Core.UnitTests.csproj | 10 +++++----- ...ield.SolidInstruments.Cryptography.UnitTests.csproj | 8 ++++---- ...ruments.DataAccess.EntityFramework.UnitTests.csproj | 8 ++++---- ...dField.SolidInstruments.DataAccess.UnitTests.csproj | 8 ++++---- ...ld.SolidInstruments.EventAuthoring.UnitTests.csproj | 8 ++++---- ...ruments.InversionOfControl.Autofac.UnitTests.csproj | 10 +++++----- ...ts.InversionOfControl.DotNetNative.UnitTests.csproj | 10 +++++----- ...olidInstruments.InversionOfControl.UnitTests.csproj | 10 +++++----- ...Field.SolidInstruments.Mathematics.UnitTests.csproj | 8 ++++---- ...olidInstruments.Messaging.InMemory.UnitTests.csproj | 8 ++++---- ...idField.SolidInstruments.Messaging.UnitTests.csproj | 8 ++++---- ...SolidInstruments.ObjectComposition.UnitTests.csproj | 10 +++++----- ...eld.SolidInstruments.Serialization.UnitTests.csproj | 8 ++++---- ....SolidInstruments.SignalProcessing.UnitTests.csproj | 8 ++++---- ...ield.SolidInstruments.TextEncoding.UnitTests.csproj | 8 ++++---- 26 files changed, 94 insertions(+), 94 deletions(-) diff --git a/example/RapidField.SolidInstruments.Example.ServiceApplication/RapidField.SolidInstruments.Example.ServiceApplication.csproj b/example/RapidField.SolidInstruments.Example.ServiceApplication/RapidField.SolidInstruments.Example.ServiceApplication.csproj index b1624e92..1247820f 100644 --- a/example/RapidField.SolidInstruments.Example.ServiceApplication/RapidField.SolidInstruments.Example.ServiceApplication.csproj +++ b/example/RapidField.SolidInstruments.Example.ServiceApplication/RapidField.SolidInstruments.Example.ServiceApplication.csproj @@ -37,8 +37,8 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - + + diff --git a/example/RapidField.SolidInstruments.Example.WebApplication/RapidField.SolidInstruments.Example.WebApplication.csproj b/example/RapidField.SolidInstruments.Example.WebApplication/RapidField.SolidInstruments.Example.WebApplication.csproj index 75ce1942..8d4e7c93 100644 --- a/example/RapidField.SolidInstruments.Example.WebApplication/RapidField.SolidInstruments.Example.WebApplication.csproj +++ b/example/RapidField.SolidInstruments.Example.WebApplication/RapidField.SolidInstruments.Example.WebApplication.csproj @@ -17,9 +17,9 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - + + + diff --git a/src/RapidField.SolidInstruments.Core/RapidField.SolidInstruments.Core.csproj b/src/RapidField.SolidInstruments.Core/RapidField.SolidInstruments.Core.csproj index cdf89a55..26193e29 100644 --- a/src/RapidField.SolidInstruments.Core/RapidField.SolidInstruments.Core.csproj +++ b/src/RapidField.SolidInstruments.Core/RapidField.SolidInstruments.Core.csproj @@ -38,6 +38,6 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - + \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess.EntityFramework/RapidField.SolidInstruments.DataAccess.EntityFramework.csproj b/src/RapidField.SolidInstruments.DataAccess.EntityFramework/RapidField.SolidInstruments.DataAccess.EntityFramework.csproj index 2c047455..25273cf6 100644 --- a/src/RapidField.SolidInstruments.DataAccess.EntityFramework/RapidField.SolidInstruments.DataAccess.EntityFramework.csproj +++ b/src/RapidField.SolidInstruments.DataAccess.EntityFramework/RapidField.SolidInstruments.DataAccess.EntityFramework.csproj @@ -37,9 +37,9 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - + + + diff --git a/src/RapidField.SolidInstruments.InversionOfControl.Autofac/RapidField.SolidInstruments.InversionOfControl.Autofac.csproj b/src/RapidField.SolidInstruments.InversionOfControl.Autofac/RapidField.SolidInstruments.InversionOfControl.Autofac.csproj index d42b1769..db17845b 100644 --- a/src/RapidField.SolidInstruments.InversionOfControl.Autofac/RapidField.SolidInstruments.InversionOfControl.Autofac.csproj +++ b/src/RapidField.SolidInstruments.InversionOfControl.Autofac/RapidField.SolidInstruments.InversionOfControl.Autofac.csproj @@ -37,9 +37,9 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - + + + diff --git a/src/RapidField.SolidInstruments.InversionOfControl.DotNetNative/RapidField.SolidInstruments.InversionOfControl.DotNetNative.csproj b/src/RapidField.SolidInstruments.InversionOfControl.DotNetNative/RapidField.SolidInstruments.InversionOfControl.DotNetNative.csproj index a69f084e..185ea1f6 100644 --- a/src/RapidField.SolidInstruments.InversionOfControl.DotNetNative/RapidField.SolidInstruments.InversionOfControl.DotNetNative.csproj +++ b/src/RapidField.SolidInstruments.InversionOfControl.DotNetNative/RapidField.SolidInstruments.InversionOfControl.DotNetNative.csproj @@ -37,9 +37,9 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - + + + diff --git a/src/RapidField.SolidInstruments.InversionOfControl/RapidField.SolidInstruments.InversionOfControl.csproj b/src/RapidField.SolidInstruments.InversionOfControl/RapidField.SolidInstruments.InversionOfControl.csproj index 8f46940e..022409fe 100644 --- a/src/RapidField.SolidInstruments.InversionOfControl/RapidField.SolidInstruments.InversionOfControl.csproj +++ b/src/RapidField.SolidInstruments.InversionOfControl/RapidField.SolidInstruments.InversionOfControl.csproj @@ -37,9 +37,9 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - + + + diff --git a/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/RapidField.SolidInstruments.Messaging.AzureServiceBus.csproj b/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/RapidField.SolidInstruments.Messaging.AzureServiceBus.csproj index 025b6022..9d7ae17b 100644 --- a/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/RapidField.SolidInstruments.Messaging.AzureServiceBus.csproj +++ b/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/RapidField.SolidInstruments.Messaging.AzureServiceBus.csproj @@ -37,7 +37,7 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - + diff --git a/src/RapidField.SolidInstruments.ObjectComposition/RapidField.SolidInstruments.ObjectComposition.csproj b/src/RapidField.SolidInstruments.ObjectComposition/RapidField.SolidInstruments.ObjectComposition.csproj index 55746144..1f99d6d5 100644 --- a/src/RapidField.SolidInstruments.ObjectComposition/RapidField.SolidInstruments.ObjectComposition.csproj +++ b/src/RapidField.SolidInstruments.ObjectComposition/RapidField.SolidInstruments.ObjectComposition.csproj @@ -37,7 +37,7 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - + diff --git a/test/RapidField.SolidInstruments.Collections.UnitTests/RapidField.SolidInstruments.Collections.UnitTests.csproj b/test/RapidField.SolidInstruments.Collections.UnitTests/RapidField.SolidInstruments.Collections.UnitTests.csproj index f570c1d7..00fff528 100644 --- a/test/RapidField.SolidInstruments.Collections.UnitTests/RapidField.SolidInstruments.Collections.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.Collections.UnitTests/RapidField.SolidInstruments.Collections.UnitTests.csproj @@ -18,10 +18,10 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - - + + + + diff --git a/test/RapidField.SolidInstruments.Command.UnitTests/RapidField.SolidInstruments.Command.UnitTests.csproj b/test/RapidField.SolidInstruments.Command.UnitTests/RapidField.SolidInstruments.Command.UnitTests.csproj index 1135b6c6..0181eb74 100644 --- a/test/RapidField.SolidInstruments.Command.UnitTests/RapidField.SolidInstruments.Command.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.Command.UnitTests/RapidField.SolidInstruments.Command.UnitTests.csproj @@ -18,11 +18,11 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - - - + + + + + diff --git a/test/RapidField.SolidInstruments.Core.UnitTests/RapidField.SolidInstruments.Core.UnitTests.csproj b/test/RapidField.SolidInstruments.Core.UnitTests/RapidField.SolidInstruments.Core.UnitTests.csproj index fa2f51a3..de024ef2 100644 --- a/test/RapidField.SolidInstruments.Core.UnitTests/RapidField.SolidInstruments.Core.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.Core.UnitTests/RapidField.SolidInstruments.Core.UnitTests.csproj @@ -18,11 +18,11 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - - - + + + + + diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/RapidField.SolidInstruments.Cryptography.UnitTests.csproj b/test/RapidField.SolidInstruments.Cryptography.UnitTests/RapidField.SolidInstruments.Cryptography.UnitTests.csproj index 91cc0862..f45dbe68 100644 --- a/test/RapidField.SolidInstruments.Cryptography.UnitTests/RapidField.SolidInstruments.Cryptography.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/RapidField.SolidInstruments.Cryptography.UnitTests.csproj @@ -35,10 +35,10 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - - + + + + diff --git a/test/RapidField.SolidInstruments.DataAccess.EntityFramework.UnitTests/RapidField.SolidInstruments.DataAccess.EntityFramework.UnitTests.csproj b/test/RapidField.SolidInstruments.DataAccess.EntityFramework.UnitTests/RapidField.SolidInstruments.DataAccess.EntityFramework.UnitTests.csproj index 9ea5bcfc..87f98c73 100644 --- a/test/RapidField.SolidInstruments.DataAccess.EntityFramework.UnitTests/RapidField.SolidInstruments.DataAccess.EntityFramework.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.DataAccess.EntityFramework.UnitTests/RapidField.SolidInstruments.DataAccess.EntityFramework.UnitTests.csproj @@ -18,10 +18,10 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - - + + + + diff --git a/test/RapidField.SolidInstruments.DataAccess.UnitTests/RapidField.SolidInstruments.DataAccess.UnitTests.csproj b/test/RapidField.SolidInstruments.DataAccess.UnitTests/RapidField.SolidInstruments.DataAccess.UnitTests.csproj index e177035f..b5fafba6 100644 --- a/test/RapidField.SolidInstruments.DataAccess.UnitTests/RapidField.SolidInstruments.DataAccess.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.DataAccess.UnitTests/RapidField.SolidInstruments.DataAccess.UnitTests.csproj @@ -18,10 +18,10 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - - + + + + diff --git a/test/RapidField.SolidInstruments.EventAuthoring.UnitTests/RapidField.SolidInstruments.EventAuthoring.UnitTests.csproj b/test/RapidField.SolidInstruments.EventAuthoring.UnitTests/RapidField.SolidInstruments.EventAuthoring.UnitTests.csproj index de54fc05..3c1dff2c 100644 --- a/test/RapidField.SolidInstruments.EventAuthoring.UnitTests/RapidField.SolidInstruments.EventAuthoring.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.EventAuthoring.UnitTests/RapidField.SolidInstruments.EventAuthoring.UnitTests.csproj @@ -18,10 +18,10 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - - + + + + diff --git a/test/RapidField.SolidInstruments.InversionOfControl.Autofac.UnitTests/RapidField.SolidInstruments.InversionOfControl.Autofac.UnitTests.csproj b/test/RapidField.SolidInstruments.InversionOfControl.Autofac.UnitTests/RapidField.SolidInstruments.InversionOfControl.Autofac.UnitTests.csproj index 0df385a1..a5ce77c6 100644 --- a/test/RapidField.SolidInstruments.InversionOfControl.Autofac.UnitTests/RapidField.SolidInstruments.InversionOfControl.Autofac.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.InversionOfControl.Autofac.UnitTests/RapidField.SolidInstruments.InversionOfControl.Autofac.UnitTests.csproj @@ -18,11 +18,11 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - - - + + + + + diff --git a/test/RapidField.SolidInstruments.InversionOfControl.DotNetNative.UnitTests/RapidField.SolidInstruments.InversionOfControl.DotNetNative.UnitTests.csproj b/test/RapidField.SolidInstruments.InversionOfControl.DotNetNative.UnitTests/RapidField.SolidInstruments.InversionOfControl.DotNetNative.UnitTests.csproj index 63980b68..2bfee811 100644 --- a/test/RapidField.SolidInstruments.InversionOfControl.DotNetNative.UnitTests/RapidField.SolidInstruments.InversionOfControl.DotNetNative.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.InversionOfControl.DotNetNative.UnitTests/RapidField.SolidInstruments.InversionOfControl.DotNetNative.UnitTests.csproj @@ -18,11 +18,11 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - - - + + + + + diff --git a/test/RapidField.SolidInstruments.InversionOfControl.UnitTests/RapidField.SolidInstruments.InversionOfControl.UnitTests.csproj b/test/RapidField.SolidInstruments.InversionOfControl.UnitTests/RapidField.SolidInstruments.InversionOfControl.UnitTests.csproj index 1c5f5b38..f508e5e5 100644 --- a/test/RapidField.SolidInstruments.InversionOfControl.UnitTests/RapidField.SolidInstruments.InversionOfControl.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.InversionOfControl.UnitTests/RapidField.SolidInstruments.InversionOfControl.UnitTests.csproj @@ -18,11 +18,11 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - - - + + + + + diff --git a/test/RapidField.SolidInstruments.Mathematics.UnitTests/RapidField.SolidInstruments.Mathematics.UnitTests.csproj b/test/RapidField.SolidInstruments.Mathematics.UnitTests/RapidField.SolidInstruments.Mathematics.UnitTests.csproj index 802b1e85..63cfafa4 100644 --- a/test/RapidField.SolidInstruments.Mathematics.UnitTests/RapidField.SolidInstruments.Mathematics.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.Mathematics.UnitTests/RapidField.SolidInstruments.Mathematics.UnitTests.csproj @@ -22,10 +22,10 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - - + + + + diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/RapidField.SolidInstruments.Messaging.InMemory.UnitTests.csproj b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/RapidField.SolidInstruments.Messaging.InMemory.UnitTests.csproj index 96815b29..00af06d6 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/RapidField.SolidInstruments.Messaging.InMemory.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/RapidField.SolidInstruments.Messaging.InMemory.UnitTests.csproj @@ -18,10 +18,10 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - - + + + + diff --git a/test/RapidField.SolidInstruments.Messaging.UnitTests/RapidField.SolidInstruments.Messaging.UnitTests.csproj b/test/RapidField.SolidInstruments.Messaging.UnitTests/RapidField.SolidInstruments.Messaging.UnitTests.csproj index 857a0199..40edfa06 100644 --- a/test/RapidField.SolidInstruments.Messaging.UnitTests/RapidField.SolidInstruments.Messaging.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.Messaging.UnitTests/RapidField.SolidInstruments.Messaging.UnitTests.csproj @@ -18,10 +18,10 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - - + + + + diff --git a/test/RapidField.SolidInstruments.ObjectComposition.UnitTests/RapidField.SolidInstruments.ObjectComposition.UnitTests.csproj b/test/RapidField.SolidInstruments.ObjectComposition.UnitTests/RapidField.SolidInstruments.ObjectComposition.UnitTests.csproj index d568a42e..c8c80856 100644 --- a/test/RapidField.SolidInstruments.ObjectComposition.UnitTests/RapidField.SolidInstruments.ObjectComposition.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.ObjectComposition.UnitTests/RapidField.SolidInstruments.ObjectComposition.UnitTests.csproj @@ -18,11 +18,11 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - - - + + + + + diff --git a/test/RapidField.SolidInstruments.Serialization.UnitTests/RapidField.SolidInstruments.Serialization.UnitTests.csproj b/test/RapidField.SolidInstruments.Serialization.UnitTests/RapidField.SolidInstruments.Serialization.UnitTests.csproj index 6dda0a0d..7172a240 100644 --- a/test/RapidField.SolidInstruments.Serialization.UnitTests/RapidField.SolidInstruments.Serialization.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.Serialization.UnitTests/RapidField.SolidInstruments.Serialization.UnitTests.csproj @@ -18,10 +18,10 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - - + + + + diff --git a/test/RapidField.SolidInstruments.SignalProcessing.UnitTests/RapidField.SolidInstruments.SignalProcessing.UnitTests.csproj b/test/RapidField.SolidInstruments.SignalProcessing.UnitTests/RapidField.SolidInstruments.SignalProcessing.UnitTests.csproj index 3bf873a8..59e575f5 100644 --- a/test/RapidField.SolidInstruments.SignalProcessing.UnitTests/RapidField.SolidInstruments.SignalProcessing.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.SignalProcessing.UnitTests/RapidField.SolidInstruments.SignalProcessing.UnitTests.csproj @@ -18,10 +18,10 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - - + + + + diff --git a/test/RapidField.SolidInstruments.TextEncoding.UnitTests/RapidField.SolidInstruments.TextEncoding.UnitTests.csproj b/test/RapidField.SolidInstruments.TextEncoding.UnitTests/RapidField.SolidInstruments.TextEncoding.UnitTests.csproj index 3c1c9273..53904f34 100644 --- a/test/RapidField.SolidInstruments.TextEncoding.UnitTests/RapidField.SolidInstruments.TextEncoding.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.TextEncoding.UnitTests/RapidField.SolidInstruments.TextEncoding.UnitTests.csproj @@ -18,10 +18,10 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - - + + + + From 918a42a40b7d6d2440bf01f8ae88f856359efd0d Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Sat, 11 Apr 2020 09:52:14 -0500 Subject: [PATCH 25/55] Modifying interfaces to conform to new C# convention. --- .../Models/INumber.cs | 4 +- .../Models/INumberSeries.cs | 4 +- .../Models/INumberSeriesNumber.cs | 6 +- .../ICalculatedSequence.cs | 8 +- .../ICircularBuffer.cs | 12 +- .../IInfiniteSequence.cs | 4 +- .../IPinnedBuffer.cs | 6 +- .../IReadOnlyPinnedBuffer.cs | 10 +- .../ITreeNode.cs | 16 +- .../ICommandBase.cs | 2 +- .../ICommandHandler.cs | 2 +- .../ICommandMediator.cs | 4 +- .../Concurrency/IConcurrencyControl.cs | 6 +- .../IInstrument.cs | 2 +- .../IModel.cs | 2 +- .../IObjectBuilder.cs | 2 +- .../IReferenceManager.cs | 4 +- .../ISemanticVersion.cs | 22 +- .../Hashing/IHashTree.cs | 8 +- .../Hashing/IHashingProcessor.cs | 8 +- .../ISecureBuffer.cs | 4 +- .../Secrets/IReadOnlySecret.cs | 8 +- .../Secrets/ISecret.cs | 2 +- .../Secrets/ISecretVault.cs | 36 +-- .../Symmetric/ICascadingSymmetricKey.cs | 6 +- .../Symmetric/ISymmetricKey.cs | 6 +- .../Symmetric/ISymmetricProcessor.cs | 12 +- .../IDataAccessRepository.cs | 34 +-- .../IDataAccessTransaction.cs | 14 +- .../DomainModelEvent.cs | 158 ++++++++++ .../DomainModelEventClassification.cs | 47 +++ .../IApplicationStateEvent.cs | 2 +- .../IDomainModelAssociatedEvent.cs | 16 + .../IDomainModelCreatedEvent.cs | 16 + .../IDomainModelDeletedEvent.cs | 16 + .../IDomainModelEvent.cs | 32 ++ ...tyEvent.cs => IDomainModelUpdatedEvent.cs} | 7 +- .../IErrorEvent.cs | 4 +- .../IEvent.cs | 10 +- .../IExceptionRaisedEvent.cs | 2 +- .../ILabeledEvent.cs | 2 +- .../IMetadataEnrichedEvent.cs | 2 +- .../IReportable.cs | 2 +- .../ISecurityEvent.cs | 2 +- .../ISystemStateEvent.cs | 2 +- .../ITransactionEvent.cs | 2 +- .../IUserActionEvent.cs | 2 +- .../IDependencyContainer.cs | 4 +- .../IDependencyEngine.cs | 4 +- .../IDependencyModule.cs | 2 +- .../IDependencyPackage.cs | 6 +- .../IDependencyScope.cs | 6 +- .../IServiceInjector.cs | 2 +- .../Data/ILineSeries.cs | 4 +- .../Data/IScalarLineSeries.cs | 2 +- .../Data/ITwoDimensionalDataSet.cs | 4 +- .../Sequences/IArithmeticSequence.cs | 2 +- .../Sequences/IGeometricSequence.cs | 2 +- .../IMessageAdapter.cs | 8 +- .../IMessageBase.cs | 6 +- .../IMessageHandler.cs | 4 +- .../IMessageListener.cs | 2 +- .../IMessageListeningFacade.cs | 12 +- .../IMessageRequestingFacade.cs | 2 +- .../IMessageTransmitter.cs | 2 +- .../IMessageTransmittingFacade.cs | 8 +- .../IMessagingClientFactory.cs | 20 +- .../IMessagingEntityPath.cs | 10 +- .../IMessagingFacade.cs | 2 +- .../IResponseMessage.cs | 2 +- .../Service/IHeartbeatScheduleItem.cs | 10 +- .../Service/IMessageListeningProfile.cs | 8 +- .../TransportPrimitives/IMessageQueue.cs | 2 +- .../IMessageSubscriptionClient.cs | 2 +- .../TransportPrimitives/IMessageTopic.cs | 14 +- .../TransportPrimitives/IMessageTransport.cs | 80 ++--- .../IMessageTransportConnection.cs | 12 +- .../TransportPrimitives/IMessagingEntity.cs | 34 +-- .../IMessagingEntityClient.cs | 6 +- .../IMessagingEntityReceiveClient.cs | 6 +- .../IMessagingEntitySendClient.cs | 2 +- .../IFactoryProducedInstanceGroup.cs | 2 +- .../IObjectContainer.cs | 6 +- .../IObjectFactory.cs | 8 +- .../ISerializer.cs | 6 +- .../IServiceExecutionLifetime.cs | 6 +- .../IServiceExecutor.cs | 4 +- .../IChannel.cs | 28 +- .../IChannelCollection.cs | 274 +++++++++--------- .../IDiscreteUnitOfOutput.cs | 4 +- .../ISignalProcessor.cs | 4 +- .../DuplexSemaphoreControlTests.cs | 2 +- .../ProcessorCountSemaphoreControlTests.cs | 2 +- .../SingleThreadLockControlTests.cs | 2 +- .../SingleThreadSpinLockControlTests.cs | 2 +- .../Concurrency/UnconstrainedControlTests.cs | 2 +- .../Models/CustomerOrder.cs | 34 +++ 97 files changed, 789 insertions(+), 467 deletions(-) create mode 100644 src/RapidField.SolidInstruments.EventAuthoring/DomainModelEvent.cs create mode 100644 src/RapidField.SolidInstruments.EventAuthoring/DomainModelEventClassification.cs create mode 100644 src/RapidField.SolidInstruments.EventAuthoring/IDomainModelAssociatedEvent.cs create mode 100644 src/RapidField.SolidInstruments.EventAuthoring/IDomainModelCreatedEvent.cs create mode 100644 src/RapidField.SolidInstruments.EventAuthoring/IDomainModelDeletedEvent.cs create mode 100644 src/RapidField.SolidInstruments.EventAuthoring/IDomainModelEvent.cs rename src/RapidField.SolidInstruments.EventAuthoring/{IDomainEntityEvent.cs => IDomainModelUpdatedEvent.cs} (65%) diff --git a/example/RapidField.SolidInstruments.Example.Contracts/Models/INumber.cs b/example/RapidField.SolidInstruments.Example.Contracts/Models/INumber.cs index c41ebbb5..1426dbff 100644 --- a/example/RapidField.SolidInstruments.Example.Contracts/Models/INumber.cs +++ b/example/RapidField.SolidInstruments.Example.Contracts/Models/INumber.cs @@ -14,7 +14,7 @@ public interface INumber /// /// Gets a unique identifier for the entity. /// - Guid Identifier + public Guid Identifier { get; } @@ -22,7 +22,7 @@ Guid Identifier /// /// Gets the value of the number. /// - Int64 Value + public Int64 Value { get; } diff --git a/example/RapidField.SolidInstruments.Example.Contracts/Models/INumberSeries.cs b/example/RapidField.SolidInstruments.Example.Contracts/Models/INumberSeries.cs index e6b6a5c1..b17e94b9 100644 --- a/example/RapidField.SolidInstruments.Example.Contracts/Models/INumberSeries.cs +++ b/example/RapidField.SolidInstruments.Example.Contracts/Models/INumberSeries.cs @@ -14,7 +14,7 @@ public interface INumberSeries /// /// Gets a unique identifier for the entity. /// - Guid Identifier + public Guid Identifier { get; } @@ -22,7 +22,7 @@ Guid Identifier /// /// Gets the name of the series. /// - String Name + public String Name { get; } diff --git a/example/RapidField.SolidInstruments.Example.Contracts/Models/INumberSeriesNumber.cs b/example/RapidField.SolidInstruments.Example.Contracts/Models/INumberSeriesNumber.cs index 12516888..eb290249 100644 --- a/example/RapidField.SolidInstruments.Example.Contracts/Models/INumberSeriesNumber.cs +++ b/example/RapidField.SolidInstruments.Example.Contracts/Models/INumberSeriesNumber.cs @@ -14,7 +14,7 @@ public interface INumberSeriesNumber /// /// Gets a unique identifier for the entity. /// - Guid Identifier + public Guid Identifier { get; } @@ -22,7 +22,7 @@ Guid Identifier /// /// Gets a unique identifier for the associated number. /// - Guid NumberIdentifier + public Guid NumberIdentifier { get; } @@ -30,7 +30,7 @@ Guid NumberIdentifier /// /// Gets a unique identifier for the associated number series. /// - Guid NumberSeriesIdentifier + public Guid NumberSeriesIdentifier { get; } diff --git a/src/RapidField.SolidInstruments.Collections/ICalculatedSequence.cs b/src/RapidField.SolidInstruments.Collections/ICalculatedSequence.cs index d85a99d9..48e4cd77 100644 --- a/src/RapidField.SolidInstruments.Collections/ICalculatedSequence.cs +++ b/src/RapidField.SolidInstruments.Collections/ICalculatedSequence.cs @@ -20,7 +20,7 @@ public interface ICalculatedSequence : ICalculatedSequence /// /// The next term in the sequence. /// - T CalculateNext(); + public T CalculateNext(); /// /// Calculates the next terms in the sequence. @@ -31,7 +31,7 @@ public interface ICalculatedSequence : ICalculatedSequence /// /// An array containing the calculated terms. /// - T[] CalculateNext(Int32 count); + public T[] CalculateNext(Int32 count); /// /// Calculates the specified range of terms and returns them as an array. @@ -45,7 +45,7 @@ public interface ICalculatedSequence : ICalculatedSequence /// /// An array containing the calculated terms in the specified range. /// - T[] ToArray(Int32 startIndex, Int32 count); + public T[] ToArray(Int32 startIndex, Int32 count); } /// @@ -56,7 +56,7 @@ public interface ICalculatedSequence /// /// Gets the number of terms that have been calculated. /// - Int32 CalculatedTermCount + public Int32 CalculatedTermCount { get; } diff --git a/src/RapidField.SolidInstruments.Collections/ICircularBuffer.cs b/src/RapidField.SolidInstruments.Collections/ICircularBuffer.cs index 6e3a3e01..aa478535 100644 --- a/src/RapidField.SolidInstruments.Collections/ICircularBuffer.cs +++ b/src/RapidField.SolidInstruments.Collections/ICircularBuffer.cs @@ -28,7 +28,7 @@ public interface ICircularBuffer : ICircularBuffer, IEnumerable /// /// The specified index is out of range. /// - T this[Int32 index] + public T this[Int32 index] { get; } @@ -42,7 +42,7 @@ T this[Int32 index] /// /// The buffer is empty. /// - T Read(); + public T Read(); /// /// Writes an element at the tail of the current . @@ -50,7 +50,7 @@ T this[Int32 index] /// /// The element to write to the buffer. /// - void Write(T element); + public void Write(T element); /// /// Writes an element at the tail of the current . @@ -66,7 +66,7 @@ T this[Int32 index] /// /// is and the write operation would have caused overwrite. /// - void Write(T element, Boolean permitOverwrite); + public void Write(T element, Boolean permitOverwrite); } /// @@ -77,7 +77,7 @@ public interface ICircularBuffer : IAsyncDisposable, IDisposable, IEnumerable /// /// Gets the maximum number of elements that the current can accommodate. /// - Int32 Capacity + public Int32 Capacity { get; } @@ -85,7 +85,7 @@ Int32 Capacity /// /// Gets the number of elements contained by the current . /// - Int32 Length + public Int32 Length { get; } diff --git a/src/RapidField.SolidInstruments.Collections/IInfiniteSequence.cs b/src/RapidField.SolidInstruments.Collections/IInfiniteSequence.cs index 32930b3b..afa86c0e 100644 --- a/src/RapidField.SolidInstruments.Collections/IInfiniteSequence.cs +++ b/src/RapidField.SolidInstruments.Collections/IInfiniteSequence.cs @@ -26,7 +26,7 @@ public interface IInfiniteSequence : ICalculatedSequence, IInfiniteSequenc /// /// is less than zero. /// - T this[Int32 index] + public T this[Int32 index] { get; } @@ -40,6 +40,6 @@ public interface IInfiniteSequence : ICalculatedSequence /// /// Clears the terms in the current , leaving in place the seed terms. /// - void Reset(); + public void Reset(); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Collections/IPinnedBuffer.cs b/src/RapidField.SolidInstruments.Collections/IPinnedBuffer.cs index 9392ffc4..a0883c75 100644 --- a/src/RapidField.SolidInstruments.Collections/IPinnedBuffer.cs +++ b/src/RapidField.SolidInstruments.Collections/IPinnedBuffer.cs @@ -28,7 +28,7 @@ public interface IPinnedBuffer : IEnumerable, IPinnedBuffer, IReadOnlyPinn /// /// The specified index is out of range. /// - new T this[Int32 index] + public new T this[Int32 index] { get; set; @@ -40,7 +40,7 @@ public interface IPinnedBuffer : IEnumerable, IPinnedBuffer, IReadOnlyPinn /// /// The object is disposed. /// - Span Span + public Span Span { get; } @@ -57,6 +57,6 @@ public interface IPinnedBuffer : IReadOnlyPinnedBuffer /// /// The object is disposed. /// - void OverwriteWithZeros(); + public void OverwriteWithZeros(); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Collections/IReadOnlyPinnedBuffer.cs b/src/RapidField.SolidInstruments.Collections/IReadOnlyPinnedBuffer.cs index f70c0471..70c3ac3e 100644 --- a/src/RapidField.SolidInstruments.Collections/IReadOnlyPinnedBuffer.cs +++ b/src/RapidField.SolidInstruments.Collections/IReadOnlyPinnedBuffer.cs @@ -28,7 +28,7 @@ public interface IReadOnlyPinnedBuffer : IEnumerable, IReadOnlyPinnedBuffe /// /// The specified index is out of range. /// - T this[Int32 index] + public T this[Int32 index] { get; } @@ -39,7 +39,7 @@ T this[Int32 index] /// /// The object is disposed. /// - ReadOnlySpan ReadOnlySpan + public ReadOnlySpan ReadOnlySpan { get; } @@ -53,7 +53,7 @@ public interface IReadOnlyPinnedBuffer : IAsyncDisposable, IDisposable /// /// Gets a value indicating whether or not the buffer is empty. /// - Boolean IsEmpty + public Boolean IsEmpty { get; } @@ -61,7 +61,7 @@ Boolean IsEmpty /// /// Gets the number of elements comprising the buffer. /// - Int32 Length + public Int32 Length { get; } @@ -69,7 +69,7 @@ Int32 Length /// /// Gets the length of the buffer, in bytes. /// - Int32 LengthInBytes + public Int32 LengthInBytes { get; } diff --git a/src/RapidField.SolidInstruments.Collections/ITreeNode.cs b/src/RapidField.SolidInstruments.Collections/ITreeNode.cs index 116fddc6..d6ef4713 100644 --- a/src/RapidField.SolidInstruments.Collections/ITreeNode.cs +++ b/src/RapidField.SolidInstruments.Collections/ITreeNode.cs @@ -27,7 +27,7 @@ public interface ITreeNode : ITreeNode /// /// The state of the node or the associated tree was changed during the destroy operation. /// - void Destroy(); + public void Destroy(); } /// @@ -42,7 +42,7 @@ public interface ITreeNode : IEnumerable>, ITreeNode /// Gets the child elements of the node, or an empty collection if the current is a terminal /// node. /// - IEnumerable> Children + public IEnumerable> Children { get; } @@ -53,7 +53,7 @@ IEnumerable> Children /// /// When true, is an empty collection. /// - Boolean IsLeaf + public Boolean IsLeaf { get; } @@ -64,7 +64,7 @@ Boolean IsLeaf /// /// When true, is a null reference. /// - Boolean IsRoot + public Boolean IsRoot { get; } @@ -73,7 +73,7 @@ Boolean IsRoot /// Gets the parent element of the node, or if the current is a root /// node. /// - ITreeNode Parent + public ITreeNode Parent { get; } @@ -81,7 +81,7 @@ ITreeNode Parent /// /// Gets or sets the value of the current node. /// - T Value + public T Value { get; set; @@ -96,7 +96,7 @@ public interface ITreeNode : IEnumerable /// /// Gets the number of connections from the tree's root node to the current . /// - Int32 Depth + public Int32 Depth { get; } @@ -104,7 +104,7 @@ Int32 Depth /// /// Gets the number of connections on the longest path between the current and a leaf node. /// - Int32 Height + public Int32 Height { get; } diff --git a/src/RapidField.SolidInstruments.Command/ICommandBase.cs b/src/RapidField.SolidInstruments.Command/ICommandBase.cs index 5ba0734b..c33750fb 100644 --- a/src/RapidField.SolidInstruments.Command/ICommandBase.cs +++ b/src/RapidField.SolidInstruments.Command/ICommandBase.cs @@ -14,7 +14,7 @@ public interface ICommandBase /// /// Gets the type of the result that is emitted when processing the command. /// - Type ResultType + public Type ResultType { get; } diff --git a/src/RapidField.SolidInstruments.Command/ICommandHandler.cs b/src/RapidField.SolidInstruments.Command/ICommandHandler.cs index 187e8449..a14868c1 100644 --- a/src/RapidField.SolidInstruments.Command/ICommandHandler.cs +++ b/src/RapidField.SolidInstruments.Command/ICommandHandler.cs @@ -38,7 +38,7 @@ public interface ICommandHandler : ICommandHandler /// The object is disposed. /// - TResult Process(TCommand command); + public TResult Process(TCommand command); } /// diff --git a/src/RapidField.SolidInstruments.Command/ICommandMediator.cs b/src/RapidField.SolidInstruments.Command/ICommandMediator.cs index ae72b914..5fdc5026 100644 --- a/src/RapidField.SolidInstruments.Command/ICommandMediator.cs +++ b/src/RapidField.SolidInstruments.Command/ICommandMediator.cs @@ -34,7 +34,7 @@ public interface ICommandMediator : IInstrument /// /// The object is disposed. /// - TResult Process(ICommand command); + public TResult Process(ICommand command); /// /// Asynchronously processes the specified . @@ -57,6 +57,6 @@ public interface ICommandMediator : IInstrument /// /// The object is disposed. /// - Task ProcessAsync(ICommand command); + public Task ProcessAsync(ICommand command); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Core/Concurrency/IConcurrencyControl.cs b/src/RapidField.SolidInstruments.Core/Concurrency/IConcurrencyControl.cs index feb32376..1d988264 100644 --- a/src/RapidField.SolidInstruments.Core/Concurrency/IConcurrencyControl.cs +++ b/src/RapidField.SolidInstruments.Core/Concurrency/IConcurrencyControl.cs @@ -20,7 +20,7 @@ public interface IConcurrencyControl : IAsyncDisposable, IDisposable /// /// The object is disposed. /// - ConcurrencyControlToken Enter(); + public ConcurrencyControlToken Enter(); /// /// Informs the control that a thread is exiting a block of code or has finished consuming a resource. @@ -35,12 +35,12 @@ public interface IConcurrencyControl : IAsyncDisposable, IDisposable /// /// The object is disposed. /// - void Exit(ConcurrencyControlToken token); + public void Exit(ConcurrencyControlToken token); /// /// Gets the consumption state of the current . /// - ConcurrencyControlConsumptionState ConsumptionState + public ConcurrencyControlConsumptionState ConsumptionState { get; } diff --git a/src/RapidField.SolidInstruments.Core/IInstrument.cs b/src/RapidField.SolidInstruments.Core/IInstrument.cs index 3f931938..4c1a4222 100644 --- a/src/RapidField.SolidInstruments.Core/IInstrument.cs +++ b/src/RapidField.SolidInstruments.Core/IInstrument.cs @@ -19,7 +19,7 @@ public interface IInstrument : IAsyncDisposable, IDisposable /// Interrogate this property to determine if the instrument is immediately available to perform an operation that reserves /// state control. This is useful for cases in which another resource may be utilized to perform the same operation. /// - Boolean IsBusy + public Boolean IsBusy { get; } diff --git a/src/RapidField.SolidInstruments.Core/IModel.cs b/src/RapidField.SolidInstruments.Core/IModel.cs index 9b2ce3b8..42cbd15a 100644 --- a/src/RapidField.SolidInstruments.Core/IModel.cs +++ b/src/RapidField.SolidInstruments.Core/IModel.cs @@ -18,7 +18,7 @@ public interface IModel : IComparable>, IModel /// /// Gets or sets a value that uniquely identifies the current . /// - TIdentifier Identifier + public TIdentifier Identifier { get; } diff --git a/src/RapidField.SolidInstruments.Core/IObjectBuilder.cs b/src/RapidField.SolidInstruments.Core/IObjectBuilder.cs index b4e4ee30..e8f8004e 100644 --- a/src/RapidField.SolidInstruments.Core/IObjectBuilder.cs +++ b/src/RapidField.SolidInstruments.Core/IObjectBuilder.cs @@ -24,6 +24,6 @@ public interface IObjectBuilder : IAsyncDisposable, IDisposable /// /// An exception was raised during finalization of the builder. /// - TResult ToResult(); + public TResult ToResult(); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Core/IReferenceManager.cs b/src/RapidField.SolidInstruments.Core/IReferenceManager.cs index be131708..d7ae2e66 100644 --- a/src/RapidField.SolidInstruments.Core/IReferenceManager.cs +++ b/src/RapidField.SolidInstruments.Core/IReferenceManager.cs @@ -23,13 +23,13 @@ public interface IReferenceManager : IAsyncDisposable, IDisposable /// /// The object is disposed. /// - void AddObject(T reference) + public void AddObject(T reference) where T : class; /// /// Gets the number of objects that are managed by the current . /// - Int32 ObjectCount + public Int32 ObjectCount { get; } diff --git a/src/RapidField.SolidInstruments.Core/ISemanticVersion.cs b/src/RapidField.SolidInstruments.Core/ISemanticVersion.cs index 1ef6eed4..b1798591 100644 --- a/src/RapidField.SolidInstruments.Core/ISemanticVersion.cs +++ b/src/RapidField.SolidInstruments.Core/ISemanticVersion.cs @@ -14,7 +14,7 @@ public interface ISemanticVersion : ICloneable, IComparable, I /// /// Gets the build metadata, if there is no metadata. /// - String BuildMetadata + public String BuildMetadata { get; } @@ -22,7 +22,7 @@ String BuildMetadata /// /// Gets a value indicating whether or not the current includes build metadata. /// - Boolean HasBuildMetadata + public Boolean HasBuildMetadata { get; } @@ -31,7 +31,7 @@ Boolean HasBuildMetadata /// Gets a value indicating whether or not the current represents a new major version (eg. /// x.0.0). /// - Boolean IsMajor + public Boolean IsMajor { get; } @@ -40,7 +40,7 @@ Boolean IsMajor /// Gets a value indicating whether or not the current represents a new minor version (eg. /// x.x.0). /// - Boolean IsMinor + public Boolean IsMinor { get; } @@ -49,7 +49,7 @@ Boolean IsMinor /// Gets a value indicating whether or not the current represents a patch version ( /// is greater than zero). /// - Boolean IsPatch + public Boolean IsPatch { get; } @@ -57,7 +57,7 @@ Boolean IsPatch /// /// Gets a value indicating whether or not the current represents a pre-release version. /// - Boolean IsPreRelease + public Boolean IsPreRelease { get; } @@ -65,7 +65,7 @@ Boolean IsPreRelease /// /// Gets a value indicating whether or not the current represents a stable version. /// - Boolean IsStable + public Boolean IsStable { get; } @@ -73,7 +73,7 @@ Boolean IsStable /// /// Gets the major version number, which is incremented for compatibility-breaking feature changes. /// - UInt64 MajorVersion + public UInt64 MajorVersion { get; } @@ -81,7 +81,7 @@ UInt64 MajorVersion /// /// Gets the minor version number, which is incremented for compatibility-retaining feature changes. /// - UInt64 MinorVersion + public UInt64 MinorVersion { get; } @@ -89,7 +89,7 @@ UInt64 MinorVersion /// /// Gets the patch version number, which is incremented for compatibility-retaining bug fix changes. /// - UInt64 PatchVersion + public UInt64 PatchVersion { get; } @@ -97,7 +97,7 @@ UInt64 PatchVersion /// /// Gets the pre-release label, or if there is no label. /// - String PreReleaseLabel + public String PreReleaseLabel { get; } diff --git a/src/RapidField.SolidInstruments.Cryptography/Hashing/IHashTree.cs b/src/RapidField.SolidInstruments.Cryptography/Hashing/IHashTree.cs index 9a2fdbd1..3d6dc3eb 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Hashing/IHashTree.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Hashing/IHashTree.cs @@ -31,7 +31,7 @@ public interface IHashTree : IHashTree /// /// An exception was raised during hashing or serialization. /// - void AddBlock(TBlock block); + public void AddBlock(TBlock block); /// /// Calculates hash values for the specified ordered data block collection and adds them to the tree. @@ -45,7 +45,7 @@ public interface IHashTree : IHashTree /// /// An exception was raised during hashing or serialization. /// - void AddBlockRange(IEnumerable blocks); + public void AddBlockRange(IEnumerable blocks); } /// @@ -57,7 +57,7 @@ public interface IHashTree : IAsyncDisposable, IDisposable /// /// Gets the number of leaf nodes in the current . /// - Int32 LeafCount + public Int32 LeafCount { get; } @@ -65,7 +65,7 @@ Int32 LeafCount /// /// Gets the root node for the current . /// - ITreeNode RootNode + public ITreeNode RootNode { get; } diff --git a/src/RapidField.SolidInstruments.Cryptography/Hashing/IHashingProcessor.cs b/src/RapidField.SolidInstruments.Cryptography/Hashing/IHashingProcessor.cs index 71278068..c65b3b75 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Hashing/IHashingProcessor.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Hashing/IHashingProcessor.cs @@ -33,7 +33,7 @@ public interface IHashingProcessor : IHashingProcessor /// /// An exception was raised during hashing or serialization. /// - Byte[] CalculateHash(T plaintextObject, HashingAlgorithmSpecification algorithm, SaltingMode saltingMode); + public Byte[] CalculateHash(T plaintextObject, HashingAlgorithmSpecification algorithm, SaltingMode saltingMode); /// /// Calculates a hash value for the specified plaintext object and compares the result with the specified hash value. @@ -57,7 +57,7 @@ public interface IHashingProcessor : IHashingProcessor /// /// An exception was raised during hashing or serialization. /// - Boolean EvaluateHash(Byte[] hash, T plaintextObject, HashingAlgorithmSpecification algorithm, SaltingMode saltingMode); + public Boolean EvaluateHash(Byte[] hash, T plaintextObject, HashingAlgorithmSpecification algorithm, SaltingMode saltingMode); } /// @@ -80,7 +80,7 @@ public interface IHashingProcessor /// /// An exception was raised during hashing or serialization. /// - Byte[] CalculateHash(Byte[] plaintextBinaryArray, HashingAlgorithmSpecification algorithm); + public Byte[] CalculateHash(Byte[] plaintextBinaryArray, HashingAlgorithmSpecification algorithm); /// /// Calculates a hash value for the specified plaintext binary array. @@ -101,6 +101,6 @@ public interface IHashingProcessor /// /// An exception was raised during hashing or serialization. /// - Byte[] CalculateHash(Byte[] plaintextBinaryArray, HashingAlgorithmSpecification algorithm, Byte[] salt); + public Byte[] CalculateHash(Byte[] plaintextBinaryArray, HashingAlgorithmSpecification algorithm, Byte[] salt); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/ISecureBuffer.cs b/src/RapidField.SolidInstruments.Cryptography/ISecureBuffer.cs index 4118811c..9f5e55b0 100644 --- a/src/RapidField.SolidInstruments.Cryptography/ISecureBuffer.cs +++ b/src/RapidField.SolidInstruments.Cryptography/ISecureBuffer.cs @@ -25,12 +25,12 @@ public interface ISecureBuffer : IAsyncDisposable, IDisposable /// /// The object is disposed. /// - void Access(Action action); + public void Access(Action action); /// /// Gets the length of the buffer, in bytes. /// - Int32 LengthInBytes + public Int32 LengthInBytes { get; } diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/IReadOnlySecret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/IReadOnlySecret.cs index 2d4dc4f8..bb7ab6e3 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/IReadOnlySecret.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/IReadOnlySecret.cs @@ -35,7 +35,7 @@ public interface IReadOnlySecret : IReadOnlySecret /// /// raised an exception. /// - void Read(Action readAction); + public void Read(Action readAction); } /// @@ -63,7 +63,7 @@ public interface IReadOnlySecret : IAsyncDisposable, IDisposable /// /// raised an exception. /// - void Read(Action> readAction); + public void Read(Action> readAction); /// /// Gets a value indicating whether or not the current has a value. @@ -76,7 +76,7 @@ public Boolean HasValue /// /// Gets a textual name that uniquely identifies the current . /// - String Name + public String Name { get; } @@ -84,7 +84,7 @@ String Name /// /// Gets the type of the secret value. /// - Type ValueType + public Type ValueType { get; } diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecret.cs index ad0fd2f3..f6eda5ce 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecret.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecret.cs @@ -29,7 +29,7 @@ public interface ISecret : IReadOnlySecret, ISecret /// /// raised an exception or returned an invalid . /// - void Write(Func writeFunction); + public void Write(Func writeFunction); } /// diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretVault.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretVault.cs index b0aff1ef..5384cf64 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretVault.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretVault.cs @@ -36,7 +36,7 @@ public interface ISecretVault : IAsyncDisposable, IDisposable /// /// The object is disposed. /// - void AddOrUpdate(String name, Byte[] secret); + public void AddOrUpdate(String name, Byte[] secret); /// /// Adds the specified secret using the specified name to the current , or updates it if a secret @@ -57,7 +57,7 @@ public interface ISecretVault : IAsyncDisposable, IDisposable /// /// The object is disposed. /// - void AddOrUpdate(String name, String secret); + public void AddOrUpdate(String name, String secret); /// /// Adds the specified secret using the specified name to the current , or updates it if a secret @@ -78,7 +78,7 @@ public interface ISecretVault : IAsyncDisposable, IDisposable /// /// The object is disposed. /// - void AddOrUpdate(String name, Guid secret); + public void AddOrUpdate(String name, Guid secret); /// /// Adds the specified secret using the specified name to the current , or updates it if a secret @@ -99,7 +99,7 @@ public interface ISecretVault : IAsyncDisposable, IDisposable /// /// The object is disposed. /// - void AddOrUpdate(String name, Double secret); + public void AddOrUpdate(String name, Double secret); /// /// Adds the specified secret using the specified name to the current , or updates it if a secret @@ -120,7 +120,7 @@ public interface ISecretVault : IAsyncDisposable, IDisposable /// /// The object is disposed. /// - void AddOrUpdate(String name, SymmetricKey secret); + public void AddOrUpdate(String name, SymmetricKey secret); /// /// Adds the specified secret using the specified name to the current , or updates it if a secret @@ -141,7 +141,7 @@ public interface ISecretVault : IAsyncDisposable, IDisposable /// /// The object is disposed. /// - void AddOrUpdate(String name, CascadingSymmetricKey secret); + public void AddOrUpdate(String name, CascadingSymmetricKey secret); /// /// Adds the specified secret using the specified name to the current , or updates it if a secret @@ -162,7 +162,7 @@ public interface ISecretVault : IAsyncDisposable, IDisposable /// /// The object is disposed. /// - void AddOrUpdate(String name, X509Certificate2 secret); + public void AddOrUpdate(String name, X509Certificate2 secret); /// /// Removes and safely disposes of all secrets that are stored by the current . @@ -170,7 +170,7 @@ public interface ISecretVault : IAsyncDisposable, IDisposable /// /// The object is disposed. /// - void Clear(); + public void Clear(); /// /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read @@ -201,7 +201,7 @@ public interface ISecretVault : IAsyncDisposable, IDisposable /// raised an exception -or- the secret vault does not contain a valid secret of the /// specified type. /// - Task ReadAsync(String name, Action> readAction); + public Task ReadAsync(String name, Action> readAction); /// /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read @@ -232,7 +232,7 @@ public interface ISecretVault : IAsyncDisposable, IDisposable /// raised an exception -or- the secret vault does not contain a valid secret of the /// specified type. /// - Task ReadAsync(String name, Action readAction); + public Task ReadAsync(String name, Action readAction); /// /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read @@ -263,7 +263,7 @@ public interface ISecretVault : IAsyncDisposable, IDisposable /// raised an exception -or- the secret vault does not contain a valid secret of the /// specified type. /// - Task ReadAsync(String name, Action readAction); + public Task ReadAsync(String name, Action readAction); /// /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read @@ -294,7 +294,7 @@ public interface ISecretVault : IAsyncDisposable, IDisposable /// raised an exception -or- the secret vault does not contain a valid secret of the /// specified type. /// - Task ReadAsync(String name, Action readAction); + public Task ReadAsync(String name, Action readAction); /// /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read @@ -325,7 +325,7 @@ public interface ISecretVault : IAsyncDisposable, IDisposable /// raised an exception -or- the secret vault does not contain a valid secret of the /// specified type. /// - Task ReadAsync(String name, Action readAction); + public Task ReadAsync(String name, Action readAction); /// /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read @@ -356,7 +356,7 @@ public interface ISecretVault : IAsyncDisposable, IDisposable /// raised an exception -or- the secret vault does not contain a valid secret of the /// specified type. /// - Task ReadAsync(String name, Action readAction); + public Task ReadAsync(String name, Action readAction); /// /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read @@ -387,7 +387,7 @@ public interface ISecretVault : IAsyncDisposable, IDisposable /// raised an exception -or- the secret vault does not contain a valid secret of the /// specified type. /// - Task ReadAsync(String name, Action readAction); + public Task ReadAsync(String name, Action readAction); /// /// Attempts to remove a secret with the specified name. @@ -407,7 +407,7 @@ public interface ISecretVault : IAsyncDisposable, IDisposable /// /// The object is disposed. /// - Boolean TryRemove(String name); + public Boolean TryRemove(String name); /// /// Gets the number of secrets that are stored by the current . @@ -415,7 +415,7 @@ public interface ISecretVault : IAsyncDisposable, IDisposable /// /// The object is disposed. /// - Int32 Count + public Int32 Count { get; } @@ -426,7 +426,7 @@ Int32 Count /// /// The object is disposed. /// - IEnumerable Names + public IEnumerable Names { get; } diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/ICascadingSymmetricKey.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/ICascadingSymmetricKey.cs index a199b6de..05ccf829 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Symmetric/ICascadingSymmetricKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Symmetric/ICascadingSymmetricKey.cs @@ -19,12 +19,12 @@ public interface ICascadingSymmetricKey : IAsyncDisposable, IDisposable /// /// A binary representation of the current . /// - ISecureBuffer ToBuffer(); + public ISecureBuffer ToBuffer(); /// /// Gets the number of layers of encryption that a resulting transform will apply. /// - Int32 Depth + public Int32 Depth { get; } @@ -32,7 +32,7 @@ Int32 Depth /// /// Gets the ordered array of keys comprising the cascading key. /// - IEnumerable Keys + public IEnumerable Keys { get; } diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/ISymmetricKey.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/ISymmetricKey.cs index 77d2c70f..db25699c 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Symmetric/ISymmetricKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Symmetric/ISymmetricKey.cs @@ -18,7 +18,7 @@ public interface ISymmetricKey : IAsyncDisposable, IDisposable /// /// A binary representation of the current . /// - ISecureBuffer ToBuffer(); + public ISecureBuffer ToBuffer(); /// /// Converts the current to symmetric-key plaintext with correct bit-length for the encryption @@ -27,12 +27,12 @@ public interface ISymmetricKey : IAsyncDisposable, IDisposable /// /// The derived key. /// - ISecureBuffer ToDerivedKeyBytes(); + public ISecureBuffer ToDerivedKeyBytes(); /// /// Gets the symmetric-key algorithm for which a key is derived. /// - SymmetricAlgorithmSpecification Algorithm + public SymmetricAlgorithmSpecification Algorithm { get; } diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/ISymmetricProcessor.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/ISymmetricProcessor.cs index 99034879..d27b6c8a 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Symmetric/ISymmetricProcessor.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Symmetric/ISymmetricProcessor.cs @@ -30,7 +30,7 @@ public interface ISymmetricProcessor /// /// An exception was raised during decryption or deserialization. /// - T Decrypt(Byte[] ciphertext, ISymmetricKey key); + public T Decrypt(Byte[] ciphertext, ISymmetricKey key); /// /// Decrypts the specified ciphertext. @@ -47,7 +47,7 @@ public interface ISymmetricProcessor /// /// An exception was raised during decryption or deserialization. /// - T Decrypt(Byte[] ciphertext, ICascadingSymmetricKey key); + public T Decrypt(Byte[] ciphertext, ICascadingSymmetricKey key); /// /// Decrypts the specified ciphertext. @@ -67,7 +67,7 @@ public interface ISymmetricProcessor /// /// An exception was raised during decryption or deserialization. /// - T Decrypt(Byte[] ciphertext, ISecureBuffer key, SymmetricAlgorithmSpecification algorithm); + public T Decrypt(Byte[] ciphertext, ISecureBuffer key, SymmetricAlgorithmSpecification algorithm); /// /// Encrypts the specified plaintext object. @@ -84,7 +84,7 @@ public interface ISymmetricProcessor /// /// An exception was raised during encryption or serialization. /// - Byte[] Encrypt(T plaintextObject, ISymmetricKey key); + public Byte[] Encrypt(T plaintextObject, ISymmetricKey key); /// /// Encrypts the specified plaintext object. @@ -101,7 +101,7 @@ public interface ISymmetricProcessor /// /// An exception was raised during encryption or serialization. /// - Byte[] Encrypt(T plaintextObject, ICascadingSymmetricKey key); + public Byte[] Encrypt(T plaintextObject, ICascadingSymmetricKey key); /// /// Encrypts the specified plaintext object. @@ -121,6 +121,6 @@ public interface ISymmetricProcessor /// /// An exception was raised during encryption or serialization. /// - Byte[] Encrypt(T plaintextObject, ISecureBuffer key, SymmetricAlgorithmSpecification algorithm); + public Byte[] Encrypt(T plaintextObject, ISecureBuffer key, SymmetricAlgorithmSpecification algorithm); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess/IDataAccessRepository.cs b/src/RapidField.SolidInstruments.DataAccess/IDataAccessRepository.cs index 9c07defc..e2f67325 100644 --- a/src/RapidField.SolidInstruments.DataAccess/IDataAccessRepository.cs +++ b/src/RapidField.SolidInstruments.DataAccess/IDataAccessRepository.cs @@ -30,7 +30,7 @@ public interface IDataAccessRepository : IDataAccessRepository /// /// The object is disposed. /// - void Add(TEntity entity); + public void Add(TEntity entity); /// /// Updates the specified entity in the current , or adds it if it doesn't @@ -45,7 +45,7 @@ public interface IDataAccessRepository : IDataAccessRepository /// /// The object is disposed. /// - void AddOrUpdate(TEntity entity); + public void AddOrUpdate(TEntity entity); /// /// Updates the specified entities in the current , or adds them if they don't @@ -63,7 +63,7 @@ public interface IDataAccessRepository : IDataAccessRepository /// /// The object is disposed. /// - void AddOrUpdateRange(IEnumerable entities); + public void AddOrUpdateRange(IEnumerable entities); /// /// Adds the specified entities to the current . @@ -80,7 +80,7 @@ public interface IDataAccessRepository : IDataAccessRepository /// /// The object is disposed. /// - void AddRange(IEnumerable entities); + public void AddRange(IEnumerable entities); /// /// Returns all entities from the current . @@ -91,7 +91,7 @@ public interface IDataAccessRepository : IDataAccessRepository /// /// The object is disposed. /// - IQueryable All(); + public IQueryable All(); /// /// Determines whether or not the specified entity exists in the current . @@ -109,7 +109,7 @@ public interface IDataAccessRepository : IDataAccessRepository /// /// The object is disposed. /// - Boolean Contains(TEntity entity); + public Boolean Contains(TEntity entity); /// /// Determines whether or not any entities matching the specified predicate exist in the current @@ -128,7 +128,7 @@ public interface IDataAccessRepository : IDataAccessRepository /// /// The object is disposed. /// - Boolean ContainsWhere(Expression> predicate); + public Boolean ContainsWhere(Expression> predicate); /// /// Returns the number of entities matching the specified predicate in the current @@ -146,7 +146,7 @@ public interface IDataAccessRepository : IDataAccessRepository /// /// The object is disposed. /// - Int64 CountWhere(Expression> predicate); + public Int64 CountWhere(Expression> predicate); /// /// Returns all entities matching the specified predicate from the current . @@ -163,7 +163,7 @@ public interface IDataAccessRepository : IDataAccessRepository /// /// The object is disposed. /// - IQueryable FindWhere(Expression> predicate); + public IQueryable FindWhere(Expression> predicate); /// /// Removes the specified entity from the current . @@ -177,7 +177,7 @@ public interface IDataAccessRepository : IDataAccessRepository /// /// The object is disposed. /// - void Remove(TEntity entity); + public void Remove(TEntity entity); /// /// Removes the specified entities from the current . @@ -194,7 +194,7 @@ public interface IDataAccessRepository : IDataAccessRepository /// /// The object is disposed. /// - void RemoveRange(IEnumerable entities); + public void RemoveRange(IEnumerable entities); /// /// Removes the entities matching the specified predicate from the current . @@ -208,7 +208,7 @@ public interface IDataAccessRepository : IDataAccessRepository /// /// The object is disposed. /// - void RemoveWhere(Expression> predicate); + public void RemoveWhere(Expression> predicate); /// /// Updates the specified entity in the current . @@ -222,7 +222,7 @@ public interface IDataAccessRepository : IDataAccessRepository /// /// The object is disposed. /// - void Update(TEntity entity); + public void Update(TEntity entity); /// /// Updates the specified entities in the current . @@ -239,7 +239,7 @@ public interface IDataAccessRepository : IDataAccessRepository /// /// The object is disposed. /// - void UpdateRange(IEnumerable entities); + public void UpdateRange(IEnumerable entities); } /// @@ -257,7 +257,7 @@ public interface IDataAccessRepository : IAsyncDisposable, IDisposable /// /// The object is disposed. /// - Boolean Any(); + public Boolean Any(); /// /// Returns the number of entities in the current . @@ -268,12 +268,12 @@ public interface IDataAccessRepository : IAsyncDisposable, IDisposable /// /// The object is disposed. /// - Int64 Count(); + public Int64 Count(); /// /// Gets the entity type of the current . /// - Type EntityType + public Type EntityType { get; } diff --git a/src/RapidField.SolidInstruments.DataAccess/IDataAccessTransaction.cs b/src/RapidField.SolidInstruments.DataAccess/IDataAccessTransaction.cs index 7e4b8dbc..c574cfff 100644 --- a/src/RapidField.SolidInstruments.DataAccess/IDataAccessTransaction.cs +++ b/src/RapidField.SolidInstruments.DataAccess/IDataAccessTransaction.cs @@ -22,7 +22,7 @@ public interface IDataAccessTransaction : IInstrument /// /// The object is disposed. /// - void Begin(); + public void Begin(); /// /// Asynchronously initiates the current . @@ -36,7 +36,7 @@ public interface IDataAccessTransaction : IInstrument /// /// The object is disposed. /// - Task BeginAsync(); + public Task BeginAsync(); /// /// Commits all changes made within the scope of the current . @@ -47,7 +47,7 @@ public interface IDataAccessTransaction : IInstrument /// /// The object is disposed. /// - void Commit(); + public void Commit(); /// /// Asynchronously commits all changes made within the scope of the current . @@ -61,7 +61,7 @@ public interface IDataAccessTransaction : IInstrument /// /// The object is disposed. /// - Task CommitAsync(); + public Task CommitAsync(); /// /// Rejects all changes made within the scope of the current . @@ -72,7 +72,7 @@ public interface IDataAccessTransaction : IInstrument /// /// The object is disposed. /// - void Reject(); + public void Reject(); /// /// Asynchronously rejects all changes made within the scope of the current . @@ -86,12 +86,12 @@ public interface IDataAccessTransaction : IInstrument /// /// The object is disposed. /// - Task RejectAsync(); + public Task RejectAsync(); /// /// Gets the state of the current . /// - DataAccessTransactionState State + public DataAccessTransactionState State { get; } diff --git a/src/RapidField.SolidInstruments.EventAuthoring/DomainModelEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/DomainModelEvent.cs new file mode 100644 index 00000000..703f3417 --- /dev/null +++ b/src/RapidField.SolidInstruments.EventAuthoring/DomainModelEvent.cs @@ -0,0 +1,158 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Core.Domain; +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace RapidField.SolidInstruments.EventAuthoring +{ + /// + /// Represents information about an event related to an object that models a domain construct. + /// + /// + /// is the default implementation of . + /// + [DataContract] + public class DomainModelEvent : DomainEvent, IDomainModelEvent + where TModel : class, IDomainModel + { + /// + /// Initializes a new instance of the class. + /// + public DomainModelEvent() + : base() + { + Model = null; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A classification that describes the effect of a the event upon . + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public DomainModelEvent(TModel model, DomainModelEventClassification classification) + : this(model, classification, Array.Empty()) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A classification that describes the effect of a the event upon . + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public DomainModelEvent(TModel model, DomainModelEventClassification classification, IEnumerable labels) + : this(model, classification, labels, DefaultVerbosity) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A classification that describes the effect of a the event upon . + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// is -or- is . + /// + /// + /// is equal to -or- + /// is equal to . + /// + public DomainModelEvent(TModel model, DomainModelEventClassification classification, IEnumerable labels, EventVerbosity verbosity) + : this(model, classification, labels, verbosity, null) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A classification that describes the effect of a the event upon . + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A textual description of the event. This argument can be . + /// + /// + /// is -or- is . + /// + /// + /// is equal to -or- + /// is equal to . + /// + public DomainModelEvent(TModel model, DomainModelEventClassification classification, IEnumerable labels, EventVerbosity verbosity, String description) + : base(labels, verbosity, description) + { + Classification = classification.RejectIf().IsEqualToValue(DomainModelEventClassification.Unspecified, nameof(classification)); + Model = model.RejectIf().IsNull(nameof(model)); + } + + /// + /// Gets or sets a classification that describes the effect of a the current upon + /// . + /// + [DataMember] + public DomainModelEventClassification Classification + { + get; + set; + } + + /// + /// Gets or sets the resulting state of the associated domain model. + /// + [DataMember] + public TModel Model + { + get; + set; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring/DomainModelEventClassification.cs b/src/RapidField.SolidInstruments.EventAuthoring/DomainModelEventClassification.cs new file mode 100644 index 00000000..60e4ce75 --- /dev/null +++ b/src/RapidField.SolidInstruments.EventAuthoring/DomainModelEventClassification.cs @@ -0,0 +1,47 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; +using System; +using System.Runtime.Serialization; + +namespace RapidField.SolidInstruments.EventAuthoring +{ + /// + /// Specifies a classification that describes the effect of an event upon an . + /// + [DataContract] + public enum DomainModelEventClassification : Int32 + { + /// + /// The domain model event classification is not specified. + /// + [EnumMember] + Unspecified = 0, + + /// + /// The event relates to the model, but does not otherwise cause a change to its characteristics. + /// + [EnumMember] + Associated = 1, + + /// + /// The event causes the model to be created. + /// + [EnumMember] + Created = 2, + + /// + /// The event causes the model to be deleted. + /// + [EnumMember] + Deleted = 3, + + /// + /// The event causes the model to be updated. + /// + [EnumMember] + Updated = 4 + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring/IApplicationStateEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/IApplicationStateEvent.cs index 2ff1166b..d306fdfb 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/IApplicationStateEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/IApplicationStateEvent.cs @@ -14,7 +14,7 @@ public interface IApplicationStateEvent : IMetadataEnrichedEvent /// /// Gets or sets a name or value that uniquely identifies the associated application. /// - String ApplicationIdentity + public String ApplicationIdentity { get; set; diff --git a/src/RapidField.SolidInstruments.EventAuthoring/IDomainModelAssociatedEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/IDomainModelAssociatedEvent.cs new file mode 100644 index 00000000..37f3225d --- /dev/null +++ b/src/RapidField.SolidInstruments.EventAuthoring/IDomainModelAssociatedEvent.cs @@ -0,0 +1,16 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; + +namespace RapidField.SolidInstruments.EventAuthoring +{ + /// + /// Represents information about an event related to an object that models a domain construct. + /// + public interface IDomainModelAssociatedEvent : IDomainModelEvent + where TModel : class, IDomainModel + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring/IDomainModelCreatedEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/IDomainModelCreatedEvent.cs new file mode 100644 index 00000000..440d0427 --- /dev/null +++ b/src/RapidField.SolidInstruments.EventAuthoring/IDomainModelCreatedEvent.cs @@ -0,0 +1,16 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; + +namespace RapidField.SolidInstruments.EventAuthoring +{ + /// + /// Represents information about the creation of an object that models a domain construct. + /// + public interface IDomainModelCreatedEvent : IDomainModelEvent + where TModel : class, IDomainModel + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring/IDomainModelDeletedEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/IDomainModelDeletedEvent.cs new file mode 100644 index 00000000..17736317 --- /dev/null +++ b/src/RapidField.SolidInstruments.EventAuthoring/IDomainModelDeletedEvent.cs @@ -0,0 +1,16 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; + +namespace RapidField.SolidInstruments.EventAuthoring +{ + /// + /// Represents information about the deletion of an object that models a domain construct. + /// + public interface IDomainModelDeletedEvent : IDomainModelEvent + where TModel : class, IDomainModel + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring/IDomainModelEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/IDomainModelEvent.cs new file mode 100644 index 00000000..37b6740c --- /dev/null +++ b/src/RapidField.SolidInstruments.EventAuthoring/IDomainModelEvent.cs @@ -0,0 +1,32 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; + +namespace RapidField.SolidInstruments.EventAuthoring +{ + /// + /// Represents information about an event related to an object that models a domain construct. + /// + public interface IDomainModelEvent : IDomainEvent + where TModel : class, IDomainModel + { + /// + /// Gets a classification that describes the effect of a the current upon + /// . + /// + public DomainModelEventClassification Classification + { + get; + } + + /// + /// Gets the resulting state of the associated domain model. + /// + public TModel Model + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring/IDomainEntityEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/IDomainModelUpdatedEvent.cs similarity index 65% rename from src/RapidField.SolidInstruments.EventAuthoring/IDomainEntityEvent.cs rename to src/RapidField.SolidInstruments.EventAuthoring/IDomainModelUpdatedEvent.cs index b3fbb6b7..a2c4b5a1 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/IDomainEntityEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/IDomainModelUpdatedEvent.cs @@ -2,12 +2,15 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= +using RapidField.SolidInstruments.Core.Domain; + namespace RapidField.SolidInstruments.EventAuthoring { /// - /// Represents information about activity related to a domain entity. + /// Represents information about an update to an object that models a domain construct. /// - public interface IDomainEntityEvent : IDomainEvent + public interface IDomainModelUpdatedEvent : IDomainModelEvent + where TModel : class, IDomainModel { } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring/IErrorEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/IErrorEvent.cs index 72971093..12f3c536 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/IErrorEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/IErrorEvent.cs @@ -14,7 +14,7 @@ public interface IErrorEvent : IEvent /// /// Gets or sets a name or value that uniquely identifies the application in which the associated error occurred. /// - String ApplicationIdentity + public String ApplicationIdentity { get; set; @@ -23,7 +23,7 @@ String ApplicationIdentity /// /// Gets or sets textual diagnostic information about the associated error. /// - String DiagnosticDetails + public String DiagnosticDetails { get; set; diff --git a/src/RapidField.SolidInstruments.EventAuthoring/IEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/IEvent.cs index 0d30258e..c932c364 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/IEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/IEvent.cs @@ -17,12 +17,12 @@ public interface IEvent : IComparable, IEquatable /// /// An array of bytes representing the current . /// - Byte[] ToByteArray(); + public Byte[] ToByteArray(); /// /// Gets or sets the category of the event. /// - EventCategory Category + public EventCategory Category { get; set; @@ -31,7 +31,7 @@ EventCategory Category /// /// Gets or sets a textual description of the event. /// - String Description + public String Description { get; set; @@ -40,7 +40,7 @@ String Description /// /// Gets or sets a that indicates when the event occurred. /// - DateTime TimeStamp + public DateTime TimeStamp { get; set; @@ -49,7 +49,7 @@ DateTime TimeStamp /// /// Gets or sets the verbosity level of the event. /// - EventVerbosity Verbosity + public EventVerbosity Verbosity { get; set; diff --git a/src/RapidField.SolidInstruments.EventAuthoring/IExceptionRaisedEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/IExceptionRaisedEvent.cs index 26de68f9..201f8a78 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/IExceptionRaisedEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/IExceptionRaisedEvent.cs @@ -14,7 +14,7 @@ public interface IExceptionRaisedEvent : IErrorEvent /// /// Gets or sets the full name of the type of the associated that was raised. /// - String ExceptionTypeFullName + public String ExceptionTypeFullName { get; set; diff --git a/src/RapidField.SolidInstruments.EventAuthoring/ILabeledEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/ILabeledEvent.cs index 24f6053a..eceb69c5 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/ILabeledEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/ILabeledEvent.cs @@ -16,7 +16,7 @@ public interface ILabeledEvent : IEvent /// Gets a collection of textual labels that provide categorical and/or contextual information about the current /// . /// - ICollection Labels + public ICollection Labels { get; } diff --git a/src/RapidField.SolidInstruments.EventAuthoring/IMetadataEnrichedEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/IMetadataEnrichedEvent.cs index a253e633..ea785bc5 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/IMetadataEnrichedEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/IMetadataEnrichedEvent.cs @@ -15,7 +15,7 @@ public interface IMetadataEnrichedEvent : IEvent /// /// Gets a dictionary of metadata for the current . /// - IDictionary Metadata + public IDictionary Metadata { get; } diff --git a/src/RapidField.SolidInstruments.EventAuthoring/IReportable.cs b/src/RapidField.SolidInstruments.EventAuthoring/IReportable.cs index 2c1ce545..f61c5371 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/IReportable.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/IReportable.cs @@ -15,6 +15,6 @@ public interface IReportable /// /// An representing information about the current object. /// - IEvent ComposeEvent(); + public IEvent ComposeEvent(); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring/ISecurityEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/ISecurityEvent.cs index 683999c9..8983844f 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/ISecurityEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/ISecurityEvent.cs @@ -12,7 +12,7 @@ public interface ISecurityEvent : IEvent /// /// Gets or sets the severity of the current . /// - SecurityEventSeverity Severity + public SecurityEventSeverity Severity { get; set; diff --git a/src/RapidField.SolidInstruments.EventAuthoring/ISystemStateEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/ISystemStateEvent.cs index f7f3db64..92722559 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/ISystemStateEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/ISystemStateEvent.cs @@ -14,7 +14,7 @@ public interface ISystemStateEvent : IMetadataEnrichedEvent /// /// Gets or sets a name or value that uniquely identifies the associated system. /// - String SystemIdentity + public String SystemIdentity { get; set; diff --git a/src/RapidField.SolidInstruments.EventAuthoring/ITransactionEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/ITransactionEvent.cs index 230e18a7..c8f67f9f 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/ITransactionEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/ITransactionEvent.cs @@ -12,7 +12,7 @@ public interface ITransactionEvent : IEvent /// /// Gets or sets the outcome of the current . /// - TransactionEventOutcome Outcome + public TransactionEventOutcome Outcome { get; set; diff --git a/src/RapidField.SolidInstruments.EventAuthoring/IUserActionEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/IUserActionEvent.cs index 7910314a..65a93e9e 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/IUserActionEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/IUserActionEvent.cs @@ -12,7 +12,7 @@ public interface IUserActionEvent : IEvent /// /// Gets or sets the outcome of the current . /// - UserActionEventOutcome Outcome + public UserActionEventOutcome Outcome { get; set; diff --git a/src/RapidField.SolidInstruments.InversionOfControl/IDependencyContainer.cs b/src/RapidField.SolidInstruments.InversionOfControl/IDependencyContainer.cs index 2b85eaf1..30f71037 100644 --- a/src/RapidField.SolidInstruments.InversionOfControl/IDependencyContainer.cs +++ b/src/RapidField.SolidInstruments.InversionOfControl/IDependencyContainer.cs @@ -24,7 +24,7 @@ public interface IDependencyContainer : IInstrument /// /// The object is disposed. /// - IDependencyScope CreateScope(); + public IDependencyScope CreateScope(); /// /// Requests an object of specified type from the current . @@ -44,6 +44,6 @@ public interface IDependencyContainer : IInstrument /// /// The object is disposed. /// - Object Resolve(Type type); + public Object Resolve(Type type); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.InversionOfControl/IDependencyEngine.cs b/src/RapidField.SolidInstruments.InversionOfControl/IDependencyEngine.cs index 5ffd3fb0..5b0203ea 100644 --- a/src/RapidField.SolidInstruments.InversionOfControl/IDependencyEngine.cs +++ b/src/RapidField.SolidInstruments.InversionOfControl/IDependencyEngine.cs @@ -17,7 +17,7 @@ public interface IDependencyEngine : IAsyncDisposable, IDisposable /// /// An exception was raised while configuring the container. /// - IDependencyContainer Container + public IDependencyContainer Container { get; } @@ -28,7 +28,7 @@ IDependencyContainer Container /// /// An exception was raised while configuring the container. /// - IServiceProvider Provider + public IServiceProvider Provider { get; } diff --git a/src/RapidField.SolidInstruments.InversionOfControl/IDependencyModule.cs b/src/RapidField.SolidInstruments.InversionOfControl/IDependencyModule.cs index 32e633ed..0368edac 100644 --- a/src/RapidField.SolidInstruments.InversionOfControl/IDependencyModule.cs +++ b/src/RapidField.SolidInstruments.InversionOfControl/IDependencyModule.cs @@ -27,6 +27,6 @@ public interface IDependencyModule /// /// An exception was raised while attempting to configure the container. /// - void Configure(TConfigurator configurator); + public void Configure(TConfigurator configurator); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.InversionOfControl/IDependencyPackage.cs b/src/RapidField.SolidInstruments.InversionOfControl/IDependencyPackage.cs index 29259a7c..540a9400 100644 --- a/src/RapidField.SolidInstruments.InversionOfControl/IDependencyPackage.cs +++ b/src/RapidField.SolidInstruments.InversionOfControl/IDependencyPackage.cs @@ -38,7 +38,7 @@ public interface IDependencyPackage : IDependencyPackage /// /// An exception was raised while attempting to configure a container. /// - TEngine CreateEngine(IConfiguration applicationConfiguration); + public TEngine CreateEngine(IConfiguration applicationConfiguration); /// /// Creates a new dependency engine that is configured by the current @@ -60,7 +60,7 @@ public interface IDependencyPackage : IDependencyPackage /// /// An exception was raised while attempting to configure a container. /// - TEngine CreateEngine(IConfiguration applicationConfiguration, IServiceCollection serviceDescriptors); + public TEngine CreateEngine(IConfiguration applicationConfiguration, IServiceCollection serviceDescriptors); } /// @@ -87,6 +87,6 @@ public interface IDependencyPackage /// /// An exception was raised while attempting to create the modules for the package. /// - IEnumerable> GetModules(IConfiguration applicationConfiguration); + public IEnumerable> GetModules(IConfiguration applicationConfiguration); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.InversionOfControl/IDependencyScope.cs b/src/RapidField.SolidInstruments.InversionOfControl/IDependencyScope.cs index 6fc3e7f4..35020082 100644 --- a/src/RapidField.SolidInstruments.InversionOfControl/IDependencyScope.cs +++ b/src/RapidField.SolidInstruments.InversionOfControl/IDependencyScope.cs @@ -23,7 +23,7 @@ public interface IDependencyScope : IAsyncDisposable, IDisposable /// /// The object is disposed. /// - IDependencyScope CreateChildScope(); + public IDependencyScope CreateChildScope(); /// /// Requests an object of specified type from the associated container for the current . @@ -43,7 +43,7 @@ public interface IDependencyScope : IAsyncDisposable, IDisposable /// /// The object is disposed. /// - Object Resolve(Type type); + public Object Resolve(Type type); /// /// Requests an object of specified type from the associated container for the current . @@ -60,7 +60,7 @@ public interface IDependencyScope : IAsyncDisposable, IDisposable /// /// The object is disposed. /// - T Resolve() + public T Resolve() where T : class; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.InversionOfControl/IServiceInjector.cs b/src/RapidField.SolidInstruments.InversionOfControl/IServiceInjector.cs index 76645c20..2118c026 100644 --- a/src/RapidField.SolidInstruments.InversionOfControl/IServiceInjector.cs +++ b/src/RapidField.SolidInstruments.InversionOfControl/IServiceInjector.cs @@ -29,6 +29,6 @@ public interface IServiceInjector /// /// An exception was raised while injecting the service descriptors. /// - void Inject(TConfigurator configurator); + public void Inject(TConfigurator configurator); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Mathematics/Data/ILineSeries.cs b/src/RapidField.SolidInstruments.Mathematics/Data/ILineSeries.cs index 6573ae71..b6409351 100644 --- a/src/RapidField.SolidInstruments.Mathematics/Data/ILineSeries.cs +++ b/src/RapidField.SolidInstruments.Mathematics/Data/ILineSeries.cs @@ -30,7 +30,7 @@ public interface ILineSeries : ITwoDimensionalDataSet /// The specified x-axis value does not exist within the data set. /// - TYAxis GetYAxisValue(TXAxis xAxisValue); + public TYAxis GetYAxisValue(TXAxis xAxisValue); /// /// Attempts to get the y-axis value associated with the specified x-axis value. @@ -44,6 +44,6 @@ public interface ILineSeries : ITwoDimensionalDataSet /// if the specified x-axis value exists, otherwise . /// - Boolean TryGetYAxisValue(TXAxis xAxisValue, out TYAxis yAxisValue); + public Boolean TryGetYAxisValue(TXAxis xAxisValue, out TYAxis yAxisValue); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Mathematics/Data/IScalarLineSeries.cs b/src/RapidField.SolidInstruments.Mathematics/Data/IScalarLineSeries.cs index 6d10d74d..87870a2a 100644 --- a/src/RapidField.SolidInstruments.Mathematics/Data/IScalarLineSeries.cs +++ b/src/RapidField.SolidInstruments.Mathematics/Data/IScalarLineSeries.cs @@ -42,6 +42,6 @@ public interface IScalarLineSeries : ILineSeries /// /// An exception was raised while attempting to interpolate a y-axis value for . /// - TYAxis GetYAxisValue(TXAxis xAxisValue, InterpolationMode interpolationMode); + public TYAxis GetYAxisValue(TXAxis xAxisValue, InterpolationMode interpolationMode); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Mathematics/Data/ITwoDimensionalDataSet.cs b/src/RapidField.SolidInstruments.Mathematics/Data/ITwoDimensionalDataSet.cs index 390abd46..310e471d 100644 --- a/src/RapidField.SolidInstruments.Mathematics/Data/ITwoDimensionalDataSet.cs +++ b/src/RapidField.SolidInstruments.Mathematics/Data/ITwoDimensionalDataSet.cs @@ -22,7 +22,7 @@ public interface ITwoDimensionalDataSet : IEnumerable /// Gets the collection of values comprising the x-axis. /// - IEnumerable XAxis + public IEnumerable XAxis { get; } @@ -30,7 +30,7 @@ IEnumerable XAxis /// /// Gets the collection of values comprising the y-axis. /// - IEnumerable YAxis + public IEnumerable YAxis { get; } diff --git a/src/RapidField.SolidInstruments.Mathematics/Sequences/IArithmeticSequence.cs b/src/RapidField.SolidInstruments.Mathematics/Sequences/IArithmeticSequence.cs index 7618c3d8..a9924e1c 100644 --- a/src/RapidField.SolidInstruments.Mathematics/Sequences/IArithmeticSequence.cs +++ b/src/RapidField.SolidInstruments.Mathematics/Sequences/IArithmeticSequence.cs @@ -15,7 +15,7 @@ public interface IArithmeticSequence : IInfiniteSequence /// /// Gets the constant difference between the terms in the sequence. /// - Double CommonDifference + public Double CommonDifference { get; } diff --git a/src/RapidField.SolidInstruments.Mathematics/Sequences/IGeometricSequence.cs b/src/RapidField.SolidInstruments.Mathematics/Sequences/IGeometricSequence.cs index dd7189bc..9c8d79c8 100644 --- a/src/RapidField.SolidInstruments.Mathematics/Sequences/IGeometricSequence.cs +++ b/src/RapidField.SolidInstruments.Mathematics/Sequences/IGeometricSequence.cs @@ -16,7 +16,7 @@ public interface IGeometricSequence : IInfiniteSequence /// /// Gets the constant factor between the terms in the sequence. /// - Double CommonRatio + public Double CommonRatio { get; } diff --git a/src/RapidField.SolidInstruments.Messaging/IMessageAdapter.cs b/src/RapidField.SolidInstruments.Messaging/IMessageAdapter.cs index c2732891..3bca7559 100644 --- a/src/RapidField.SolidInstruments.Messaging/IMessageAdapter.cs +++ b/src/RapidField.SolidInstruments.Messaging/IMessageAdapter.cs @@ -31,7 +31,7 @@ public interface IMessageAdapter /// /// is . /// - TAdaptedMessage ConvertForward(TMessage message) + public TAdaptedMessage ConvertForward(TMessage message) where TMessage : class, IMessageBase; /// @@ -49,13 +49,13 @@ TAdaptedMessage ConvertForward(TMessage message) /// /// is . /// - TMessage ConvertReverse(TAdaptedMessage message) + public TMessage ConvertReverse(TAdaptedMessage message) where TMessage : class, IMessageBase; /// /// Gets the type of implementation-specific adapted messages. /// - Type AdaptedMessageType + public Type AdaptedMessageType { get; } @@ -63,7 +63,7 @@ Type AdaptedMessageType /// /// Gets the format that is used to serialize and deserialize messages. /// - SerializationFormat MessageSerializationFormat + public SerializationFormat MessageSerializationFormat { get; } diff --git a/src/RapidField.SolidInstruments.Messaging/IMessageBase.cs b/src/RapidField.SolidInstruments.Messaging/IMessageBase.cs index 1289e460..85714aaa 100644 --- a/src/RapidField.SolidInstruments.Messaging/IMessageBase.cs +++ b/src/RapidField.SolidInstruments.Messaging/IMessageBase.cs @@ -15,7 +15,7 @@ public interface IMessageBase : ICommandBase /// /// Gets or sets a unique identifier that is assigned to related messages. /// - Guid CorrelationIdentifier + public Guid CorrelationIdentifier { get; set; @@ -24,7 +24,7 @@ Guid CorrelationIdentifier /// /// Gets or sets a unique identifier for the message. /// - Guid Identifier + public Guid Identifier { get; set; @@ -33,7 +33,7 @@ Guid Identifier /// /// Gets or sets instructions and contextual information relating to processing for the current . /// - MessageProcessingInformation ProcessingInformation + public MessageProcessingInformation ProcessingInformation { get; set; diff --git a/src/RapidField.SolidInstruments.Messaging/IMessageHandler.cs b/src/RapidField.SolidInstruments.Messaging/IMessageHandler.cs index 008663b7..c0cfa199 100644 --- a/src/RapidField.SolidInstruments.Messaging/IMessageHandler.cs +++ b/src/RapidField.SolidInstruments.Messaging/IMessageHandler.cs @@ -41,7 +41,7 @@ public interface IMessageHandler : ICommandHandler /// Gets the targeted entity type for the current . /// - MessagingEntityType EntityType + public MessagingEntityType EntityType { get; } @@ -49,7 +49,7 @@ MessagingEntityType EntityType /// /// Gets the role of the current . /// - MessageHandlerRole Role + public MessageHandlerRole Role { get; } diff --git a/src/RapidField.SolidInstruments.Messaging/IMessageListener.cs b/src/RapidField.SolidInstruments.Messaging/IMessageListener.cs index c2e082f7..da1d568a 100644 --- a/src/RapidField.SolidInstruments.Messaging/IMessageListener.cs +++ b/src/RapidField.SolidInstruments.Messaging/IMessageListener.cs @@ -15,7 +15,7 @@ public interface IMessageListener /// /// Gets the type of the message that the current processes. /// - Type MessageType + public Type MessageType { get; } diff --git a/src/RapidField.SolidInstruments.Messaging/IMessageListeningFacade.cs b/src/RapidField.SolidInstruments.Messaging/IMessageListeningFacade.cs index 9d7465cf..6794bddc 100644 --- a/src/RapidField.SolidInstruments.Messaging/IMessageListeningFacade.cs +++ b/src/RapidField.SolidInstruments.Messaging/IMessageListeningFacade.cs @@ -68,7 +68,7 @@ public interface IMessageListeningFacade : IMessagingFacade /// /// The object is disposed. /// - void RegisterQueueMessageHandler(Action messageHandler) + public void RegisterQueueMessageHandler(Action messageHandler) where TMessage : class, IMessage; /// @@ -97,7 +97,7 @@ void RegisterQueueMessageHandler(Action messageHandler) /// /// The object is disposed. /// - void RegisterQueueMessageHandler(Action messageHandler, IEnumerable pathLabels) + public void RegisterQueueMessageHandler(Action messageHandler, IEnumerable pathLabels) where TMessage : class, IMessage; /// @@ -121,7 +121,7 @@ void RegisterQueueMessageHandler(Action messageHandler, IEnu /// /// The object is disposed. /// - void RegisterRequestMessageHandler(Func requestMessageHandler) + public void RegisterRequestMessageHandler(Func requestMessageHandler) where TRequestMessage : class, IRequestMessage where TResponseMessage : class, IResponseMessage; @@ -143,7 +143,7 @@ void RegisterRequestMessageHandler(Func /// The object is disposed. /// - void RegisterTopicMessageHandler(Action messageHandler) + public void RegisterTopicMessageHandler(Action messageHandler) where TMessage : class, IMessage; /// @@ -172,14 +172,14 @@ void RegisterTopicMessageHandler(Action messageHandler) /// /// The object is disposed. /// - void RegisterTopicMessageHandler(Action messageHandler, IEnumerable pathLabels) + public void RegisterTopicMessageHandler(Action messageHandler, IEnumerable pathLabels) where TMessage : class, IMessage; /// /// Gets the collection of message types for which the current has one or more /// registered handlers. /// - IEnumerable ListenedMessageTypes + public IEnumerable ListenedMessageTypes { get; } diff --git a/src/RapidField.SolidInstruments.Messaging/IMessageRequestingFacade.cs b/src/RapidField.SolidInstruments.Messaging/IMessageRequestingFacade.cs index d5bf772e..e11b7eed 100644 --- a/src/RapidField.SolidInstruments.Messaging/IMessageRequestingFacade.cs +++ b/src/RapidField.SolidInstruments.Messaging/IMessageRequestingFacade.cs @@ -78,7 +78,7 @@ public interface IMessageRequestingFacade : IMessagingFacade /// /// The object is disposed. /// - Task RequestAsync(TRequestMessage requestMessage) + public Task RequestAsync(TRequestMessage requestMessage) where TRequestMessage : class, IRequestMessage where TResponseMessage : class, IResponseMessage; } diff --git a/src/RapidField.SolidInstruments.Messaging/IMessageTransmitter.cs b/src/RapidField.SolidInstruments.Messaging/IMessageTransmitter.cs index 74dd85b1..a64faead 100644 --- a/src/RapidField.SolidInstruments.Messaging/IMessageTransmitter.cs +++ b/src/RapidField.SolidInstruments.Messaging/IMessageTransmitter.cs @@ -15,7 +15,7 @@ public interface IMessageTransmitter /// /// Gets the type of the message that the current transmits. /// - Type MessageType + public Type MessageType { get; } diff --git a/src/RapidField.SolidInstruments.Messaging/IMessageTransmittingFacade.cs b/src/RapidField.SolidInstruments.Messaging/IMessageTransmittingFacade.cs index ef606806..a6cbb787 100644 --- a/src/RapidField.SolidInstruments.Messaging/IMessageTransmittingFacade.cs +++ b/src/RapidField.SolidInstruments.Messaging/IMessageTransmittingFacade.cs @@ -59,7 +59,7 @@ public interface IMessageTransmittingFacade : IMessagingFacade /// /// The object is disposed. /// - Task TransmitToQueueAsync(TMessage message, IEnumerable pathLabels) + public Task TransmitToQueueAsync(TMessage message, IEnumerable pathLabels) where TMessage : class, IMessageBase; /// @@ -83,7 +83,7 @@ Task TransmitToQueueAsync(TMessage message, IEnumerable pathLa /// /// The object is disposed. /// - Task TransmitToQueueAsync(TMessage message) + public Task TransmitToQueueAsync(TMessage message) where TMessage : class, IMessageBase; /// @@ -115,7 +115,7 @@ Task TransmitToQueueAsync(TMessage message) /// /// The object is disposed. /// - Task TransmitToTopicAsync(TMessage message, IEnumerable pathLabels) + public Task TransmitToTopicAsync(TMessage message, IEnumerable pathLabels) where TMessage : class, IMessageBase; /// @@ -139,7 +139,7 @@ Task TransmitToTopicAsync(TMessage message, IEnumerable pathLa /// /// The object is disposed. /// - Task TransmitToTopicAsync(TMessage message) + public Task TransmitToTopicAsync(TMessage message) where TMessage : class, IMessageBase; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/IMessagingClientFactory.cs b/src/RapidField.SolidInstruments.Messaging/IMessagingClientFactory.cs index 3b8401bb..cec4b834 100644 --- a/src/RapidField.SolidInstruments.Messaging/IMessagingClientFactory.cs +++ b/src/RapidField.SolidInstruments.Messaging/IMessagingClientFactory.cs @@ -40,7 +40,7 @@ public interface IMessagingClientFactory : /// contains one or more null or empty labels and/or labels with non-alphanumeric characters, /// or contains more than three elements. /// - IMessagingEntityPath GetQueuePath(IEnumerable pathLabels) + public IMessagingEntityPath GetQueuePath(IEnumerable pathLabels) where TMessage : class; /// @@ -58,7 +58,7 @@ IMessagingEntityPath GetQueuePath(IEnumerable pathLabels) /// /// The object is disposed. /// - TReceiver GetQueueReceiver() + public TReceiver GetQueueReceiver() where TMessage : class; /// @@ -84,7 +84,7 @@ TReceiver GetQueueReceiver() /// /// The object is disposed. /// - TReceiver GetQueueReceiver(IEnumerable pathLabels) + public TReceiver GetQueueReceiver(IEnumerable pathLabels) where TMessage : class; /// @@ -102,7 +102,7 @@ TReceiver GetQueueReceiver(IEnumerable pathLabels) /// /// The object is disposed. /// - TSender GetQueueSender() + public TSender GetQueueSender() where TMessage : class; /// @@ -128,7 +128,7 @@ TSender GetQueueSender() /// /// The object is disposed. /// - TSender GetQueueSender(IEnumerable pathLabels) + public TSender GetQueueSender(IEnumerable pathLabels) where TMessage : class; /// @@ -173,7 +173,7 @@ public String GetSubscriptionName(String receiverIdentifier, IMessagin /// contains one or more null or empty labels and/or labels with non-alphanumeric characters, /// or contains more than three elements. /// - IMessagingEntityPath GetTopicPath(IEnumerable pathLabels) + public IMessagingEntityPath GetTopicPath(IEnumerable pathLabels) where TMessage : class; /// @@ -200,7 +200,7 @@ IMessagingEntityPath GetTopicPath(IEnumerable pathLabels) /// /// The object is disposed. /// - TReceiver GetTopicReceiver(String receiverIdentifier) + public TReceiver GetTopicReceiver(String receiverIdentifier) where TMessage : class; /// @@ -235,7 +235,7 @@ TReceiver GetTopicReceiver(String receiverIdentifier) /// /// The object is disposed. /// - TReceiver GetTopicReceiver(String receiverIdentifier, IEnumerable pathLabels) + public TReceiver GetTopicReceiver(String receiverIdentifier, IEnumerable pathLabels) where TMessage : class; /// @@ -253,7 +253,7 @@ TReceiver GetTopicReceiver(String receiverIdentifier, IEnumerable /// The object is disposed. /// - TSender GetTopicSender() + public TSender GetTopicSender() where TMessage : class; /// @@ -279,7 +279,7 @@ TSender GetTopicSender() /// /// The object is disposed. /// - TSender GetTopicSender(IEnumerable pathLabels) + public TSender GetTopicSender(IEnumerable pathLabels) where TMessage : class; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/IMessagingEntityPath.cs b/src/RapidField.SolidInstruments.Messaging/IMessagingEntityPath.cs index 72a4e216..ee6c31d9 100644 --- a/src/RapidField.SolidInstruments.Messaging/IMessagingEntityPath.cs +++ b/src/RapidField.SolidInstruments.Messaging/IMessagingEntityPath.cs @@ -19,7 +19,7 @@ public interface IMessagingEntityPath : IComparable, IEqua /// /// The specified value is invalid. /// - String LabelOne + public String LabelOne { get; set; @@ -32,7 +32,7 @@ String LabelOne /// /// The specified value is invalid. /// - String LabelThree + public String LabelThree { get; set; @@ -45,7 +45,7 @@ String LabelThree /// /// The specified value is invalid. /// - String LabelTwo + public String LabelTwo { get; set; @@ -63,7 +63,7 @@ String LabelTwo /// /// The specified value is invalid. /// - String MessageType + public String MessageType { get; set; @@ -76,7 +76,7 @@ String MessageType /// /// The specified value is invalid. /// - String Prefix + public String Prefix { get; set; diff --git a/src/RapidField.SolidInstruments.Messaging/IMessagingFacade.cs b/src/RapidField.SolidInstruments.Messaging/IMessagingFacade.cs index b623308d..3e04f553 100644 --- a/src/RapidField.SolidInstruments.Messaging/IMessagingFacade.cs +++ b/src/RapidField.SolidInstruments.Messaging/IMessagingFacade.cs @@ -43,7 +43,7 @@ public interface IMessagingFacade : IInstrument /// /// Gets the unique textual identifier for the current . /// - String Identifier + public String Identifier { get; } diff --git a/src/RapidField.SolidInstruments.Messaging/IResponseMessage.cs b/src/RapidField.SolidInstruments.Messaging/IResponseMessage.cs index 9a58906f..01b6e213 100644 --- a/src/RapidField.SolidInstruments.Messaging/IResponseMessage.cs +++ b/src/RapidField.SolidInstruments.Messaging/IResponseMessage.cs @@ -14,7 +14,7 @@ public interface IResponseMessage : IMessage /// /// Gets or sets the identifier for the associated request message. /// - Guid RequestMessageIdentifier + public Guid RequestMessageIdentifier { get; set; diff --git a/src/RapidField.SolidInstruments.Messaging/Service/IHeartbeatScheduleItem.cs b/src/RapidField.SolidInstruments.Messaging/Service/IHeartbeatScheduleItem.cs index 3fbe9926..199b8b9f 100644 --- a/src/RapidField.SolidInstruments.Messaging/Service/IHeartbeatScheduleItem.cs +++ b/src/RapidField.SolidInstruments.Messaging/Service/IHeartbeatScheduleItem.cs @@ -28,12 +28,12 @@ public interface IHeartbeatScheduleItem : IComparable, I /// /// An exception was raised while attempting to transmit the heartbeat message. /// - Task TransmitHeartbeatMessageAsync(IMessageTransmittingFacade messageTransmittingFacade); + public Task TransmitHeartbeatMessageAsync(IMessageTransmittingFacade messageTransmittingFacade); /// /// Gets the messaging entity type that is used when transmitting the message. /// - MessagingEntityType EntityType + public MessagingEntityType EntityType { get; } @@ -41,7 +41,7 @@ MessagingEntityType EntityType /// /// Gets the regular interval, in seconds, at which the message is transmitted. /// - Int32 IntervalInSeconds + public Int32 IntervalInSeconds { get; } @@ -49,7 +49,7 @@ Int32 IntervalInSeconds /// /// Gets the label, if any, that is associated with the message. /// - String Label + public String Label { get; } @@ -57,7 +57,7 @@ String Label /// /// Gets the type of the associated heartbeat message. /// - Type MessageType + public Type MessageType { get; } diff --git a/src/RapidField.SolidInstruments.Messaging/Service/IMessageListeningProfile.cs b/src/RapidField.SolidInstruments.Messaging/Service/IMessageListeningProfile.cs index 0ac8a114..02080e93 100644 --- a/src/RapidField.SolidInstruments.Messaging/Service/IMessageListeningProfile.cs +++ b/src/RapidField.SolidInstruments.Messaging/Service/IMessageListeningProfile.cs @@ -22,7 +22,7 @@ public interface IMessageListeningProfile /// /// was already added. /// - void AddQueueListener() + public void AddQueueListener() where TMessage : class, IMessage; /// @@ -37,7 +37,7 @@ void AddQueueListener() /// /// was already added. /// - void AddRequestListener() + public void AddRequestListener() where TRequestMessage : class, IRequestMessage where TResponseMessage : class, IResponseMessage; @@ -50,14 +50,14 @@ void AddRequestListener() /// /// was already added. /// - void AddTopicListener() + public void AddTopicListener() where TMessage : class, IMessage; /// /// Gets a collection of message types that are supported by the associated /// . /// - IEnumerable SupportedMessageTypes + public IEnumerable SupportedMessageTypes { get; } diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageQueue.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageQueue.cs index 6104a709..7acec8c1 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageQueue.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageQueue.cs @@ -33,6 +33,6 @@ public interface IMessageQueue : IMessagingEntity /// /// The operation timed out. /// - Task> DequeueAsync(Int32 count); + public Task> DequeueAsync(Int32 count); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageSubscriptionClient.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageSubscriptionClient.cs index f7282136..bcbc3f22 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageSubscriptionClient.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageSubscriptionClient.cs @@ -14,7 +14,7 @@ public interface IMessageSubscriptionClient : IMessagingEntityReceiveClient /// /// Gets the unique name of the associated subscription. /// - String SubscriptionName + public String SubscriptionName { get; } diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTopic.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTopic.cs index 5f67e133..cff04706 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTopic.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTopic.cs @@ -35,7 +35,7 @@ public interface IMessageTopic : IMessagingEntity /// /// The object is disposed. /// - Task CreateSubscriptionAsync(String subscriptionName); + public Task CreateSubscriptionAsync(String subscriptionName); /// /// Asynchronously and non-destructively returns the next available messages from the current , @@ -66,7 +66,7 @@ public interface IMessageTopic : IMessagingEntity /// /// The operation timed out. /// - Task> DequeueAsync(String subscriptionName, Int32 count); + public Task> DequeueAsync(String subscriptionName, Int32 count); /// /// Asynchronously destroys the specified subscription to the current . @@ -89,7 +89,7 @@ public interface IMessageTopic : IMessagingEntity /// /// The object is disposed. /// - Task DestroySubscriptionAsync(String subscriptionName); + public Task DestroySubscriptionAsync(String subscriptionName); /// /// Attempts to create a new subscription to the current . @@ -100,7 +100,7 @@ public interface IMessageTopic : IMessagingEntity /// /// if the subscription was successfully created, otherwise . /// - Boolean TryCreateSubscription(String subscriptionName); + public Boolean TryCreateSubscription(String subscriptionName); /// /// Attempts to destroy the specified subscription to the current . @@ -111,12 +111,12 @@ public interface IMessageTopic : IMessagingEntity /// /// if the subscription was successfully destroyed, otherwise . /// - Boolean TryDestroySubscription(String subscriptionName); + public Boolean TryDestroySubscription(String subscriptionName); /// /// Gets the number of subscriptions to the current . /// - Int32 SubscriptionCount + public Int32 SubscriptionCount { get; } @@ -124,7 +124,7 @@ Int32 SubscriptionCount /// /// Gets the unique names of the subscriptions to the current . /// - IEnumerable SubscriptionNames + public IEnumerable SubscriptionNames { get; } diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransport.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransport.cs index 630bdca0..2dddfa9e 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransport.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransport.cs @@ -24,7 +24,7 @@ public interface IMessageTransport : IInstrument /// /// is . /// - void CloseConnection(IMessageTransportConnection connection); + public void CloseConnection(IMessageTransportConnection connection); /// /// Asynchronously notifies the specified queue that a locked message was not processed and can be made available for @@ -52,7 +52,7 @@ public interface IMessageTransport : IInstrument /// /// The operation timed out. /// - Task ConveyFailureToQueueAsync(MessageLockToken lockToken, IMessagingEntityPath path); + public Task ConveyFailureToQueueAsync(MessageLockToken lockToken, IMessagingEntityPath path); /// /// Asynchronously notifies the specified subscription that a locked message was not processed and can be made available for @@ -80,7 +80,7 @@ public interface IMessageTransport : IInstrument /// /// The operation timed out. /// - Task ConveyFailureToSubscriptionAsync(MessageLockToken lockToken, IMessagingEntityPath path); + public Task ConveyFailureToSubscriptionAsync(MessageLockToken lockToken, IMessagingEntityPath path); /// /// Asynchronously notifies the specified queue that a locked message was processed successfully and can be destroyed @@ -105,7 +105,7 @@ public interface IMessageTransport : IInstrument /// /// The object is disposed. /// - Task ConveySuccessToQueueAsync(MessageLockToken lockToken, IMessagingEntityPath path); + public Task ConveySuccessToQueueAsync(MessageLockToken lockToken, IMessagingEntityPath path); /// /// Asynchronously notifies the specified subscription that a locked message was processed successfully and can be destroyed @@ -130,7 +130,7 @@ public interface IMessageTransport : IInstrument /// /// The object is disposed. /// - Task ConveySuccessToSubscriptionAsync(MessageLockToken lockToken, IMessagingEntityPath path); + public Task ConveySuccessToSubscriptionAsync(MessageLockToken lockToken, IMessagingEntityPath path); /// /// Opens and returns a new to the current . @@ -141,7 +141,7 @@ public interface IMessageTransport : IInstrument /// /// The object is disposed. /// - IMessageTransportConnection CreateConnection(); + public IMessageTransportConnection CreateConnection(); /// /// Asynchronously creates a new queue. @@ -161,7 +161,7 @@ public interface IMessageTransport : IInstrument /// /// An entity with the specified path already exists. /// - Task CreateQueueAsync(IMessagingEntityPath path); + public Task CreateQueueAsync(IMessagingEntityPath path); /// /// Asynchronously creates a new queue. @@ -188,7 +188,7 @@ public interface IMessageTransport : IInstrument /// /// An entity with the specified path already exists. /// - Task CreateQueueAsync(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold); + public Task CreateQueueAsync(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold); /// /// Asynchronously creates a new queue. @@ -220,7 +220,7 @@ public interface IMessageTransport : IInstrument /// /// An entity with the specified path already exists. /// - Task CreateQueueAsync(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold, TimeSpan enqueueTimeoutThreshold); + public Task CreateQueueAsync(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold, TimeSpan enqueueTimeoutThreshold); /// /// Asynchronously creates a new subscription. @@ -247,7 +247,7 @@ public interface IMessageTransport : IInstrument /// /// The specified subscription already exists. /// - Task CreateSubscriptionAsync(IMessagingEntityPath path, String subscriptionName); + public Task CreateSubscriptionAsync(IMessagingEntityPath path, String subscriptionName); /// /// Asynchronously creates a new topic. @@ -267,7 +267,7 @@ public interface IMessageTransport : IInstrument /// /// An entity with the specified path already exists. /// - Task CreateTopicAsync(IMessagingEntityPath path); + public Task CreateTopicAsync(IMessagingEntityPath path); /// /// Asynchronously creates a new topic. @@ -294,7 +294,7 @@ public interface IMessageTransport : IInstrument /// /// An entity with the specified path already exists. /// - Task CreateTopicAsync(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold); + public Task CreateTopicAsync(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold); /// /// Asynchronously creates a new topic. @@ -326,7 +326,7 @@ public interface IMessageTransport : IInstrument /// /// An entity with the specified path already exists. /// - Task CreateTopicAsync(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold, TimeSpan enqueueTimeoutThreshold); + public Task CreateTopicAsync(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold, TimeSpan enqueueTimeoutThreshold); /// /// Asynchronously destroys the specified queue. @@ -346,7 +346,7 @@ public interface IMessageTransport : IInstrument /// /// The specified entity does not exist. /// - Task DestroyQueueAsync(IMessagingEntityPath path); + public Task DestroyQueueAsync(IMessagingEntityPath path); /// /// Asynchronously destroys the specified subscription. @@ -373,7 +373,7 @@ public interface IMessageTransport : IInstrument /// /// The specified subscription does not exist. /// - Task DestroySubscriptionAsync(IMessagingEntityPath path, String subscriptionName); + public Task DestroySubscriptionAsync(IMessagingEntityPath path, String subscriptionName); /// /// Asynchronously destroys the specified topic. @@ -393,7 +393,7 @@ public interface IMessageTransport : IInstrument /// /// The specified entity does not exist. /// - Task DestroyTopicAsync(IMessagingEntityPath path); + public Task DestroyTopicAsync(IMessagingEntityPath path); /// /// Returns a value indicating whether or not the specified queue exists. @@ -410,7 +410,7 @@ public interface IMessageTransport : IInstrument /// /// The object is disposed. /// - Boolean QueueExists(IMessagingEntityPath path); + public Boolean QueueExists(IMessagingEntityPath path); /// /// Asynchronously requests the specified number of messages from the specified queue. @@ -439,7 +439,7 @@ public interface IMessageTransport : IInstrument /// /// The operation timed out. /// - Task> ReceiveFromQueueAsync(IMessagingEntityPath path, Int32 count); + public Task> ReceiveFromQueueAsync(IMessagingEntityPath path, Int32 count); /// /// Asynchronously requests the specified number of messages from the specified topic. @@ -475,7 +475,7 @@ public interface IMessageTransport : IInstrument /// /// The operation timed out. /// - Task> ReceiveFromTopicAsync(IMessagingEntityPath path, String subscriptionName, Int32 count); + public Task> ReceiveFromTopicAsync(IMessagingEntityPath path, String subscriptionName, Int32 count); /// /// Asynchronously sends the specified message to the specified queue. @@ -501,7 +501,7 @@ public interface IMessageTransport : IInstrument /// /// The operation timed out. /// - Task SendToQueueAsync(IMessagingEntityPath path, PrimitiveMessage message); + public Task SendToQueueAsync(IMessagingEntityPath path, PrimitiveMessage message); /// /// Asynchronously sends the specified message to the specified topic. @@ -527,7 +527,7 @@ public interface IMessageTransport : IInstrument /// /// The operation timed out. /// - Task SendToTopicAsync(IMessagingEntityPath path, PrimitiveMessage message); + public Task SendToTopicAsync(IMessagingEntityPath path, PrimitiveMessage message); /// /// Returns a value indicating whether or not the specified subscription exists. @@ -551,7 +551,7 @@ public interface IMessageTransport : IInstrument /// /// The object is disposed. /// - Boolean SubscriptionExists(IMessagingEntityPath path, String subscriptionName); + public Boolean SubscriptionExists(IMessagingEntityPath path, String subscriptionName); /// /// Returns a value indicating whether or not the specified topic exists. @@ -568,7 +568,7 @@ public interface IMessageTransport : IInstrument /// /// The object is disposed. /// - Boolean TopicExists(IMessagingEntityPath path); + public Boolean TopicExists(IMessagingEntityPath path); /// /// Attempts to create a new queue. @@ -579,7 +579,7 @@ public interface IMessageTransport : IInstrument /// /// if the queue was successfully created, otherwise . /// - Boolean TryCreateQueue(IMessagingEntityPath path); + public Boolean TryCreateQueue(IMessagingEntityPath path); /// /// Attempts to create a new queue. @@ -594,7 +594,7 @@ public interface IMessageTransport : IInstrument /// /// if the queue was successfully created, otherwise . /// - Boolean TryCreateQueue(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold); + public Boolean TryCreateQueue(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold); /// /// Attempts to create a new queue. @@ -613,7 +613,7 @@ public interface IMessageTransport : IInstrument /// /// if the queue was successfully created, otherwise . /// - Boolean TryCreateQueue(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold, TimeSpan enqueueTimeoutThreshold); + public Boolean TryCreateQueue(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold, TimeSpan enqueueTimeoutThreshold); /// /// Attempts to create a new subscription. @@ -627,7 +627,7 @@ public interface IMessageTransport : IInstrument /// /// if the subscription was successfully created, otherwise . /// - Boolean TryCreateSubscription(IMessagingEntityPath path, String subscriptionName); + public Boolean TryCreateSubscription(IMessagingEntityPath path, String subscriptionName); /// /// Attempts to create a new topic. @@ -638,7 +638,7 @@ public interface IMessageTransport : IInstrument /// /// if the topic was successfully created, otherwise . /// - Boolean TryCreateTopic(IMessagingEntityPath path); + public Boolean TryCreateTopic(IMessagingEntityPath path); /// /// Attempts to create a new topic. @@ -653,7 +653,7 @@ public interface IMessageTransport : IInstrument /// /// if the topic was successfully created, otherwise . /// - Boolean TryCreateTopic(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold); + public Boolean TryCreateTopic(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold); /// /// Attempts to create a new topic. @@ -672,7 +672,7 @@ public interface IMessageTransport : IInstrument /// /// if the topic was successfully created, otherwise . /// - Boolean TryCreateTopic(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold, TimeSpan enqueueTimeoutThreshold); + public Boolean TryCreateTopic(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold, TimeSpan enqueueTimeoutThreshold); /// /// Attempts to destroy the specified queue. @@ -683,7 +683,7 @@ public interface IMessageTransport : IInstrument /// /// if the queue was successfully destroyed, otherwise . /// - Boolean TryDestroyQueue(IMessagingEntityPath path); + public Boolean TryDestroyQueue(IMessagingEntityPath path); /// /// Attempts to destroy the specified subscription. @@ -697,7 +697,7 @@ public interface IMessageTransport : IInstrument /// /// if the subscription was successfully destroyed, otherwise . /// - Boolean TryDestroySubscription(IMessagingEntityPath path, String subscriptionName); + public Boolean TryDestroySubscription(IMessagingEntityPath path, String subscriptionName); /// /// Attempts to destroy the specified topic. @@ -708,12 +708,12 @@ public interface IMessageTransport : IInstrument /// /// if the topic was successfully destroyed, otherwise . /// - Boolean TryDestroyTopic(IMessagingEntityPath path); + public Boolean TryDestroyTopic(IMessagingEntityPath path); /// /// Gets the number of active connections to the current . /// - Int32 ConnectionCount + public Int32 ConnectionCount { get; } @@ -721,7 +721,7 @@ Int32 ConnectionCount /// /// Gets a collection of active connections to the current . /// - IEnumerable Connections + public IEnumerable Connections { get; } @@ -729,7 +729,7 @@ IEnumerable Connections /// /// Gets the format that is used to serialize enqueued message bodies. /// - SerializationFormat MessageBodySerializationFormat + public SerializationFormat MessageBodySerializationFormat { get; } @@ -737,7 +737,7 @@ SerializationFormat MessageBodySerializationFormat /// /// Gets the number of queues within the current . /// - Int32 QueueCount + public Int32 QueueCount { get; } @@ -745,7 +745,7 @@ Int32 QueueCount /// /// Gets a collection of available queue paths for the current . /// - IEnumerable QueuePaths + public IEnumerable QueuePaths { get; } @@ -753,7 +753,7 @@ IEnumerable QueuePaths /// /// Gets the number of topics within the current . /// - Int32 TopicCount + public Int32 TopicCount { get; } @@ -761,7 +761,7 @@ Int32 TopicCount /// /// Gets a collection of available topic paths for the current . /// - IEnumerable TopicPaths + public IEnumerable TopicPaths { get; } diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransportConnection.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransportConnection.cs index 9ad13553..db2bb941 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransportConnection.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransportConnection.cs @@ -18,7 +18,7 @@ public interface IMessageTransportConnection : IAsyncDisposable, IDisposable /// /// An exception was raised while closing the transport connection. /// - void Close(); + public void Close(); /// /// Registers the specified message handler for the specified queue. @@ -39,7 +39,7 @@ public interface IMessageTransportConnection : IAsyncDisposable, IDisposable /// /// The object is disposed. /// - void RegisterQueueHandler(IMessagingEntityPath queuePath, Action handleMessageAction); + public void RegisterQueueHandler(IMessagingEntityPath queuePath, Action handleMessageAction); /// /// Registers the specified message handler for the specified topic subscription. @@ -66,12 +66,12 @@ public interface IMessageTransportConnection : IAsyncDisposable, IDisposable /// /// The object is disposed. /// - void RegisterSubscriptionHandler(IMessagingEntityPath topicPath, String subscriptionName, Action handleMessageAction); + public void RegisterSubscriptionHandler(IMessagingEntityPath topicPath, String subscriptionName, Action handleMessageAction); /// /// Gets a value that uniquely identifies the current . /// - Guid Identifier + public Guid Identifier { get; } @@ -79,7 +79,7 @@ Guid Identifier /// /// Gets the state of the current . /// - MessageTransportConnectionState State + public MessageTransportConnectionState State { get; } @@ -90,7 +90,7 @@ MessageTransportConnectionState State /// /// The connection is closed. /// - IMessageTransport Transport + public IMessageTransport Transport { get; } diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessagingEntity.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessagingEntity.cs index fdc1bce3..93bf71b7 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessagingEntity.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessagingEntity.cs @@ -36,7 +36,7 @@ public interface IMessagingEntity : IAsyncDisposable, IDisposable /// /// The operation timed out. /// - Task ConveyFailureAsync(MessageLockToken lockToken); + public Task ConveyFailureAsync(MessageLockToken lockToken); /// /// Asynchronously notifies the current that a locked message was processed successfully and @@ -57,7 +57,7 @@ public interface IMessagingEntity : IAsyncDisposable, IDisposable /// /// The object is disposed. /// - Task ConveySuccessAsync(MessageLockToken lockToken); + public Task ConveySuccessAsync(MessageLockToken lockToken); /// /// Asynchronously enqueues the specified message. @@ -77,7 +77,7 @@ public interface IMessagingEntity : IAsyncDisposable, IDisposable /// /// The operation timed out. /// - Task EnqueueAsync(PrimitiveMessage message); + public Task EnqueueAsync(PrimitiveMessage message); /// /// Attempts to set the operational state of the current to @@ -87,7 +87,7 @@ public interface IMessagingEntity : IAsyncDisposable, IDisposable /// /// True if the operational state was successfully set, otherwise false. /// - Boolean TryDisableDequeues(); + public Boolean TryDisableDequeues(); /// /// Attempts to set the operational state of the current to @@ -97,7 +97,7 @@ public interface IMessagingEntity : IAsyncDisposable, IDisposable /// /// True if the operational state was successfully set, otherwise false. /// - Boolean TryDisableEnqueues(); + public Boolean TryDisableEnqueues(); /// /// Attempts to set the operational state of the current to @@ -106,7 +106,7 @@ public interface IMessagingEntity : IAsyncDisposable, IDisposable /// /// True if the operational state was successfully set, otherwise false. /// - Boolean TryPause(); + public Boolean TryPause(); /// /// Attempts to set the operational state of the current to @@ -115,12 +115,12 @@ public interface IMessagingEntity : IAsyncDisposable, IDisposable /// /// True if the operational state was successfully set, otherwise false. /// - Boolean TryResume(); + public Boolean TryResume(); /// /// Gets the maximum length of time to wait for a message to be enqueued before raising an exception. /// - TimeSpan EnqueueTimeoutThreshold + public TimeSpan EnqueueTimeoutThreshold { get; } @@ -128,7 +128,7 @@ TimeSpan EnqueueTimeoutThreshold /// /// Gets the messaging entity type of the current . /// - MessagingEntityType EntityType + public MessagingEntityType EntityType { get; } @@ -136,7 +136,7 @@ MessagingEntityType EntityType /// /// Gets a unique identifier for the current . /// - Guid Identifier + public Guid Identifier { get; } @@ -144,7 +144,7 @@ Guid Identifier /// /// Gets a value indicating whether or not the current is empty. /// - Boolean IsEmpty + public Boolean IsEmpty { get; } @@ -155,7 +155,7 @@ Boolean IsEmpty /// /// The object is disposed. /// - IEnumerable LockTokens + public IEnumerable LockTokens { get; } @@ -163,7 +163,7 @@ IEnumerable LockTokens /// /// Gets the format that is used to serialize enqueued message bodies. /// - SerializationFormat MessageBodySerializationFormat + public SerializationFormat MessageBodySerializationFormat { get; } @@ -171,7 +171,7 @@ SerializationFormat MessageBodySerializationFormat /// /// Gets the number of messages in the current . /// - Int32 MessageCount + public Int32 MessageCount { get; } @@ -180,7 +180,7 @@ Int32 MessageCount /// Gets the length of time that a locked message is held before abandoning the associated token and making the message /// available for processing. /// - TimeSpan MessageLockExpirationThreshold + public TimeSpan MessageLockExpirationThreshold { get; } @@ -188,7 +188,7 @@ TimeSpan MessageLockExpirationThreshold /// /// Gets the operational state of the current . /// - MessagingEntityOperationalState OperationalState + public MessagingEntityOperationalState OperationalState { get; } @@ -196,7 +196,7 @@ MessagingEntityOperationalState OperationalState /// /// Gets a unique textual path that identifies the current . /// - IMessagingEntityPath Path + public IMessagingEntityPath Path { get; } diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessagingEntityClient.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessagingEntityClient.cs index 6db97a2d..ce24aec2 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessagingEntityClient.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessagingEntityClient.cs @@ -12,7 +12,7 @@ public interface IMessagingEntityClient /// /// Gets the client's connection to the associated entity's . /// - IMessageTransportConnection Connection + public IMessageTransportConnection Connection { get; } @@ -20,7 +20,7 @@ IMessageTransportConnection Connection /// /// Gets the entity type of the associated . /// - MessagingEntityType EntityType + public MessagingEntityType EntityType { get; } @@ -28,7 +28,7 @@ MessagingEntityType EntityType /// /// Gets the unique textual path for the messaging entity with which the client transacts. /// - IMessagingEntityPath Path + public IMessagingEntityPath Path { get; } diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessagingEntityReceiveClient.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessagingEntityReceiveClient.cs index ab108b41..c7985fc7 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessagingEntityReceiveClient.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessagingEntityReceiveClient.cs @@ -34,7 +34,7 @@ public interface IMessagingEntityReceiveClient : IMessagingEntityClient /// /// The operation timed out. /// - Task ConveyFailureAsync(MessageLockToken lockToken); + public Task ConveyFailureAsync(MessageLockToken lockToken); /// /// Asynchronously notifies the associated that a locked message was processed successfully @@ -55,7 +55,7 @@ public interface IMessagingEntityReceiveClient : IMessagingEntityClient /// /// The object is disposed. /// - Task ConveySuccessAsync(MessageLockToken lockToken); + public Task ConveySuccessAsync(MessageLockToken lockToken); /// /// Registers the specified message handler for the associated . @@ -69,6 +69,6 @@ public interface IMessagingEntityReceiveClient : IMessagingEntityClient /// /// The connection is closed. /// - void RegisterMessageHandler(Action handleMessageAction); + public void RegisterMessageHandler(Action handleMessageAction); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessagingEntitySendClient.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessagingEntitySendClient.cs index c2898b06..551bfdd8 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessagingEntitySendClient.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessagingEntitySendClient.cs @@ -30,6 +30,6 @@ public interface IMessagingEntitySendClient : IMessagingEntityClient /// /// The connection is closed. /// - Task SendAsync(PrimitiveMessage message); + public Task SendAsync(PrimitiveMessage message); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.ObjectComposition/IFactoryProducedInstanceGroup.cs b/src/RapidField.SolidInstruments.ObjectComposition/IFactoryProducedInstanceGroup.cs index 589dfd55..fac6ce17 100644 --- a/src/RapidField.SolidInstruments.ObjectComposition/IFactoryProducedInstanceGroup.cs +++ b/src/RapidField.SolidInstruments.ObjectComposition/IFactoryProducedInstanceGroup.cs @@ -25,7 +25,7 @@ public interface IFactoryProducedInstanceGroup : IObjectContainer /// /// The object is disposed. /// - Lazy GetLazy() + public Lazy GetLazy() where T : class; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.ObjectComposition/IObjectContainer.cs b/src/RapidField.SolidInstruments.ObjectComposition/IObjectContainer.cs index d779224b..9b5419cd 100644 --- a/src/RapidField.SolidInstruments.ObjectComposition/IObjectContainer.cs +++ b/src/RapidField.SolidInstruments.ObjectComposition/IObjectContainer.cs @@ -31,7 +31,7 @@ public interface IObjectContainer : IInstrument /// /// An exception was raised during object production. /// - T Get() + public T Get() where T : class; /// @@ -56,7 +56,7 @@ T Get() /// /// An exception was raised during object production. /// - T GetNew() + public T GetNew() where T : class; /// @@ -65,7 +65,7 @@ T GetNew() /// /// The object is disposed. /// - IEnumerable InstanceTypes + public IEnumerable InstanceTypes { get; } diff --git a/src/RapidField.SolidInstruments.ObjectComposition/IObjectFactory.cs b/src/RapidField.SolidInstruments.ObjectComposition/IObjectFactory.cs index d6552911..69f71a2b 100644 --- a/src/RapidField.SolidInstruments.ObjectComposition/IObjectFactory.cs +++ b/src/RapidField.SolidInstruments.ObjectComposition/IObjectFactory.cs @@ -33,13 +33,13 @@ public interface IObjectFactory : IObjectFactory /// /// An exception was raised during object production. /// - TProduct Produce() + public TProduct Produce() where TProduct : class, ProductTBase; /// /// Gets the base type from which all produced types derive. /// - Type ProductBaseType + public Type ProductBaseType { get; } @@ -71,7 +71,7 @@ public interface IObjectFactory : IAsyncDisposable, IDisposable /// /// An exception was raised during object production. /// - Object Produce(Type type); + public Object Produce(Type type); /// /// Gets the types that can be produced by the current . @@ -79,7 +79,7 @@ public interface IObjectFactory : IAsyncDisposable, IDisposable /// /// The object is disposed. /// - IEnumerable SupportedProductTypes + public IEnumerable SupportedProductTypes { get; } diff --git a/src/RapidField.SolidInstruments.Serialization/ISerializer.cs b/src/RapidField.SolidInstruments.Serialization/ISerializer.cs index f1af8c80..f1dc36b8 100644 --- a/src/RapidField.SolidInstruments.Serialization/ISerializer.cs +++ b/src/RapidField.SolidInstruments.Serialization/ISerializer.cs @@ -27,7 +27,7 @@ public interface ISerializer : ISerializer /// /// is . /// - T Deserialize(Byte[] buffer); + public T Deserialize(Byte[] buffer); /// /// Converts the specified object to a serialized buffer. @@ -41,7 +41,7 @@ public interface ISerializer : ISerializer /// /// is . /// - Byte[] Serialize(T target); + public Byte[] Serialize(T target); } /// @@ -52,7 +52,7 @@ public interface ISerializer /// /// Gets the format to use for serialization and deserialization. /// - SerializationFormat Format + public SerializationFormat Format { get; } diff --git a/src/RapidField.SolidInstruments.Service/IServiceExecutionLifetime.cs b/src/RapidField.SolidInstruments.Service/IServiceExecutionLifetime.cs index 1bb26860..94166a3a 100644 --- a/src/RapidField.SolidInstruments.Service/IServiceExecutionLifetime.cs +++ b/src/RapidField.SolidInstruments.Service/IServiceExecutionLifetime.cs @@ -17,7 +17,7 @@ public interface IServiceExecutionLifetime : IAsyncDisposable, IDisposable /// /// The object is disposed. /// - void End(); + public void End(); /// /// Blocks the current thread until or is invoked. @@ -28,12 +28,12 @@ public interface IServiceExecutionLifetime : IAsyncDisposable, IDisposable /// /// The object is disposed. /// - void KeepAlive(); + public void KeepAlive(); /// /// Gets a value that indicates whether or not the service is operational. /// - Boolean IsAlive + public Boolean IsAlive { get; } diff --git a/src/RapidField.SolidInstruments.Service/IServiceExecutor.cs b/src/RapidField.SolidInstruments.Service/IServiceExecutor.cs index 984b3ebf..a45d402b 100644 --- a/src/RapidField.SolidInstruments.Service/IServiceExecutor.cs +++ b/src/RapidField.SolidInstruments.Service/IServiceExecutor.cs @@ -18,12 +18,12 @@ public interface IServiceExecutor : IInstrument /// /// An exception was raised during execution of the service. /// - void Execute(); + public void Execute(); /// /// Gets the name of the service. /// - String ServiceName + public String ServiceName { get; } diff --git a/src/RapidField.SolidInstruments.SignalProcessing/IChannel.cs b/src/RapidField.SolidInstruments.SignalProcessing/IChannel.cs index 5dea4cdc..2d06a9cd 100644 --- a/src/RapidField.SolidInstruments.SignalProcessing/IChannel.cs +++ b/src/RapidField.SolidInstruments.SignalProcessing/IChannel.cs @@ -32,7 +32,7 @@ public interface IChannel : IChannel /// /// An exception was raised while performing the read operation, or the channel was unavailable. /// - T this[Int32 index] + public T this[Int32 index] { get; } @@ -55,7 +55,7 @@ T this[Int32 index] /// /// An exception was raised while performing the read operation, or the channel was unavailable. /// - Task> ReadAsync(Int32 index); + public Task> ReadAsync(Int32 index); /// /// Asynchronously reads a sample from the channel's output stream at the specified index. @@ -80,7 +80,7 @@ T this[Int32 index] /// /// An exception was raised while performing the read operation, or the channel was unavailable. /// - Task> ReadAsync(Int32 index, Int32 lookBehindLength); + public Task> ReadAsync(Int32 index, Int32 lookBehindLength); /// /// Asynchronously reads a sample from the channel's output stream at the specified index. @@ -109,7 +109,7 @@ T this[Int32 index] /// /// An exception was raised while performing the read operation, or the channel was unavailable. /// - Task> ReadAsync(Int32 index, Int32 lookBehindLength, Int32 lookAheadLength); + public Task> ReadAsync(Int32 index, Int32 lookBehindLength, Int32 lookAheadLength); /// /// Returns a discrete unit that represents a silent or empty signal. @@ -117,7 +117,7 @@ T this[Int32 index] /// /// A discrete unit that represents a silent or empty signal. /// - T ReadSilence(); + public T ReadSilence(); /// /// Returns a discrete unit that represents a silent or empty signal. @@ -132,12 +132,12 @@ T this[Int32 index] /// is equal to and /// is less than zero. /// - IDiscreteUnitOfOutput ReadSilence(Int32 index); + public IDiscreteUnitOfOutput ReadSilence(Int32 index); /// /// Gets the behavior of the channel when an out-of-range read request is made. /// - InvalidReadBehavior InvalidReadBehavior + public InvalidReadBehavior InvalidReadBehavior { get; } @@ -156,12 +156,12 @@ public interface IChannel : IInstrument /// /// The channel's status is equal to . /// - void Toggle(); + public void Toggle(); /// /// Gets a unique identifier for the current . /// - Guid Identifier + public Guid Identifier { get; } @@ -169,7 +169,7 @@ Guid Identifier /// /// Gets the name of the current . /// - String Name + public String Name { get; } @@ -178,7 +178,7 @@ String Name /// Gets the number of discrete units in the output stream for the current , or -1 if the length is /// not fixed. /// - Int32 OutputLength + public Int32 OutputLength { get; } @@ -186,7 +186,7 @@ Int32 OutputLength /// /// Gets a value indicating whether or not the output stream for the current has a fixed length. /// - Boolean OutputLengthIsFixed + public Boolean OutputLengthIsFixed { get; } @@ -194,7 +194,7 @@ Boolean OutputLengthIsFixed /// /// Gets the type of a discrete unit of output value for the current . /// - Type OutputType + public Type OutputType { get; } @@ -202,7 +202,7 @@ Type OutputType /// /// Gets the status of the current . /// - ChannelStatus Status + public ChannelStatus Status { get; } diff --git a/src/RapidField.SolidInstruments.SignalProcessing/IChannelCollection.cs b/src/RapidField.SolidInstruments.SignalProcessing/IChannelCollection.cs index 2d273241..7b42ba11 100644 --- a/src/RapidField.SolidInstruments.SignalProcessing/IChannelCollection.cs +++ b/src/RapidField.SolidInstruments.SignalProcessing/IChannelCollection.cs @@ -79,7 +79,7 @@ public interface IChannelCollection /// Gets the first channel in the collection. /// - TChannelA ChannelA + public TChannelA ChannelA { get; } @@ -87,7 +87,7 @@ TChannelA ChannelA /// /// Gets the second channel in the collection. /// - TChannelB ChannelB + public TChannelB ChannelB { get; } @@ -95,7 +95,7 @@ TChannelB ChannelB /// /// Gets the third channel in the collection. /// - TChannelC ChannelC + public TChannelC ChannelC { get; } @@ -103,7 +103,7 @@ TChannelC ChannelC /// /// Gets the fourth channel in the collection. /// - TChannelD ChannelD + public TChannelD ChannelD { get; } @@ -111,7 +111,7 @@ TChannelD ChannelD /// /// Gets the fifth channel in the collection. /// - TChannelE ChannelE + public TChannelE ChannelE { get; } @@ -119,7 +119,7 @@ TChannelE ChannelE /// /// Gets the sixth channel in the collection. /// - TChannelF ChannelF + public TChannelF ChannelF { get; } @@ -127,7 +127,7 @@ TChannelF ChannelF /// /// Gets the seventh channel in the collection. /// - TChannelG ChannelG + public TChannelG ChannelG { get; } @@ -135,7 +135,7 @@ TChannelG ChannelG /// /// Gets the eighth channel in the collection. /// - TChannelH ChannelH + public TChannelH ChannelH { get; } @@ -143,7 +143,7 @@ TChannelH ChannelH /// /// Gets the ninth channel in the collection. /// - TChannelI ChannelI + public TChannelI ChannelI { get; } @@ -151,7 +151,7 @@ TChannelI ChannelI /// /// Gets the tenth channel in the collection. /// - TChannelJ ChannelJ + public TChannelJ ChannelJ { get; } @@ -159,7 +159,7 @@ TChannelJ ChannelJ /// /// Gets the eleventh channel in the collection. /// - TChannelK ChannelK + public TChannelK ChannelK { get; } @@ -167,7 +167,7 @@ TChannelK ChannelK /// /// Gets the twelfth channel in the collection. /// - TChannelL ChannelL + public TChannelL ChannelL { get; } @@ -175,7 +175,7 @@ TChannelL ChannelL /// /// Gets the thirteenth channel in the collection. /// - TChannelM ChannelM + public TChannelM ChannelM { get; } @@ -183,7 +183,7 @@ TChannelM ChannelM /// /// Gets the fourteenth channel in the collection. /// - TChannelN ChannelN + public TChannelN ChannelN { get; } @@ -191,7 +191,7 @@ TChannelN ChannelN /// /// Gets the fifteenth channel in the collection. /// - TChannelO ChannelO + public TChannelO ChannelO { get; } @@ -199,7 +199,7 @@ TChannelO ChannelO /// /// Gets the sixteenth channel in the collection. /// - TChannelP ChannelP + public TChannelP ChannelP { get; } @@ -273,7 +273,7 @@ public interface IChannelCollection /// Gets the first channel in the collection. /// - TChannelA ChannelA + public TChannelA ChannelA { get; } @@ -281,7 +281,7 @@ TChannelA ChannelA /// /// Gets the second channel in the collection. /// - TChannelB ChannelB + public TChannelB ChannelB { get; } @@ -289,7 +289,7 @@ TChannelB ChannelB /// /// Gets the third channel in the collection. /// - TChannelC ChannelC + public TChannelC ChannelC { get; } @@ -297,7 +297,7 @@ TChannelC ChannelC /// /// Gets the fourth channel in the collection. /// - TChannelD ChannelD + public TChannelD ChannelD { get; } @@ -305,7 +305,7 @@ TChannelD ChannelD /// /// Gets the fifth channel in the collection. /// - TChannelE ChannelE + public TChannelE ChannelE { get; } @@ -313,7 +313,7 @@ TChannelE ChannelE /// /// Gets the sixth channel in the collection. /// - TChannelF ChannelF + public TChannelF ChannelF { get; } @@ -321,7 +321,7 @@ TChannelF ChannelF /// /// Gets the seventh channel in the collection. /// - TChannelG ChannelG + public TChannelG ChannelG { get; } @@ -329,7 +329,7 @@ TChannelG ChannelG /// /// Gets the eighth channel in the collection. /// - TChannelH ChannelH + public TChannelH ChannelH { get; } @@ -337,7 +337,7 @@ TChannelH ChannelH /// /// Gets the ninth channel in the collection. /// - TChannelI ChannelI + public TChannelI ChannelI { get; } @@ -345,7 +345,7 @@ TChannelI ChannelI /// /// Gets the tenth channel in the collection. /// - TChannelJ ChannelJ + public TChannelJ ChannelJ { get; } @@ -353,7 +353,7 @@ TChannelJ ChannelJ /// /// Gets the eleventh channel in the collection. /// - TChannelK ChannelK + public TChannelK ChannelK { get; } @@ -361,7 +361,7 @@ TChannelK ChannelK /// /// Gets the twelfth channel in the collection. /// - TChannelL ChannelL + public TChannelL ChannelL { get; } @@ -369,7 +369,7 @@ TChannelL ChannelL /// /// Gets the thirteenth channel in the collection. /// - TChannelM ChannelM + public TChannelM ChannelM { get; } @@ -377,7 +377,7 @@ TChannelM ChannelM /// /// Gets the fourteenth channel in the collection. /// - TChannelN ChannelN + public TChannelN ChannelN { get; } @@ -385,7 +385,7 @@ TChannelN ChannelN /// /// Gets the fifteenth channel in the collection. /// - TChannelO ChannelO + public TChannelO ChannelO { get; } @@ -455,7 +455,7 @@ public interface IChannelCollection /// Gets the first channel in the collection. /// - TChannelA ChannelA + public TChannelA ChannelA { get; } @@ -463,7 +463,7 @@ TChannelA ChannelA /// /// Gets the second channel in the collection. /// - TChannelB ChannelB + public TChannelB ChannelB { get; } @@ -471,7 +471,7 @@ TChannelB ChannelB /// /// Gets the third channel in the collection. /// - TChannelC ChannelC + public TChannelC ChannelC { get; } @@ -479,7 +479,7 @@ TChannelC ChannelC /// /// Gets the fourth channel in the collection. /// - TChannelD ChannelD + public TChannelD ChannelD { get; } @@ -487,7 +487,7 @@ TChannelD ChannelD /// /// Gets the fifth channel in the collection. /// - TChannelE ChannelE + public TChannelE ChannelE { get; } @@ -495,7 +495,7 @@ TChannelE ChannelE /// /// Gets the sixth channel in the collection. /// - TChannelF ChannelF + public TChannelF ChannelF { get; } @@ -503,7 +503,7 @@ TChannelF ChannelF /// /// Gets the seventh channel in the collection. /// - TChannelG ChannelG + public TChannelG ChannelG { get; } @@ -511,7 +511,7 @@ TChannelG ChannelG /// /// Gets the eighth channel in the collection. /// - TChannelH ChannelH + public TChannelH ChannelH { get; } @@ -519,7 +519,7 @@ TChannelH ChannelH /// /// Gets the ninth channel in the collection. /// - TChannelI ChannelI + public TChannelI ChannelI { get; } @@ -527,7 +527,7 @@ TChannelI ChannelI /// /// Gets the tenth channel in the collection. /// - TChannelJ ChannelJ + public TChannelJ ChannelJ { get; } @@ -535,7 +535,7 @@ TChannelJ ChannelJ /// /// Gets the eleventh channel in the collection. /// - TChannelK ChannelK + public TChannelK ChannelK { get; } @@ -543,7 +543,7 @@ TChannelK ChannelK /// /// Gets the twelfth channel in the collection. /// - TChannelL ChannelL + public TChannelL ChannelL { get; } @@ -551,7 +551,7 @@ TChannelL ChannelL /// /// Gets the thirteenth channel in the collection. /// - TChannelM ChannelM + public TChannelM ChannelM { get; } @@ -559,7 +559,7 @@ TChannelM ChannelM /// /// Gets the fourteenth channel in the collection. /// - TChannelN ChannelN + public TChannelN ChannelN { get; } @@ -625,7 +625,7 @@ public interface IChannelCollection /// Gets the first channel in the collection. /// - TChannelA ChannelA + public TChannelA ChannelA { get; } @@ -633,7 +633,7 @@ TChannelA ChannelA /// /// Gets the second channel in the collection. /// - TChannelB ChannelB + public TChannelB ChannelB { get; } @@ -641,7 +641,7 @@ TChannelB ChannelB /// /// Gets the third channel in the collection. /// - TChannelC ChannelC + public TChannelC ChannelC { get; } @@ -649,7 +649,7 @@ TChannelC ChannelC /// /// Gets the fourth channel in the collection. /// - TChannelD ChannelD + public TChannelD ChannelD { get; } @@ -657,7 +657,7 @@ TChannelD ChannelD /// /// Gets the fifth channel in the collection. /// - TChannelE ChannelE + public TChannelE ChannelE { get; } @@ -665,7 +665,7 @@ TChannelE ChannelE /// /// Gets the sixth channel in the collection. /// - TChannelF ChannelF + public TChannelF ChannelF { get; } @@ -673,7 +673,7 @@ TChannelF ChannelF /// /// Gets the seventh channel in the collection. /// - TChannelG ChannelG + public TChannelG ChannelG { get; } @@ -681,7 +681,7 @@ TChannelG ChannelG /// /// Gets the eighth channel in the collection. /// - TChannelH ChannelH + public TChannelH ChannelH { get; } @@ -689,7 +689,7 @@ TChannelH ChannelH /// /// Gets the ninth channel in the collection. /// - TChannelI ChannelI + public TChannelI ChannelI { get; } @@ -697,7 +697,7 @@ TChannelI ChannelI /// /// Gets the tenth channel in the collection. /// - TChannelJ ChannelJ + public TChannelJ ChannelJ { get; } @@ -705,7 +705,7 @@ TChannelJ ChannelJ /// /// Gets the eleventh channel in the collection. /// - TChannelK ChannelK + public TChannelK ChannelK { get; } @@ -713,7 +713,7 @@ TChannelK ChannelK /// /// Gets the twelfth channel in the collection. /// - TChannelL ChannelL + public TChannelL ChannelL { get; } @@ -721,7 +721,7 @@ TChannelL ChannelL /// /// Gets the thirteenth channel in the collection. /// - TChannelM ChannelM + public TChannelM ChannelM { get; } @@ -783,7 +783,7 @@ public interface IChannelCollection /// Gets the first channel in the collection. /// - TChannelA ChannelA + public TChannelA ChannelA { get; } @@ -791,7 +791,7 @@ TChannelA ChannelA /// /// Gets the second channel in the collection. /// - TChannelB ChannelB + public TChannelB ChannelB { get; } @@ -799,7 +799,7 @@ TChannelB ChannelB /// /// Gets the third channel in the collection. /// - TChannelC ChannelC + public TChannelC ChannelC { get; } @@ -807,7 +807,7 @@ TChannelC ChannelC /// /// Gets the fourth channel in the collection. /// - TChannelD ChannelD + public TChannelD ChannelD { get; } @@ -815,7 +815,7 @@ TChannelD ChannelD /// /// Gets the fifth channel in the collection. /// - TChannelE ChannelE + public TChannelE ChannelE { get; } @@ -823,7 +823,7 @@ TChannelE ChannelE /// /// Gets the sixth channel in the collection. /// - TChannelF ChannelF + public TChannelF ChannelF { get; } @@ -831,7 +831,7 @@ TChannelF ChannelF /// /// Gets the seventh channel in the collection. /// - TChannelG ChannelG + public TChannelG ChannelG { get; } @@ -839,7 +839,7 @@ TChannelG ChannelG /// /// Gets the eighth channel in the collection. /// - TChannelH ChannelH + public TChannelH ChannelH { get; } @@ -847,7 +847,7 @@ TChannelH ChannelH /// /// Gets the ninth channel in the collection. /// - TChannelI ChannelI + public TChannelI ChannelI { get; } @@ -855,7 +855,7 @@ TChannelI ChannelI /// /// Gets the tenth channel in the collection. /// - TChannelJ ChannelJ + public TChannelJ ChannelJ { get; } @@ -863,7 +863,7 @@ TChannelJ ChannelJ /// /// Gets the eleventh channel in the collection. /// - TChannelK ChannelK + public TChannelK ChannelK { get; } @@ -871,7 +871,7 @@ TChannelK ChannelK /// /// Gets the twelfth channel in the collection. /// - TChannelL ChannelL + public TChannelL ChannelL { get; } @@ -929,7 +929,7 @@ public interface IChannelCollection /// Gets the first channel in the collection. /// - TChannelA ChannelA + public TChannelA ChannelA { get; } @@ -937,7 +937,7 @@ TChannelA ChannelA /// /// Gets the second channel in the collection. /// - TChannelB ChannelB + public TChannelB ChannelB { get; } @@ -945,7 +945,7 @@ TChannelB ChannelB /// /// Gets the third channel in the collection. /// - TChannelC ChannelC + public TChannelC ChannelC { get; } @@ -953,7 +953,7 @@ TChannelC ChannelC /// /// Gets the fourth channel in the collection. /// - TChannelD ChannelD + public TChannelD ChannelD { get; } @@ -961,7 +961,7 @@ TChannelD ChannelD /// /// Gets the fifth channel in the collection. /// - TChannelE ChannelE + public TChannelE ChannelE { get; } @@ -969,7 +969,7 @@ TChannelE ChannelE /// /// Gets the sixth channel in the collection. /// - TChannelF ChannelF + public TChannelF ChannelF { get; } @@ -977,7 +977,7 @@ TChannelF ChannelF /// /// Gets the seventh channel in the collection. /// - TChannelG ChannelG + public TChannelG ChannelG { get; } @@ -985,7 +985,7 @@ TChannelG ChannelG /// /// Gets the eighth channel in the collection. /// - TChannelH ChannelH + public TChannelH ChannelH { get; } @@ -993,7 +993,7 @@ TChannelH ChannelH /// /// Gets the ninth channel in the collection. /// - TChannelI ChannelI + public TChannelI ChannelI { get; } @@ -1001,7 +1001,7 @@ TChannelI ChannelI /// /// Gets the tenth channel in the collection. /// - TChannelJ ChannelJ + public TChannelJ ChannelJ { get; } @@ -1009,7 +1009,7 @@ TChannelJ ChannelJ /// /// Gets the eleventh channel in the collection. /// - TChannelK ChannelK + public TChannelK ChannelK { get; } @@ -1063,7 +1063,7 @@ public interface IChannelCollection /// Gets the first channel in the collection. /// - TChannelA ChannelA + public TChannelA ChannelA { get; } @@ -1071,7 +1071,7 @@ TChannelA ChannelA /// /// Gets the second channel in the collection. /// - TChannelB ChannelB + public TChannelB ChannelB { get; } @@ -1079,7 +1079,7 @@ TChannelB ChannelB /// /// Gets the third channel in the collection. /// - TChannelC ChannelC + public TChannelC ChannelC { get; } @@ -1087,7 +1087,7 @@ TChannelC ChannelC /// /// Gets the fourth channel in the collection. /// - TChannelD ChannelD + public TChannelD ChannelD { get; } @@ -1095,7 +1095,7 @@ TChannelD ChannelD /// /// Gets the fifth channel in the collection. /// - TChannelE ChannelE + public TChannelE ChannelE { get; } @@ -1103,7 +1103,7 @@ TChannelE ChannelE /// /// Gets the sixth channel in the collection. /// - TChannelF ChannelF + public TChannelF ChannelF { get; } @@ -1111,7 +1111,7 @@ TChannelF ChannelF /// /// Gets the seventh channel in the collection. /// - TChannelG ChannelG + public TChannelG ChannelG { get; } @@ -1119,7 +1119,7 @@ TChannelG ChannelG /// /// Gets the eighth channel in the collection. /// - TChannelH ChannelH + public TChannelH ChannelH { get; } @@ -1127,7 +1127,7 @@ TChannelH ChannelH /// /// Gets the ninth channel in the collection. /// - TChannelI ChannelI + public TChannelI ChannelI { get; } @@ -1135,7 +1135,7 @@ TChannelI ChannelI /// /// Gets the tenth channel in the collection. /// - TChannelJ ChannelJ + public TChannelJ ChannelJ { get; } @@ -1185,7 +1185,7 @@ public interface IChannelCollection /// Gets the first channel in the collection. /// - TChannelA ChannelA + public TChannelA ChannelA { get; } @@ -1193,7 +1193,7 @@ TChannelA ChannelA /// /// Gets the second channel in the collection. /// - TChannelB ChannelB + public TChannelB ChannelB { get; } @@ -1201,7 +1201,7 @@ TChannelB ChannelB /// /// Gets the third channel in the collection. /// - TChannelC ChannelC + public TChannelC ChannelC { get; } @@ -1209,7 +1209,7 @@ TChannelC ChannelC /// /// Gets the fourth channel in the collection. /// - TChannelD ChannelD + public TChannelD ChannelD { get; } @@ -1217,7 +1217,7 @@ TChannelD ChannelD /// /// Gets the fifth channel in the collection. /// - TChannelE ChannelE + public TChannelE ChannelE { get; } @@ -1225,7 +1225,7 @@ TChannelE ChannelE /// /// Gets the sixth channel in the collection. /// - TChannelF ChannelF + public TChannelF ChannelF { get; } @@ -1233,7 +1233,7 @@ TChannelF ChannelF /// /// Gets the seventh channel in the collection. /// - TChannelG ChannelG + public TChannelG ChannelG { get; } @@ -1241,7 +1241,7 @@ TChannelG ChannelG /// /// Gets the eighth channel in the collection. /// - TChannelH ChannelH + public TChannelH ChannelH { get; } @@ -1249,7 +1249,7 @@ TChannelH ChannelH /// /// Gets the ninth channel in the collection. /// - TChannelI ChannelI + public TChannelI ChannelI { get; } @@ -1295,7 +1295,7 @@ public interface IChannelCollection /// Gets the first channel in the collection. /// - TChannelA ChannelA + public TChannelA ChannelA { get; } @@ -1303,7 +1303,7 @@ TChannelA ChannelA /// /// Gets the second channel in the collection. /// - TChannelB ChannelB + public TChannelB ChannelB { get; } @@ -1311,7 +1311,7 @@ TChannelB ChannelB /// /// Gets the third channel in the collection. /// - TChannelC ChannelC + public TChannelC ChannelC { get; } @@ -1319,7 +1319,7 @@ TChannelC ChannelC /// /// Gets the fourth channel in the collection. /// - TChannelD ChannelD + public TChannelD ChannelD { get; } @@ -1327,7 +1327,7 @@ TChannelD ChannelD /// /// Gets the fifth channel in the collection. /// - TChannelE ChannelE + public TChannelE ChannelE { get; } @@ -1335,7 +1335,7 @@ TChannelE ChannelE /// /// Gets the sixth channel in the collection. /// - TChannelF ChannelF + public TChannelF ChannelF { get; } @@ -1343,7 +1343,7 @@ TChannelF ChannelF /// /// Gets the seventh channel in the collection. /// - TChannelG ChannelG + public TChannelG ChannelG { get; } @@ -1351,7 +1351,7 @@ TChannelG ChannelG /// /// Gets the eighth channel in the collection. /// - TChannelH ChannelH + public TChannelH ChannelH { get; } @@ -1393,7 +1393,7 @@ public interface IChannelCollection /// Gets the first channel in the collection. /// - TChannelA ChannelA + public TChannelA ChannelA { get; } @@ -1401,7 +1401,7 @@ TChannelA ChannelA /// /// Gets the second channel in the collection. /// - TChannelB ChannelB + public TChannelB ChannelB { get; } @@ -1409,7 +1409,7 @@ TChannelB ChannelB /// /// Gets the third channel in the collection. /// - TChannelC ChannelC + public TChannelC ChannelC { get; } @@ -1417,7 +1417,7 @@ TChannelC ChannelC /// /// Gets the fourth channel in the collection. /// - TChannelD ChannelD + public TChannelD ChannelD { get; } @@ -1425,7 +1425,7 @@ TChannelD ChannelD /// /// Gets the fifth channel in the collection. /// - TChannelE ChannelE + public TChannelE ChannelE { get; } @@ -1433,7 +1433,7 @@ TChannelE ChannelE /// /// Gets the sixth channel in the collection. /// - TChannelF ChannelF + public TChannelF ChannelF { get; } @@ -1441,7 +1441,7 @@ TChannelF ChannelF /// /// Gets the seventh channel in the collection. /// - TChannelG ChannelG + public TChannelG ChannelG { get; } @@ -1479,7 +1479,7 @@ public interface IChannelCollection /// Gets the first channel in the collection. /// - TChannelA ChannelA + public TChannelA ChannelA { get; } @@ -1487,7 +1487,7 @@ TChannelA ChannelA /// /// Gets the second channel in the collection. /// - TChannelB ChannelB + public TChannelB ChannelB { get; } @@ -1495,7 +1495,7 @@ TChannelB ChannelB /// /// Gets the third channel in the collection. /// - TChannelC ChannelC + public TChannelC ChannelC { get; } @@ -1503,7 +1503,7 @@ TChannelC ChannelC /// /// Gets the fourth channel in the collection. /// - TChannelD ChannelD + public TChannelD ChannelD { get; } @@ -1511,7 +1511,7 @@ TChannelD ChannelD /// /// Gets the fifth channel in the collection. /// - TChannelE ChannelE + public TChannelE ChannelE { get; } @@ -1519,7 +1519,7 @@ TChannelE ChannelE /// /// Gets the sixth channel in the collection. /// - TChannelF ChannelF + public TChannelF ChannelF { get; } @@ -1553,7 +1553,7 @@ public interface IChannelCollection /// Gets the first channel in the collection. /// - TChannelA ChannelA + public TChannelA ChannelA { get; } @@ -1561,7 +1561,7 @@ TChannelA ChannelA /// /// Gets the second channel in the collection. /// - TChannelB ChannelB + public TChannelB ChannelB { get; } @@ -1569,7 +1569,7 @@ TChannelB ChannelB /// /// Gets the third channel in the collection. /// - TChannelC ChannelC + public TChannelC ChannelC { get; } @@ -1577,7 +1577,7 @@ TChannelC ChannelC /// /// Gets the fourth channel in the collection. /// - TChannelD ChannelD + public TChannelD ChannelD { get; } @@ -1585,7 +1585,7 @@ TChannelD ChannelD /// /// Gets the fifth channel in the collection. /// - TChannelE ChannelE + public TChannelE ChannelE { get; } @@ -1615,7 +1615,7 @@ public interface IChannelCollection /// /// Gets the first channel in the collection. /// - TChannelA ChannelA + public TChannelA ChannelA { get; } @@ -1623,7 +1623,7 @@ TChannelA ChannelA /// /// Gets the second channel in the collection. /// - TChannelB ChannelB + public TChannelB ChannelB { get; } @@ -1631,7 +1631,7 @@ TChannelB ChannelB /// /// Gets the third channel in the collection. /// - TChannelC ChannelC + public TChannelC ChannelC { get; } @@ -1639,7 +1639,7 @@ TChannelC ChannelC /// /// Gets the fourth channel in the collection. /// - TChannelD ChannelD + public TChannelD ChannelD { get; } @@ -1665,7 +1665,7 @@ public interface IChannelCollection : IChannelC /// /// Gets the first channel in the collection. /// - TChannelA ChannelA + public TChannelA ChannelA { get; } @@ -1673,7 +1673,7 @@ TChannelA ChannelA /// /// Gets the second channel in the collection. /// - TChannelB ChannelB + public TChannelB ChannelB { get; } @@ -1681,7 +1681,7 @@ TChannelB ChannelB /// /// Gets the third channel in the collection. /// - TChannelC ChannelC + public TChannelC ChannelC { get; } @@ -1703,7 +1703,7 @@ public interface IChannelCollection : IChannelCollection /// /// Gets the first channel in the collection. /// - TChannelA ChannelA + public TChannelA ChannelA { get; } @@ -1711,7 +1711,7 @@ TChannelA ChannelA /// /// Gets the second channel in the collection. /// - TChannelB ChannelB + public TChannelB ChannelB { get; } @@ -1729,7 +1729,7 @@ public interface IChannelCollection : IChannelCollection /// /// Gets the first channel in the collection. /// - TChannelA ChannelA + public TChannelA ChannelA { get; } @@ -1743,7 +1743,7 @@ public interface IChannelCollection : IReadOnlyCollection /// /// Gets a unique identifier for the current . /// - Guid Identifier + public Guid Identifier { get; } diff --git a/src/RapidField.SolidInstruments.SignalProcessing/IDiscreteUnitOfOutput.cs b/src/RapidField.SolidInstruments.SignalProcessing/IDiscreteUnitOfOutput.cs index 474da8b7..7101232f 100644 --- a/src/RapidField.SolidInstruments.SignalProcessing/IDiscreteUnitOfOutput.cs +++ b/src/RapidField.SolidInstruments.SignalProcessing/IDiscreteUnitOfOutput.cs @@ -18,7 +18,7 @@ public interface IDiscreteUnitOfOutput /// Gets the zero-based index for the current within the associated channel's output /// stream. /// - public Int32 ChannelReadIndex + public public Int32 ChannelReadIndex { get; } @@ -26,7 +26,7 @@ public Int32 ChannelReadIndex /// /// Gets the output value. /// - public T Value + public public T Value { get; } diff --git a/src/RapidField.SolidInstruments.SignalProcessing/ISignalProcessor.cs b/src/RapidField.SolidInstruments.SignalProcessing/ISignalProcessor.cs index 7848f29b..f54bc577 100644 --- a/src/RapidField.SolidInstruments.SignalProcessing/ISignalProcessor.cs +++ b/src/RapidField.SolidInstruments.SignalProcessing/ISignalProcessor.cs @@ -19,7 +19,7 @@ public interface ISignalProcessor : IChannel, ISign /// /// Gets the operational settings for the current . /// - TSettings Settings + public TSettings Settings { get; } @@ -33,7 +33,7 @@ public interface ISignalProcessor : IChannel /// /// Gets the input channels for the current . /// - IChannelCollection InputChannels + public IChannelCollection InputChannels { get; } diff --git a/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/DuplexSemaphoreControlTests.cs b/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/DuplexSemaphoreControlTests.cs index 00a10b0c..0dabcdc9 100644 --- a/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/DuplexSemaphoreControlTests.cs +++ b/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/DuplexSemaphoreControlTests.cs @@ -27,7 +27,7 @@ public void OperationLatency_ShouldBeLow() { // Arrange. var mode = ConcurrencyControlMode.DuplexSemaphore; - var latencyThresholdInTicks = 987; + var latencyThresholdInTicks = 2584; // Assert. ConcurrencyControlTests.OperationLatency_ShouldBeLow(mode, PerformUsingPrimitive, latencyThresholdInTicks); diff --git a/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/ProcessorCountSemaphoreControlTests.cs b/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/ProcessorCountSemaphoreControlTests.cs index cbb5e6cd..0819e587 100644 --- a/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/ProcessorCountSemaphoreControlTests.cs +++ b/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/ProcessorCountSemaphoreControlTests.cs @@ -27,7 +27,7 @@ public void OperationLatency_ShouldBeLow() { // Arrange. var mode = ConcurrencyControlMode.ProcessorCountSemaphore; - var latencyThresholdInTicks = 987; + var latencyThresholdInTicks = 2584; // Assert. ConcurrencyControlTests.OperationLatency_ShouldBeLow(mode, PerformUsingPrimitive, latencyThresholdInTicks); diff --git a/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/SingleThreadLockControlTests.cs b/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/SingleThreadLockControlTests.cs index c2a5aafb..fc85d7d7 100644 --- a/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/SingleThreadLockControlTests.cs +++ b/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/SingleThreadLockControlTests.cs @@ -27,7 +27,7 @@ public void OperationLatency_ShouldBeLow() { // Arrange. var mode = ConcurrencyControlMode.SingleThreadLock; - var latencyThresholdInTicks = 987; + var latencyThresholdInTicks = 2584; // Assert. ConcurrencyControlTests.OperationLatency_ShouldBeLow(mode, PerformUsingPrimitive, latencyThresholdInTicks); diff --git a/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/SingleThreadSpinLockControlTests.cs b/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/SingleThreadSpinLockControlTests.cs index 9c86c6d0..f15de863 100644 --- a/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/SingleThreadSpinLockControlTests.cs +++ b/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/SingleThreadSpinLockControlTests.cs @@ -27,7 +27,7 @@ public void OperationLatency_ShouldBeLow() { // Arrange. var mode = ConcurrencyControlMode.SingleThreadSpinLock; - var latencyThresholdInTicks = 610; + var latencyThresholdInTicks = 987; // Assert. ConcurrencyControlTests.OperationLatency_ShouldBeLow(mode, PerformUsingPrimitive, latencyThresholdInTicks); diff --git a/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/UnconstrainedControlTests.cs b/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/UnconstrainedControlTests.cs index e874a48f..dad56afe 100644 --- a/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/UnconstrainedControlTests.cs +++ b/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/UnconstrainedControlTests.cs @@ -26,7 +26,7 @@ public void OperationLatency_ShouldBeLow() { // Arrange. var mode = ConcurrencyControlMode.Unconstrained; - var latencyThresholdInTicks = 610; + var latencyThresholdInTicks = 987; // Assert. ConcurrencyControlTests.OperationLatency_ShouldBeLow(mode, PerformUsingPrimitive, latencyThresholdInTicks); diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/CustomerOrder.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/CustomerOrder.cs index c72d7810..f0e92aab 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/CustomerOrder.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/CustomerOrder.cs @@ -2,6 +2,7 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= +using RapidField.SolidInstruments.Core; using RapidField.SolidInstruments.Core.Domain; using System; using System.Collections.Generic; @@ -26,6 +27,39 @@ public CustomerOrder() Products = new List(); } + /// + /// Initializes a new instance of the class. + /// + /// + /// A value that uniquely identifies the model. The default value is equal to the default instance of . + /// + /// + /// The customer that placed the order. + /// + public CustomerOrder(Guid identifier, Customer customer) + : this(identifier, customer, TimeStamp.Current) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A value that uniquely identifies the model. The default value is equal to the default instance of . + /// + /// + /// The customer that placed the order. + /// + /// + /// The date and time when the order was placed. + /// + public CustomerOrder(Guid identifier, Customer customer, DateTime placementTimeStamp) + : this(identifier, customer, placementTimeStamp, null) + { + return; + } + /// /// Initializes a new instance of the class. /// From bd53722f643fedbb68e9aad6b476109367d847bf Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Sat, 11 Apr 2020 09:59:50 -0500 Subject: [PATCH 26/55] Fixing a copy paste mistake. --- .../IDiscreteUnitOfOutput.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/RapidField.SolidInstruments.SignalProcessing/IDiscreteUnitOfOutput.cs b/src/RapidField.SolidInstruments.SignalProcessing/IDiscreteUnitOfOutput.cs index 7101232f..474da8b7 100644 --- a/src/RapidField.SolidInstruments.SignalProcessing/IDiscreteUnitOfOutput.cs +++ b/src/RapidField.SolidInstruments.SignalProcessing/IDiscreteUnitOfOutput.cs @@ -18,7 +18,7 @@ public interface IDiscreteUnitOfOutput /// Gets the zero-based index for the current within the associated channel's output /// stream. /// - public public Int32 ChannelReadIndex + public Int32 ChannelReadIndex { get; } @@ -26,7 +26,7 @@ public public Int32 ChannelReadIndex /// /// Gets the output value. /// - public public T Value + public T Value { get; } From aa0785767f434572c58573338c06dc488c7f3009 Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Wed, 20 May 2020 09:21:37 -0500 Subject: [PATCH 27/55] Ramping up for testing of in-memory messaging testing. --- .editorconfig | 6 + .../Commands/AddFibonacciNumberCommand.cs | 6 +- .../GetFibonacciNumberValuesCommand.cs | 2 + .../appsettings.json | 2 +- .../BinaryTreeNode.cs | 4 +- .../Command.cs | 7 +- .../DomainModelCommandClassification.cs | 47 +++++++ .../Domain/GlobalIdentityDomainModel.cs | 2 +- .../IGlobalIdentityAggregateDomainModel.cs | 16 +++ .../Domain/IGlobalIdentityValueDomainModel.cs | 15 ++ .../INumericIdentityAggregateDomainModel.cs | 16 +++ .../INumericIdentityValueDomainModel.cs | 15 ++ .../ISemanticIdentityAggregateDomainModel.cs | 16 +++ .../ISemanticIdentityValueDomainModel.cs | 15 ++ .../Domain/NumericIdentityDomainModel.cs | 2 +- .../Domain/SemanticIdentityDomainModel.cs | 2 +- .../Extensions/EnumExtensions.cs | 27 ++++ .../IModel.cs | 2 +- .../CryptographicTransform.cs | 6 +- .../Symmetric/Aes/AesCipher.cs | 2 +- .../Symmetric/SymmetricKey.cs | 8 +- .../DataAccessCommand.cs | 11 +- .../DataAccessCommandHandler.cs | 16 ++- .../DomainEvent.cs | 20 ++- .../DomainModelAssociatedEvent.cs | 92 +++++++++++++ .../DomainModelCreatedEvent.cs | 92 +++++++++++++ .../DomainModelDeletedEvent.cs | 92 +++++++++++++ .../DomainModelEvent.cs | 44 +++++- .../DomainModelEventClassification.cs | 8 +- .../DomainModelUpdatedEvent.cs | 92 +++++++++++++ .../GeneralInformationEvent.cs | 39 +++++- .../IDomainModelAssociatedEvent.cs | 3 + .../IDomainModelCreatedEvent.cs | 3 + .../IDomainModelDeletedEvent.cs | 3 + .../IDomainModelEvent.cs | 12 ++ .../IDomainModelUpdatedEvent.cs | 3 + .../AzureServiceBusListeningFacade.cs | 2 +- .../DomainModelAssociatedEventMessage.cs | 100 ++++++++++++++ .../DomainModelCreatedEventMessage.cs | 100 ++++++++++++++ .../DomainModelDeletedEventMessage.cs | 100 ++++++++++++++ .../EventMessages/DomainModelEventMessage.cs | 106 ++++++++++++++ .../DomainModelUpdatedEventMessage.cs | 100 ++++++++++++++ .../IDomainModelAssociatedEventMessage.cs | 24 ++++ .../IDomainModelCreatedEventMessage.cs | 24 ++++ .../IDomainModelDeletedEventMessage.cs | 24 ++++ .../EventMessages/IDomainModelEventMessage.cs | 31 +++++ .../IDomainModelUpdatedEventMessage.cs | 24 ++++ .../MessageListeningFacade.cs | 4 +- .../SimualtedCommandWithResult.cs | 4 + .../SimulatedCommand.cs | 4 + .../DomainEventTests.cs | 91 ++++++++++++ .../GeneralInformationEventTests.cs | 96 +++++++++++++ .../Customer/CreateDomainModelCommand.cs | 13 ++ .../Customer/DomainModelCreatedEvent.cs | 85 ++++++++++++ .../Customer/DomainModelDeletedEvent.cs | 85 ++++++++++++ .../Customer/DomainModelUpdatedEvent.cs | 85 ++++++++++++ .../CustomerOrder/DomainModelCreatedEvent.cs | 85 ++++++++++++ .../CustomerOrder/DomainModelDeletedEvent.cs | 85 ++++++++++++ .../CustomerOrder/DomainModelUpdatedEvent.cs | 85 ++++++++++++ .../Product/DomainModelCreatedEvent.cs | 85 ++++++++++++ .../Product/DomainModelDeletedEvent.cs | 85 ++++++++++++ .../Product/DomainModelUpdatedEvent.cs | 85 ++++++++++++ .../DomainModelCreatedEventMessage.cs | 64 +++++++++ .../DomainModelDeletedEventMessage.cs | 64 +++++++++ .../DomainModelUpdatedEventMessage.cs | 64 +++++++++ .../DomainModelCreatedEventMessage.cs | 64 +++++++++ .../DomainModelDeletedEventMessage.cs | 64 +++++++++ .../DomainModelUpdatedEventMessage.cs | 64 +++++++++ .../Product/DomainModelCreatedEventMessage.cs | 64 +++++++++ .../Product/DomainModelDeletedEventMessage.cs | 64 +++++++++ .../Product/DomainModelUpdatedEventMessage.cs | 64 +++++++++ .../Models/Customer.cs | 51 ------- .../Models/Customer/DomainModel.cs | 104 ++++++++++++++ .../Models/Customer/IAggregate.cs | 31 +++++ .../Models/Customer/IValue.cs | 23 ++++ .../Models/CustomerOrder.cs | 116 ---------------- .../Models/CustomerOrder/DomainModel.cs | 116 ++++++++++++++++ .../Models/CustomerOrder/IAggregate.cs | 51 +++++++ .../Models/CustomerOrder/IValue.cs | 23 ++++ .../Models/Product.cs | 81 ----------- .../Models/Product/DomainModel.cs | 129 ++++++++++++++++++ .../Models/Product/IAggregate.cs | 43 ++++++ .../Models/Product/IValue.cs | 31 +++++ ...uments.Messaging.InMemory.UnitTests.csproj | 9 +- 84 files changed, 3458 insertions(+), 286 deletions(-) create mode 100644 src/RapidField.SolidInstruments.Command/DomainModelCommandClassification.cs create mode 100644 src/RapidField.SolidInstruments.Core/Domain/IGlobalIdentityAggregateDomainModel.cs create mode 100644 src/RapidField.SolidInstruments.Core/Domain/IGlobalIdentityValueDomainModel.cs create mode 100644 src/RapidField.SolidInstruments.Core/Domain/INumericIdentityAggregateDomainModel.cs create mode 100644 src/RapidField.SolidInstruments.Core/Domain/INumericIdentityValueDomainModel.cs create mode 100644 src/RapidField.SolidInstruments.Core/Domain/ISemanticIdentityAggregateDomainModel.cs create mode 100644 src/RapidField.SolidInstruments.Core/Domain/ISemanticIdentityValueDomainModel.cs create mode 100644 src/RapidField.SolidInstruments.Core/Extensions/EnumExtensions.cs create mode 100644 src/RapidField.SolidInstruments.EventAuthoring/DomainModelAssociatedEvent.cs create mode 100644 src/RapidField.SolidInstruments.EventAuthoring/DomainModelCreatedEvent.cs create mode 100644 src/RapidField.SolidInstruments.EventAuthoring/DomainModelDeletedEvent.cs create mode 100644 src/RapidField.SolidInstruments.EventAuthoring/DomainModelUpdatedEvent.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelAssociatedEventMessage.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelCreatedEventMessage.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelDeletedEventMessage.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelEventMessage.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelUpdatedEventMessage.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/EventMessages/IDomainModelAssociatedEventMessage.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/EventMessages/IDomainModelCreatedEventMessage.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/EventMessages/IDomainModelDeletedEventMessage.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/EventMessages/IDomainModelEventMessage.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/EventMessages/IDomainModelUpdatedEventMessage.cs create mode 100644 test/RapidField.SolidInstruments.EventAuthoring.UnitTests/DomainEventTests.cs create mode 100644 test/RapidField.SolidInstruments.EventAuthoring.UnitTests/GeneralInformationEventTests.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Customer/CreateDomainModelCommand.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Customer/DomainModelCreatedEvent.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Customer/DomainModelDeletedEvent.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Customer/DomainModelUpdatedEvent.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/CustomerOrder/DomainModelCreatedEvent.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/CustomerOrder/DomainModelDeletedEvent.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/CustomerOrder/DomainModelUpdatedEvent.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Product/DomainModelCreatedEvent.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Product/DomainModelDeletedEvent.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Product/DomainModelUpdatedEvent.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Customer/DomainModelCreatedEventMessage.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Customer/DomainModelDeletedEventMessage.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Customer/DomainModelUpdatedEventMessage.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/CustomerOrder/DomainModelCreatedEventMessage.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/CustomerOrder/DomainModelDeletedEventMessage.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/CustomerOrder/DomainModelUpdatedEventMessage.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Product/DomainModelCreatedEventMessage.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Product/DomainModelDeletedEventMessage.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Product/DomainModelUpdatedEventMessage.cs delete mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Customer.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Customer/DomainModel.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Customer/IAggregate.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Customer/IValue.cs delete mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/CustomerOrder.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/CustomerOrder/DomainModel.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/CustomerOrder/IAggregate.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/CustomerOrder/IValue.cs delete mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Product.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Product/DomainModel.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Product/IAggregate.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Product/IValue.cs diff --git a/.editorconfig b/.editorconfig index d79dbddb..233ee712 100644 --- a/.editorconfig +++ b/.editorconfig @@ -2,6 +2,12 @@ # Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. # ================================================================================================================================= +# All files +[*] + +# Editor guidelines +guidelines = 132 1.5px dashed 24608b4e + # C# files [*.cs] diff --git a/example/RapidField.SolidInstruments.Example.DatabaseModel/Commands/AddFibonacciNumberCommand.cs b/example/RapidField.SolidInstruments.Example.DatabaseModel/Commands/AddFibonacciNumberCommand.cs index c379667f..29673dd5 100644 --- a/example/RapidField.SolidInstruments.Example.DatabaseModel/Commands/AddFibonacciNumberCommand.cs +++ b/example/RapidField.SolidInstruments.Example.DatabaseModel/Commands/AddFibonacciNumberCommand.cs @@ -5,12 +5,14 @@ using RapidField.SolidInstruments.DataAccess; using System; using System.Diagnostics; +using System.Runtime.Serialization; namespace RapidField.SolidInstruments.Example.DatabaseModel.Commands { /// /// Represents a data access command that adds a specified numeric value to the Fibonacci series. /// + [DataContract] public sealed class AddFibonacciNumberCommand : DataAccessCommand { /// @@ -27,11 +29,13 @@ internal AddFibonacciNumberCommand(Int64 numberValue) } /// - /// Gets the value of the Fibonacci number that is added to the series. + /// Gets or sets the value of the Fibonacci number that is added to the series. /// + [DataMember] public Int64 NumberValue { get; + set; } } } \ No newline at end of file diff --git a/example/RapidField.SolidInstruments.Example.DatabaseModel/Commands/GetFibonacciNumberValuesCommand.cs b/example/RapidField.SolidInstruments.Example.DatabaseModel/Commands/GetFibonacciNumberValuesCommand.cs index aac45ad2..3340341e 100644 --- a/example/RapidField.SolidInstruments.Example.DatabaseModel/Commands/GetFibonacciNumberValuesCommand.cs +++ b/example/RapidField.SolidInstruments.Example.DatabaseModel/Commands/GetFibonacciNumberValuesCommand.cs @@ -6,12 +6,14 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Runtime.Serialization; namespace RapidField.SolidInstruments.Example.DatabaseModel.Commands { /// /// Represents a data access command that returns the numeric values for the Fibonacci series. /// + [DataContract] public sealed class GetFibonacciNumberValuesCommand : DataAccessCommand> { /// diff --git a/example/RapidField.SolidInstruments.Example.ServiceApplication/appsettings.json b/example/RapidField.SolidInstruments.Example.ServiceApplication/appsettings.json index 53f27a4b..a247b242 100644 --- a/example/RapidField.SolidInstruments.Example.ServiceApplication/appsettings.json +++ b/example/RapidField.SolidInstruments.Example.ServiceApplication/appsettings.json @@ -4,7 +4,7 @@ { "ConnectionStrings": { - "SolidInstrumentsServiceBusDev": "" // Intentionally blank. Replace when testing. Do not commit. + "SolidInstrumentsServiceBusDev": "" // IMPORTANT Intentionally blank. Replace when testing. Do not commit. }, "Logging": { "IncludeScopes": false, diff --git a/src/RapidField.SolidInstruments.Collections/BinaryTreeNode.cs b/src/RapidField.SolidInstruments.Collections/BinaryTreeNode.cs index 5e97accd..dff7978e 100644 --- a/src/RapidField.SolidInstruments.Collections/BinaryTreeNode.cs +++ b/src/RapidField.SolidInstruments.Collections/BinaryTreeNode.cs @@ -13,12 +13,12 @@ namespace RapidField.SolidInstruments.Collections /// Represents a node in a binary tree structure. /// /// - /// is the default implementation of . + /// is the default implementation of . /// /// /// The value type of the node. /// - public class BinaryTreeNode : TreeNode>, ITreeNode> + public class BinaryTreeNode : TreeNode>, IBinaryTreeNode { /// /// Initializes a new instance of the class. diff --git a/src/RapidField.SolidInstruments.Command/Command.cs b/src/RapidField.SolidInstruments.Command/Command.cs index 6ef3993f..15eebcef 100644 --- a/src/RapidField.SolidInstruments.Command/Command.cs +++ b/src/RapidField.SolidInstruments.Command/Command.cs @@ -5,6 +5,7 @@ using RapidField.SolidInstruments.Core; using System; using System.Diagnostics; +using System.Runtime.Serialization; namespace RapidField.SolidInstruments.Command { @@ -17,6 +18,7 @@ namespace RapidField.SolidInstruments.Command /// /// The type of the result that is emitted when processing the command. /// + [DataContract] public abstract class Command : ICommand { /// @@ -38,6 +40,7 @@ protected Command() /// /// Gets the type of the result that is emitted when processing the command. /// + [IgnoreDataMember] public Type ResultType => ResultTypeReference; /// @@ -53,6 +56,7 @@ protected Command() /// /// is the default implementation of . /// + [DataContract] public abstract class Command : ICommand { /// @@ -74,6 +78,7 @@ protected Command() /// /// Gets the type of the result that is emitted when processing the command. /// - public Type ResultType => Nix.Type; + [IgnoreDataMember] + public virtual Type ResultType => Nix.Type; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/DomainModelCommandClassification.cs b/src/RapidField.SolidInstruments.Command/DomainModelCommandClassification.cs new file mode 100644 index 00000000..31808817 --- /dev/null +++ b/src/RapidField.SolidInstruments.Command/DomainModelCommandClassification.cs @@ -0,0 +1,47 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; +using System; +using System.Runtime.Serialization; + +namespace RapidField.SolidInstruments.Command +{ + /// + /// Specifies a classification that describes the effect of a command upon an . + /// + [DataContract] + public enum DomainModelCommandClassification : Int32 + { + /// + /// The domain model command classification is not specified. + /// + [EnumMember] + Unspecified = 0, + + /// + /// The command relates to the model, but does not otherwise cause a change to its characteristics. + /// + [EnumMember] + Associated = 1, + + /// + /// The command causes the model to be created. + /// + [EnumMember] + Created = 2, + + /// + /// The command causes the model to be deleted. + /// + [EnumMember] + Deleted = 3, + + /// + /// The command causes the model to be updated. + /// + [EnumMember] + Updated = 4 + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Core/Domain/GlobalIdentityDomainModel.cs b/src/RapidField.SolidInstruments.Core/Domain/GlobalIdentityDomainModel.cs index 62ad48ff..d868b96a 100644 --- a/src/RapidField.SolidInstruments.Core/Domain/GlobalIdentityDomainModel.cs +++ b/src/RapidField.SolidInstruments.Core/Domain/GlobalIdentityDomainModel.cs @@ -14,7 +14,7 @@ namespace RapidField.SolidInstruments.Core.Domain /// is the default implementation of . /// [DataContract] - public abstract class GlobalIdentityDomainModel : GlobalIdentityModel, IGlobalIdentityDomainModel + public abstract class GlobalIdentityDomainModel : GlobalIdentityModel, IGlobalIdentityAggregateDomainModel { /// /// Initializes a new instance of the class. diff --git a/src/RapidField.SolidInstruments.Core/Domain/IGlobalIdentityAggregateDomainModel.cs b/src/RapidField.SolidInstruments.Core/Domain/IGlobalIdentityAggregateDomainModel.cs new file mode 100644 index 00000000..0246d200 --- /dev/null +++ b/src/RapidField.SolidInstruments.Core/Domain/IGlobalIdentityAggregateDomainModel.cs @@ -0,0 +1,16 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.Core.Domain +{ + /// + /// Represents an object that models an aggregate domain construct and that is identified primarily by a + /// value. + /// + public interface IGlobalIdentityAggregateDomainModel : IGlobalIdentityDomainModel, IAggregateDomainModel + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Core/Domain/IGlobalIdentityValueDomainModel.cs b/src/RapidField.SolidInstruments.Core/Domain/IGlobalIdentityValueDomainModel.cs new file mode 100644 index 00000000..510f90b7 --- /dev/null +++ b/src/RapidField.SolidInstruments.Core/Domain/IGlobalIdentityValueDomainModel.cs @@ -0,0 +1,15 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.Core.Domain +{ + /// + /// Represents an object that models a value domain construct and that is identified primarily by a value. + /// + public interface IGlobalIdentityValueDomainModel : IGlobalIdentityDomainModel, IValueDomainModel + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Core/Domain/INumericIdentityAggregateDomainModel.cs b/src/RapidField.SolidInstruments.Core/Domain/INumericIdentityAggregateDomainModel.cs new file mode 100644 index 00000000..60061ff9 --- /dev/null +++ b/src/RapidField.SolidInstruments.Core/Domain/INumericIdentityAggregateDomainModel.cs @@ -0,0 +1,16 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.Core.Domain +{ + /// + /// Represents an object that models an aggregate domain construct and that is identified primarily by an + /// value. + /// + public interface INumericIdentityAggregateDomainModel : INumericIdentityDomainModel, IAggregateDomainModel + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Core/Domain/INumericIdentityValueDomainModel.cs b/src/RapidField.SolidInstruments.Core/Domain/INumericIdentityValueDomainModel.cs new file mode 100644 index 00000000..66591952 --- /dev/null +++ b/src/RapidField.SolidInstruments.Core/Domain/INumericIdentityValueDomainModel.cs @@ -0,0 +1,15 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.Core.Domain +{ + /// + /// Represents an object that models a value domain construct and that is identified primarily by an value. + /// + public interface INumericIdentityValueDomainModel : INumericIdentityDomainModel, IValueDomainModel + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Core/Domain/ISemanticIdentityAggregateDomainModel.cs b/src/RapidField.SolidInstruments.Core/Domain/ISemanticIdentityAggregateDomainModel.cs new file mode 100644 index 00000000..ed018193 --- /dev/null +++ b/src/RapidField.SolidInstruments.Core/Domain/ISemanticIdentityAggregateDomainModel.cs @@ -0,0 +1,16 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.Core.Domain +{ + /// + /// Represents an object that models an aggregate domain construct and that is identified primarily by a + /// value. + /// + public interface ISemanticIdentityAggregateDomainModel : ISemanticIdentityDomainModel, IAggregateDomainModel + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Core/Domain/ISemanticIdentityValueDomainModel.cs b/src/RapidField.SolidInstruments.Core/Domain/ISemanticIdentityValueDomainModel.cs new file mode 100644 index 00000000..d3a38422 --- /dev/null +++ b/src/RapidField.SolidInstruments.Core/Domain/ISemanticIdentityValueDomainModel.cs @@ -0,0 +1,15 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.Core.Domain +{ + /// + /// Represents an object that models a value domain construct and that is identified primarily by a value. + /// + public interface ISemanticIdentityValueDomainModel : ISemanticIdentityDomainModel, IValueDomainModel + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Core/Domain/NumericIdentityDomainModel.cs b/src/RapidField.SolidInstruments.Core/Domain/NumericIdentityDomainModel.cs index a3b08928..b1c6733e 100644 --- a/src/RapidField.SolidInstruments.Core/Domain/NumericIdentityDomainModel.cs +++ b/src/RapidField.SolidInstruments.Core/Domain/NumericIdentityDomainModel.cs @@ -14,7 +14,7 @@ namespace RapidField.SolidInstruments.Core.Domain /// is the default implementation of . /// [DataContract] - public abstract class NumericIdentityDomainModel : NumericIdentityModel, INumericIdentityDomainModel + public abstract class NumericIdentityDomainModel : NumericIdentityModel, INumericIdentityAggregateDomainModel { /// /// Initializes a new instance of the class. diff --git a/src/RapidField.SolidInstruments.Core/Domain/SemanticIdentityDomainModel.cs b/src/RapidField.SolidInstruments.Core/Domain/SemanticIdentityDomainModel.cs index ef35e7bb..5094a457 100644 --- a/src/RapidField.SolidInstruments.Core/Domain/SemanticIdentityDomainModel.cs +++ b/src/RapidField.SolidInstruments.Core/Domain/SemanticIdentityDomainModel.cs @@ -14,7 +14,7 @@ namespace RapidField.SolidInstruments.Core.Domain /// is the default implementation of . /// [DataContract] - public abstract class SemanticIdentityDomainModel : SemanticIdentityModel, ISemanticIdentityDomainModel + public abstract class SemanticIdentityDomainModel : SemanticIdentityModel, ISemanticIdentityAggregateDomainModel { /// /// Initializes a new instance of the class. diff --git a/src/RapidField.SolidInstruments.Core/Extensions/EnumExtensions.cs b/src/RapidField.SolidInstruments.Core/Extensions/EnumExtensions.cs new file mode 100644 index 00000000..a05ca71b --- /dev/null +++ b/src/RapidField.SolidInstruments.Core/Extensions/EnumExtensions.cs @@ -0,0 +1,27 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Collections.Generic; + +namespace RapidField.SolidInstruments.Core.Extensions +{ + /// + /// Extends types with general purpose features. + /// + public static class EnumExtensions + { + /// + /// Converts the value of the current to its equivalent integer and textual name pair. + /// + /// + /// The current instance of the . + /// + /// + /// An integer and textual name pair representation of the current . + /// + public static KeyValuePair ToKeyValuePair(this T target) + where T : Enum => new KeyValuePair(Convert.ToInt32(target), Enum.GetName(typeof(T), target)); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Core/IModel.cs b/src/RapidField.SolidInstruments.Core/IModel.cs index 42cbd15a..037b6510 100644 --- a/src/RapidField.SolidInstruments.Core/IModel.cs +++ b/src/RapidField.SolidInstruments.Core/IModel.cs @@ -16,7 +16,7 @@ public interface IModel : IComparable>, IModel where TIdentifier : IComparable, IComparable, IEquatable { /// - /// Gets or sets a value that uniquely identifies the current . + /// Gets a value that uniquely identifies the current . /// public TIdentifier Identifier { diff --git a/src/RapidField.SolidInstruments.Cryptography/CryptographicTransform.cs b/src/RapidField.SolidInstruments.Cryptography/CryptographicTransform.cs index 4bd9c9b1..4d5479d2 100644 --- a/src/RapidField.SolidInstruments.Cryptography/CryptographicTransform.cs +++ b/src/RapidField.SolidInstruments.Cryptography/CryptographicTransform.cs @@ -183,7 +183,7 @@ private static Byte[] ConditionallyApplyPadding(Byte[] block, Int32 blockSizeInB blockAsByteList.Add(Convert.ToByte(paddingLengthInBytes)); break; - case PaddingModePcks7: + case PaddingModePkcs7: while (blockAsByteList.Count < blockSizeInBytes) { @@ -261,7 +261,7 @@ private static Byte[] ConditionallyRemovePadding(Byte[] block, Int32 blockSizeIn break; - case PaddingModePcks7: + case PaddingModePkcs7: for (var i = startPosition; i >= 0; i--) { @@ -450,7 +450,7 @@ protected PaddingMode PaddingMode /// Represents the padding mode. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - internal const PaddingMode PaddingModePcks7 = PaddingMode.PKCS7; + internal const PaddingMode PaddingModePkcs7 = PaddingMode.PKCS7; /// /// Represents the padding mode. diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/Aes/AesCipher.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/Aes/AesCipher.cs index 1351e086..766305a1 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Symmetric/Aes/AesCipher.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Symmetric/Aes/AesCipher.cs @@ -63,6 +63,6 @@ protected AesCipher(Int32 keySize, CipherMode mode, RandomNumberGenerator random /// Represents the padding setting for the cipher. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const PaddingMode PaddingModeSetting = CryptographicTransform.PaddingModePcks7; + private const PaddingMode PaddingModeSetting = CryptographicTransform.PaddingModePkcs7; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKey.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKey.cs index 99d8f27e..aa8b9e90 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKey.cs @@ -696,7 +696,8 @@ public SymmetricAlgorithmSpecification Algorithm /// /// The author acknowledges that obscurity does not ensure security. Encrypting sensitive information with a known key does /// not secure it. This is intended to stand up a barrier against unsophisticated attacks targeting users who have - /// mistakenly exposed their key source. + /// mistakenly exposed their key source. This sequence is fairly arbitrary and can be modified if needed, but there are + /// probably few good reasons to. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] private static readonly PinnedBuffer BufferEncryptionKey = new PinnedBuffer(new Byte[] @@ -714,9 +715,14 @@ public SymmetricAlgorithmSpecification Algorithm /// Represents substitution bytes that are used to fulfill key derivation operations when is /// equal to . /// + /// + /// This sequence is deliberately and carefully balanced. Modifications can introduce severe security flaws and break the + /// class functionally. Do not modify without very careful consideration and extremely good reason. + /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] private static readonly Byte[] SubstitutionBox = { + // IMPORTANT Read the remarks above. Inform senior members of the team if a pull request introduces changes here. 0xff, 0xf0, 0xfd, 0xf2, 0xfb, 0xf4, 0xf9, 0xf6, 0xf7, 0xf8, 0xf5, 0xfa, 0xf3, 0xfc, 0xf1, 0xfe, 0x0f, 0x00, 0x0d, 0x02, 0x0b, 0x04, 0x09, 0x06, 0x07, 0x08, 0x05, 0x0a, 0x03, 0x0c, 0x01, 0x0e, 0x4f, 0x40, 0x4d, 0x42, 0x4b, 0x44, 0x49, 0x46, 0x47, 0x48, 0x45, 0x4a, 0x43, 0x4c, 0x41, 0x4e, diff --git a/src/RapidField.SolidInstruments.DataAccess/DataAccessCommand.cs b/src/RapidField.SolidInstruments.DataAccess/DataAccessCommand.cs index 1202e688..300b3a8e 100644 --- a/src/RapidField.SolidInstruments.DataAccess/DataAccessCommand.cs +++ b/src/RapidField.SolidInstruments.DataAccess/DataAccessCommand.cs @@ -5,6 +5,8 @@ using RapidField.SolidInstruments.Core; using System; using System.Diagnostics; +using System.Runtime.Serialization; +using CommandNamespace = RapidField.SolidInstruments.Command; namespace RapidField.SolidInstruments.DataAccess { @@ -17,6 +19,7 @@ namespace RapidField.SolidInstruments.DataAccess /// /// The type of the result that is produced by handling the data access command. /// + [DataContract] public abstract class DataAccessCommand : DataAccessCommand, IDataAccessCommand { /// @@ -31,6 +34,7 @@ protected DataAccessCommand() /// /// Gets the type of the result that is produced by handling the data access command. /// + [IgnoreDataMember] public sealed override Type ResultType => ResultTypeReference; /// @@ -46,12 +50,14 @@ protected DataAccessCommand() /// /// is the default implementation of . /// - public abstract class DataAccessCommand : IDataAccessCommand + [DataContract] + public abstract class DataAccessCommand : CommandNamespace.Command, IDataAccessCommand { /// /// Initializes a new instance of the class. /// protected DataAccessCommand() + : base() { return; } @@ -59,6 +65,7 @@ protected DataAccessCommand() /// /// Gets the type of the result that is produced by processing the data access command. /// - public virtual Type ResultType => Nix.Type; + [IgnoreDataMember] + public override Type ResultType => Nix.Type; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess/DataAccessCommandHandler.cs b/src/RapidField.SolidInstruments.DataAccess/DataAccessCommandHandler.cs index 1d6d74f7..a0cd8c5f 100644 --- a/src/RapidField.SolidInstruments.DataAccess/DataAccessCommandHandler.cs +++ b/src/RapidField.SolidInstruments.DataAccess/DataAccessCommandHandler.cs @@ -64,9 +64,11 @@ protected override void Dispose(Boolean disposing) { Repositories.Dispose(); - // Don't remove this. Although the transaction object is injected and, therefore, should normally be managed by - // a consuming class, the handler manages the complete life cycle of the transaction. Further, derived classes - // may initialize transactions in their constructors and thereby rely on this class to manage them. + /* IMPORTANT + * Don't remove this. Although the transaction object is injected and, therefore, should normally be managed by + * a consuming class, the handler manages the complete life cycle of the transaction. Further, derived classes + * may initialize transactions in their constructors and thereby rely on this class to manage them. + */ Transaction.Dispose(); } } @@ -205,9 +207,11 @@ protected override void Dispose(Boolean disposing) { Repositories.Dispose(); - // Don't remove this. Although the transaction object is injected and, therefore, should normally be managed by - // a consuming class, the handler manages the complete life cycle of the transaction. Further, derived classes - // may initialize transactions in their constructors and thereby rely on this class to manage them. + /* IMPORTANT + * Don't remove this. Although the transaction object is injected and, therefore, should normally be managed by + * a consuming class, the handler manages the complete life cycle of the transaction. Further, derived classes + * may initialize transactions in their constructors and thereby rely on this class to manage them. + */ Transaction.Dispose(); } } diff --git a/src/RapidField.SolidInstruments.EventAuthoring/DomainEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/DomainEvent.cs index 833afd85..9b62f8c4 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/DomainEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/DomainEvent.cs @@ -113,7 +113,7 @@ public DomainEvent(IEnumerable labels, EventVerbosity verbosity, String public DomainEvent(IEnumerable labels, EventVerbosity verbosity, String description, DateTime timeStamp) : base(StaticCategory, verbosity, description, timeStamp) { - Labels = new List(labels.RejectIf().IsNull(nameof(labels)).TargetArgument); + LabelsReference = new List((labels.RejectIf().IsNull(nameof(labels)).TargetArgument)); } /// @@ -123,7 +123,16 @@ public DomainEvent(IEnumerable labels, EventVerbosity verbosity, String [DataMember] public ICollection Labels { - get; + get + { + if (LabelsReference is null) + { + // This is necessary to accommodate specific serialization scenarios. + LabelsReference = new List(); + } + + return LabelsReference; + } } /// @@ -131,5 +140,12 @@ public ICollection Labels /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] private const EventCategory StaticCategory = EventCategory.Domain; + + /// + /// Represents a collection of textual labels that provide categorical and/or contextual information about the current + /// . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private ICollection LabelsReference; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring/DomainModelAssociatedEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/DomainModelAssociatedEvent.cs new file mode 100644 index 00000000..2d68497d --- /dev/null +++ b/src/RapidField.SolidInstruments.EventAuthoring/DomainModelAssociatedEvent.cs @@ -0,0 +1,92 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace RapidField.SolidInstruments.EventAuthoring +{ + /// + /// Represents information about an event related to an object that models a domain construct. + /// + /// + /// is the default implementation of + /// . + /// + /// + /// The type of the associated domain model. + /// + [DataContract] + public class DomainModelAssociatedEvent : DomainModelEvent, IDomainModelAssociatedEvent + where TModel : class, IDomainModel + { + /// + /// Initializes a new instance of the class. + /// + public DomainModelAssociatedEvent() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// is . + /// + public DomainModelAssociatedEvent(TModel model) + : this(model, Array.Empty()) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// is -or- is . + /// + public DomainModelAssociatedEvent(TModel model, IEnumerable labels) + : this(model, labels, DefaultVerbosity) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public DomainModelAssociatedEvent(TModel model, IEnumerable labels, EventVerbosity verbosity) + : base(model, DomainModelEventClassification.Associated, labels, verbosity) + { + return; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring/DomainModelCreatedEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/DomainModelCreatedEvent.cs new file mode 100644 index 00000000..36e9d63f --- /dev/null +++ b/src/RapidField.SolidInstruments.EventAuthoring/DomainModelCreatedEvent.cs @@ -0,0 +1,92 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace RapidField.SolidInstruments.EventAuthoring +{ + /// + /// Represents information about the creation of an object that models a domain construct. + /// + /// + /// is the default implementation of + /// . + /// + /// + /// The type of the associated domain model. + /// + [DataContract] + public class DomainModelCreatedEvent : DomainModelEvent, IDomainModelCreatedEvent + where TModel : class, IDomainModel + { + /// + /// Initializes a new instance of the class. + /// + public DomainModelCreatedEvent() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// is . + /// + public DomainModelCreatedEvent(TModel model) + : this(model, Array.Empty()) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// is -or- is . + /// + public DomainModelCreatedEvent(TModel model, IEnumerable labels) + : this(model, labels, DefaultVerbosity) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public DomainModelCreatedEvent(TModel model, IEnumerable labels, EventVerbosity verbosity) + : base(model, DomainModelEventClassification.Created, labels, verbosity) + { + return; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring/DomainModelDeletedEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/DomainModelDeletedEvent.cs new file mode 100644 index 00000000..afb09f7b --- /dev/null +++ b/src/RapidField.SolidInstruments.EventAuthoring/DomainModelDeletedEvent.cs @@ -0,0 +1,92 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace RapidField.SolidInstruments.EventAuthoring +{ + /// + /// Represents information about the deletion of an object that models a domain construct. + /// + /// + /// is the default implementation of + /// . + /// + /// + /// The type of the associated domain model. + /// + [DataContract] + public class DomainModelDeletedEvent : DomainModelEvent, IDomainModelDeletedEvent + where TModel : class, IDomainModel + { + /// + /// Initializes a new instance of the class. + /// + public DomainModelDeletedEvent() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// is . + /// + public DomainModelDeletedEvent(TModel model) + : this(model, Array.Empty()) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// is -or- is . + /// + public DomainModelDeletedEvent(TModel model, IEnumerable labels) + : this(model, labels, DefaultVerbosity) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public DomainModelDeletedEvent(TModel model, IEnumerable labels, EventVerbosity verbosity) + : base(model, DomainModelEventClassification.Deleted, labels, verbosity) + { + return; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring/DomainModelEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/DomainModelEvent.cs index 703f3417..599d8071 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/DomainModelEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/DomainModelEvent.cs @@ -6,6 +6,7 @@ using RapidField.SolidInstruments.Core.Domain; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Runtime.Serialization; namespace RapidField.SolidInstruments.EventAuthoring @@ -16,6 +17,9 @@ namespace RapidField.SolidInstruments.EventAuthoring /// /// is the default implementation of . /// + /// + /// The type of the associated domain model. + /// [DataContract] public class DomainModelEvent : DomainEvent, IDomainModelEvent where TModel : class, IDomainModel @@ -97,7 +101,7 @@ public DomainModelEvent(TModel model, DomainModelEventClassification classificat /// is equal to . /// public DomainModelEvent(TModel model, DomainModelEventClassification classification, IEnumerable labels, EventVerbosity verbosity) - : this(model, classification, labels, verbosity, null) + : this(model, classification, labels, verbosity, GetDescription(model, classification)) { return; } @@ -134,6 +138,38 @@ public DomainModelEvent(TModel model, DomainModelEventClassification classificat Model = model.RejectIf().IsNull(nameof(model)); } + /// + /// Returns a description for the current . + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A classification that describes the effect of a the event upon . + /// + /// + /// A description for the current . + /// + [DebuggerHidden] + private static String GetDescription(IDomainModel model, DomainModelEventClassification classification) + { + if (model is null) + { + return null; + } + + var modelTypeName = model.GetType().FullName; + + return classification switch + { + DomainModelEventClassification.Associated => $"An event occurred that was associated with a model of type {modelTypeName}.", + DomainModelEventClassification.Created => $"A model of type {modelTypeName} was created.", + DomainModelEventClassification.Deleted => $"A model of type {modelTypeName} was deleted.", + DomainModelEventClassification.Updated => $"A model of type {modelTypeName} was updated.", + _ => null, + }; + } + /// /// Gets or sets a classification that describes the effect of a the current upon /// . @@ -154,5 +190,11 @@ public TModel Model get; set; } + + /// + /// Gets the type of the associated domain model. + /// + [IgnoreDataMember] + public Type ModelType => typeof(TModel); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring/DomainModelEventClassification.cs b/src/RapidField.SolidInstruments.EventAuthoring/DomainModelEventClassification.cs index 60e4ce75..17a7ee9e 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/DomainModelEventClassification.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/DomainModelEventClassification.cs @@ -21,25 +21,25 @@ public enum DomainModelEventClassification : Int32 Unspecified = 0, /// - /// The event relates to the model, but does not otherwise cause a change to its characteristics. + /// The event relates to the model, but does not otherwise represent a change to its characteristics. /// [EnumMember] Associated = 1, /// - /// The event causes the model to be created. + /// The event represents creation of the model. /// [EnumMember] Created = 2, /// - /// The event causes the model to be deleted. + /// The event represents deletion of the model. /// [EnumMember] Deleted = 3, /// - /// The event causes the model to be updated. + /// The event represents an update to the model. /// [EnumMember] Updated = 4 diff --git a/src/RapidField.SolidInstruments.EventAuthoring/DomainModelUpdatedEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/DomainModelUpdatedEvent.cs new file mode 100644 index 00000000..b38e9726 --- /dev/null +++ b/src/RapidField.SolidInstruments.EventAuthoring/DomainModelUpdatedEvent.cs @@ -0,0 +1,92 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace RapidField.SolidInstruments.EventAuthoring +{ + /// + /// Represents information about an update to an object that models a domain construct. + /// + /// + /// is the default implementation of + /// . + /// + /// + /// The type of the associated domain model. + /// + [DataContract] + public class DomainModelUpdatedEvent : DomainModelEvent, IDomainModelUpdatedEvent + where TModel : class, IDomainModel + { + /// + /// Initializes a new instance of the class. + /// + public DomainModelUpdatedEvent() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// is . + /// + public DomainModelUpdatedEvent(TModel model) + : this(model, Array.Empty()) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// is -or- is . + /// + public DomainModelUpdatedEvent(TModel model, IEnumerable labels) + : this(model, labels, DefaultVerbosity) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public DomainModelUpdatedEvent(TModel model, IEnumerable labels, EventVerbosity verbosity) + : base(model, DomainModelEventClassification.Updated, labels, verbosity) + { + return; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring/GeneralInformationEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/GeneralInformationEvent.cs index 93f5575a..c6697de6 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/GeneralInformationEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/GeneralInformationEvent.cs @@ -113,8 +113,8 @@ public GeneralInformationEvent(IEnumerable labels, EventVerbosity verbos public GeneralInformationEvent(IEnumerable labels, EventVerbosity verbosity, String description, DateTime timeStamp) : base(StaticCategory, verbosity, description, timeStamp) { - Labels = new List(labels.RejectIf().IsNull(nameof(labels)).TargetArgument); - Metadata = new Dictionary(); + LabelsReference = new List((labels.RejectIf().IsNull(nameof(labels)).TargetArgument)); + MetadataReference = new Dictionary(); } /// @@ -124,7 +124,16 @@ public GeneralInformationEvent(IEnumerable labels, EventVerbosity verbos [DataMember] public ICollection Labels { - get; + get + { + if (LabelsReference is null) + { + // This is necessary to accommodate specific serialization scenarios. + LabelsReference = new List(); + } + + return LabelsReference; + } } /// @@ -133,7 +142,16 @@ public ICollection Labels [DataMember] public IDictionary Metadata { - get; + get + { + if (MetadataReference is null) + { + // This is necessary to accommodate specific serialization scenarios. + MetadataReference = new Dictionary(); + } + + return MetadataReference; + } } /// @@ -141,5 +159,18 @@ public IDictionary Metadata /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] private const EventCategory StaticCategory = EventCategory.GeneralInformation; + + /// + /// Represents a collection of textual labels that provide categorical and/or contextual information about the current + /// . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private ICollection LabelsReference; + + /// + /// Represents a dictionary of metadata for the current . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private IDictionary MetadataReference; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring/IDomainModelAssociatedEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/IDomainModelAssociatedEvent.cs index 37f3225d..91c067fc 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/IDomainModelAssociatedEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/IDomainModelAssociatedEvent.cs @@ -9,6 +9,9 @@ namespace RapidField.SolidInstruments.EventAuthoring /// /// Represents information about an event related to an object that models a domain construct. /// + /// + /// The type of the associated domain model. + /// public interface IDomainModelAssociatedEvent : IDomainModelEvent where TModel : class, IDomainModel { diff --git a/src/RapidField.SolidInstruments.EventAuthoring/IDomainModelCreatedEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/IDomainModelCreatedEvent.cs index 440d0427..7b42686a 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/IDomainModelCreatedEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/IDomainModelCreatedEvent.cs @@ -9,6 +9,9 @@ namespace RapidField.SolidInstruments.EventAuthoring /// /// Represents information about the creation of an object that models a domain construct. /// + /// + /// The type of the associated domain model. + /// public interface IDomainModelCreatedEvent : IDomainModelEvent where TModel : class, IDomainModel { diff --git a/src/RapidField.SolidInstruments.EventAuthoring/IDomainModelDeletedEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/IDomainModelDeletedEvent.cs index 17736317..ce6f120f 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/IDomainModelDeletedEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/IDomainModelDeletedEvent.cs @@ -9,6 +9,9 @@ namespace RapidField.SolidInstruments.EventAuthoring /// /// Represents information about the deletion of an object that models a domain construct. /// + /// + /// The type of the associated domain model. + /// public interface IDomainModelDeletedEvent : IDomainModelEvent where TModel : class, IDomainModel { diff --git a/src/RapidField.SolidInstruments.EventAuthoring/IDomainModelEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/IDomainModelEvent.cs index 37b6740c..68952a8c 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/IDomainModelEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/IDomainModelEvent.cs @@ -3,12 +3,16 @@ // ================================================================================================================================= using RapidField.SolidInstruments.Core.Domain; +using System; namespace RapidField.SolidInstruments.EventAuthoring { /// /// Represents information about an event related to an object that models a domain construct. /// + /// + /// The type of the associated domain model. + /// public interface IDomainModelEvent : IDomainEvent where TModel : class, IDomainModel { @@ -28,5 +32,13 @@ public TModel Model { get; } + + /// + /// Gets the type of the associated domain model. + /// + public Type ModelType + { + get; + } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring/IDomainModelUpdatedEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/IDomainModelUpdatedEvent.cs index a2c4b5a1..fd02377a 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/IDomainModelUpdatedEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/IDomainModelUpdatedEvent.cs @@ -9,6 +9,9 @@ namespace RapidField.SolidInstruments.EventAuthoring /// /// Represents information about an update to an object that models a domain construct. /// + /// + /// The type of the associated domain model. + /// public interface IDomainModelUpdatedEvent : IDomainModelEvent where TModel : class, IDomainModel { diff --git a/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusListeningFacade.cs b/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusListeningFacade.cs index b24088c5..30ba5a34 100644 --- a/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusListeningFacade.cs +++ b/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusListeningFacade.cs @@ -99,7 +99,7 @@ protected sealed override void RegisterMessageHandler(Action. /// [DebuggerHidden] - private static Task HandleReceiverExceptionAsync(ExceptionReceivedEventArgs exceptionReceivedArguments) => Task.CompletedTask; // TODO + private static Task HandleReceiverExceptionAsync(ExceptionReceivedEventArgs exceptionReceivedArguments) => Task.CompletedTask; // TODO Handle receiver exceptions. /// /// Gets options that specify how receive clients handle messages. diff --git a/src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelAssociatedEventMessage.cs b/src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelAssociatedEventMessage.cs new file mode 100644 index 00000000..b3f956fc --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelAssociatedEventMessage.cs @@ -0,0 +1,100 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; +using RapidField.SolidInstruments.EventAuthoring; +using System; +using System.Runtime.Serialization; + +namespace RapidField.SolidInstruments.Messaging.EventMessages +{ + /// + /// Represents a message that provides notification about an event related to an object that models a domain construct. + /// + /// + /// is the default implementation of + /// . + /// + /// + /// The type of the associated domain model. + /// + /// + /// The type of the associated event. + /// + [DataContract] + public abstract class DomainModelAssociatedEventMessage : DomainModelEventMessage, IDomainModelAssociatedEventMessage + where TModel : class, IDomainModel + where TEvent : DomainModelAssociatedEvent, new() + { + /// + /// Initializes a new instance of the class. + /// + protected DomainModelAssociatedEventMessage() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated event. + /// + /// + /// is . + /// + protected DomainModelAssociatedEventMessage(TEvent eventObject) + : base(eventObject) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated event. + /// + /// + /// A unique identifier that is assigned to related messages. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + protected DomainModelAssociatedEventMessage(TEvent eventObject, Guid correlationIdentifier) + : base(eventObject, correlationIdentifier) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated event. + /// + /// + /// A unique identifier that is assigned to related messages. + /// + /// + /// A unique identifier for the message. + /// + /// + /// is . + /// + /// + /// is equal to -or- is + /// equal to . + /// + protected DomainModelAssociatedEventMessage(TEvent eventObject, Guid correlationIdentifier, Guid identifier) + : base(eventObject, correlationIdentifier, identifier) + { + return; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelCreatedEventMessage.cs b/src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelCreatedEventMessage.cs new file mode 100644 index 00000000..80d46313 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelCreatedEventMessage.cs @@ -0,0 +1,100 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; +using RapidField.SolidInstruments.EventAuthoring; +using System; +using System.Runtime.Serialization; + +namespace RapidField.SolidInstruments.Messaging.EventMessages +{ + /// + /// Represents a message that provides notification about the creation of an object that models a domain construct. + /// + /// + /// is the default implementation of + /// . + /// + /// + /// The type of the associated domain model. + /// + /// + /// The type of the associated event. + /// + [DataContract] + public abstract class DomainModelCreatedEventMessage : DomainModelEventMessage, IDomainModelCreatedEventMessage + where TModel : class, IDomainModel + where TEvent : DomainModelCreatedEvent, new() + { + /// + /// Initializes a new instance of the class. + /// + protected DomainModelCreatedEventMessage() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated event. + /// + /// + /// is . + /// + protected DomainModelCreatedEventMessage(TEvent eventObject) + : base(eventObject) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated event. + /// + /// + /// A unique identifier that is assigned to related messages. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + protected DomainModelCreatedEventMessage(TEvent eventObject, Guid correlationIdentifier) + : base(eventObject, correlationIdentifier) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated event. + /// + /// + /// A unique identifier that is assigned to related messages. + /// + /// + /// A unique identifier for the message. + /// + /// + /// is . + /// + /// + /// is equal to -or- is + /// equal to . + /// + protected DomainModelCreatedEventMessage(TEvent eventObject, Guid correlationIdentifier, Guid identifier) + : base(eventObject, correlationIdentifier, identifier) + { + return; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelDeletedEventMessage.cs b/src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelDeletedEventMessage.cs new file mode 100644 index 00000000..af8292f0 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelDeletedEventMessage.cs @@ -0,0 +1,100 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; +using RapidField.SolidInstruments.EventAuthoring; +using System; +using System.Runtime.Serialization; + +namespace RapidField.SolidInstruments.Messaging.EventMessages +{ + /// + /// Represents a message that provides notification about the deletion of an object that models a domain construct. + /// + /// + /// is the default implementation of + /// . + /// + /// + /// The type of the associated domain model. + /// + /// + /// The type of the associated event. + /// + [DataContract] + public abstract class DomainModelDeletedEventMessage : DomainModelEventMessage, IDomainModelDeletedEventMessage + where TModel : class, IDomainModel + where TEvent : DomainModelDeletedEvent, new() + { + /// + /// Initializes a new instance of the class. + /// + protected DomainModelDeletedEventMessage() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated event. + /// + /// + /// is . + /// + protected DomainModelDeletedEventMessage(TEvent eventObject) + : base(eventObject) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated event. + /// + /// + /// A unique identifier that is assigned to related messages. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + protected DomainModelDeletedEventMessage(TEvent eventObject, Guid correlationIdentifier) + : base(eventObject, correlationIdentifier) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated event. + /// + /// + /// A unique identifier that is assigned to related messages. + /// + /// + /// A unique identifier for the message. + /// + /// + /// is . + /// + /// + /// is equal to -or- is + /// equal to . + /// + protected DomainModelDeletedEventMessage(TEvent eventObject, Guid correlationIdentifier, Guid identifier) + : base(eventObject, correlationIdentifier, identifier) + { + return; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelEventMessage.cs b/src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelEventMessage.cs new file mode 100644 index 00000000..578b4d77 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelEventMessage.cs @@ -0,0 +1,106 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; +using RapidField.SolidInstruments.EventAuthoring; +using System; +using System.Runtime.Serialization; + +namespace RapidField.SolidInstruments.Messaging.EventMessages +{ + /// + /// Represents a message that provides notification about an event related to an object that models a domain construct. + /// + /// + /// is the default implementation of + /// . + /// + /// + /// The type of the associated domain model. + /// + /// + /// The type of the associated event. + /// + [DataContract] + public abstract class DomainModelEventMessage : DomainEventMessage, IDomainModelEventMessage + where TModel : class, IDomainModel + where TEvent : DomainModelEvent, new() + { + /// + /// Initializes a new instance of the class. + /// + protected DomainModelEventMessage() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated event. + /// + /// + /// is . + /// + protected DomainModelEventMessage(TEvent eventObject) + : base(eventObject) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated event. + /// + /// + /// A unique identifier that is assigned to related messages. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + protected DomainModelEventMessage(TEvent eventObject, Guid correlationIdentifier) + : base(eventObject, correlationIdentifier) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated event. + /// + /// + /// A unique identifier that is assigned to related messages. + /// + /// + /// A unique identifier for the message. + /// + /// + /// is . + /// + /// + /// is equal to -or- is + /// equal to . + /// + protected DomainModelEventMessage(TEvent eventObject, Guid correlationIdentifier, Guid identifier) + : base(eventObject, correlationIdentifier, identifier) + { + return; + } + + /// + /// Gets the resulting state of the associated domain model. + /// + [IgnoreDataMember] + public TModel Model => Event.Model; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelUpdatedEventMessage.cs b/src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelUpdatedEventMessage.cs new file mode 100644 index 00000000..83453463 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelUpdatedEventMessage.cs @@ -0,0 +1,100 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; +using RapidField.SolidInstruments.EventAuthoring; +using System; +using System.Runtime.Serialization; + +namespace RapidField.SolidInstruments.Messaging.EventMessages +{ + /// + /// Represents a message that provides notification about an update to an object that models a domain construct. + /// + /// + /// is the default implementation of + /// . + /// + /// + /// The type of the associated domain model. + /// + /// + /// The type of the associated event. + /// + [DataContract] + public abstract class DomainModelUpdatedEventMessage : DomainModelEventMessage, IDomainModelUpdatedEventMessage + where TModel : class, IDomainModel + where TEvent : DomainModelUpdatedEvent, new() + { + /// + /// Initializes a new instance of the class. + /// + protected DomainModelUpdatedEventMessage() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated event. + /// + /// + /// is . + /// + protected DomainModelUpdatedEventMessage(TEvent eventObject) + : base(eventObject) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated event. + /// + /// + /// A unique identifier that is assigned to related messages. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + protected DomainModelUpdatedEventMessage(TEvent eventObject, Guid correlationIdentifier) + : base(eventObject, correlationIdentifier) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated event. + /// + /// + /// A unique identifier that is assigned to related messages. + /// + /// + /// A unique identifier for the message. + /// + /// + /// is . + /// + /// + /// is equal to -or- is + /// equal to . + /// + protected DomainModelUpdatedEventMessage(TEvent eventObject, Guid correlationIdentifier, Guid identifier) + : base(eventObject, correlationIdentifier, identifier) + { + return; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/EventMessages/IDomainModelAssociatedEventMessage.cs b/src/RapidField.SolidInstruments.Messaging/EventMessages/IDomainModelAssociatedEventMessage.cs new file mode 100644 index 00000000..fa8e7308 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/EventMessages/IDomainModelAssociatedEventMessage.cs @@ -0,0 +1,24 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; +using RapidField.SolidInstruments.EventAuthoring; + +namespace RapidField.SolidInstruments.Messaging.EventMessages +{ + /// + /// Represents a message that provides notification about an event related to an object that models a domain construct. + /// + /// + /// The type of the associated domain model. + /// + /// + /// The type of the associated event. + /// + public interface IDomainModelAssociatedEventMessage : IDomainModelEventMessage + where TModel : class, IDomainModel + where TEvent : class, IDomainModelAssociatedEvent + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/EventMessages/IDomainModelCreatedEventMessage.cs b/src/RapidField.SolidInstruments.Messaging/EventMessages/IDomainModelCreatedEventMessage.cs new file mode 100644 index 00000000..1547cc76 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/EventMessages/IDomainModelCreatedEventMessage.cs @@ -0,0 +1,24 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; +using RapidField.SolidInstruments.EventAuthoring; + +namespace RapidField.SolidInstruments.Messaging.EventMessages +{ + /// + /// Represents a message that provides notification about the creation of an object that models a domain construct. + /// + /// + /// The type of the associated domain model. + /// + /// + /// The type of the associated event. + /// + public interface IDomainModelCreatedEventMessage : IDomainModelEventMessage + where TModel : class, IDomainModel + where TEvent : class, IDomainModelCreatedEvent + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/EventMessages/IDomainModelDeletedEventMessage.cs b/src/RapidField.SolidInstruments.Messaging/EventMessages/IDomainModelDeletedEventMessage.cs new file mode 100644 index 00000000..0a09cc3e --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/EventMessages/IDomainModelDeletedEventMessage.cs @@ -0,0 +1,24 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; +using RapidField.SolidInstruments.EventAuthoring; + +namespace RapidField.SolidInstruments.Messaging.EventMessages +{ + /// + /// Represents a message that provides notification about the deletion of an object that models a domain construct. + /// + /// + /// The type of the associated domain model. + /// + /// + /// The type of the associated event. + /// + public interface IDomainModelDeletedEventMessage : IDomainModelEventMessage + where TModel : class, IDomainModel + where TEvent : class, IDomainModelDeletedEvent + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/EventMessages/IDomainModelEventMessage.cs b/src/RapidField.SolidInstruments.Messaging/EventMessages/IDomainModelEventMessage.cs new file mode 100644 index 00000000..762f81da --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/EventMessages/IDomainModelEventMessage.cs @@ -0,0 +1,31 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; +using RapidField.SolidInstruments.EventAuthoring; + +namespace RapidField.SolidInstruments.Messaging.EventMessages +{ + /// + /// Represents a message that provides notification about an event related to an object that models a domain construct. + /// + /// + /// The type of the associated domain model. + /// + /// + /// The type of the associated event. + /// + public interface IDomainModelEventMessage : IDomainEventMessage + where TModel : class, IDomainModel + where TEvent : class, IDomainModelEvent + { + /// + /// Gets the resulting state of the associated domain model. + /// + public TModel Model + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/EventMessages/IDomainModelUpdatedEventMessage.cs b/src/RapidField.SolidInstruments.Messaging/EventMessages/IDomainModelUpdatedEventMessage.cs new file mode 100644 index 00000000..f7b27c9c --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/EventMessages/IDomainModelUpdatedEventMessage.cs @@ -0,0 +1,24 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; +using RapidField.SolidInstruments.EventAuthoring; + +namespace RapidField.SolidInstruments.Messaging.EventMessages +{ + /// + /// Represents a message that provides notification about an updated to an object that models a domain construct. + /// + /// + /// The type of the associated domain model. + /// + /// + /// The type of the associated event. + /// + public interface IDomainModelUpdatedEventMessage : IDomainModelEventMessage + where TModel : class, IDomainModel + where TEvent : class, IDomainModelUpdatedEvent + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/MessageListeningFacade.cs b/src/RapidField.SolidInstruments.Messaging/MessageListeningFacade.cs index ea4b430b..e8033393 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessageListeningFacade.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessageListeningFacade.cs @@ -594,8 +594,8 @@ private Task ExecuteFailurePolicyAsync(Action messageHandler { throw failurePolicy.SecondaryFailureBehavior switch { - MessageListeningSecondaryFailureBehavior.Discard => new UnsupportedSpecificationException($"The specified secondary failure behavior, {failurePolicy.SecondaryFailureBehavior}, is not supported."), // TODO - MessageListeningSecondaryFailureBehavior.RouteToDeadLetterQueue => new UnsupportedSpecificationException($"The specified secondary failure behavior, {failurePolicy.SecondaryFailureBehavior}, is not supported."), // TODO + MessageListeningSecondaryFailureBehavior.Discard => new UnsupportedSpecificationException($"The specified secondary failure behavior, {failurePolicy.SecondaryFailureBehavior}, is not supported."), // TODO Implement discard failure behavior. + MessageListeningSecondaryFailureBehavior.RouteToDeadLetterQueue => new UnsupportedSpecificationException($"The specified secondary failure behavior, {failurePolicy.SecondaryFailureBehavior}, is not supported."), // TODO Implement DLQ failure behavior. _ => new UnsupportedSpecificationException($"The specified secondary failure behavior, {failurePolicy.SecondaryFailureBehavior}, is not supported.") }; } diff --git a/test/RapidField.SolidInstruments.Command.UnitTests/SimualtedCommandWithResult.cs b/test/RapidField.SolidInstruments.Command.UnitTests/SimualtedCommandWithResult.cs index 94e58067..f99c6fa1 100644 --- a/test/RapidField.SolidInstruments.Command.UnitTests/SimualtedCommandWithResult.cs +++ b/test/RapidField.SolidInstruments.Command.UnitTests/SimualtedCommandWithResult.cs @@ -3,12 +3,14 @@ // ================================================================================================================================= using System; +using System.Runtime.Serialization; namespace RapidField.SolidInstruments.Command.UnitTests { /// /// Represents an derivative that is used for testing. /// + [DataContract] internal class SimulatedCommandWithResult : Command { /// @@ -24,6 +26,7 @@ public SimulatedCommandWithResult() /// /// Gets or sets an identifier for the command. /// + [DataMember] public Guid Identifier { get; @@ -33,6 +36,7 @@ public Guid Identifier /// /// Gets or sets a value indicating whether or not the current has been processed. /// + [DataMember] public Boolean IsProcessed { get; diff --git a/test/RapidField.SolidInstruments.Command.UnitTests/SimulatedCommand.cs b/test/RapidField.SolidInstruments.Command.UnitTests/SimulatedCommand.cs index 659aea8a..1253f4b4 100644 --- a/test/RapidField.SolidInstruments.Command.UnitTests/SimulatedCommand.cs +++ b/test/RapidField.SolidInstruments.Command.UnitTests/SimulatedCommand.cs @@ -3,12 +3,14 @@ // ================================================================================================================================= using System; +using System.Runtime.Serialization; namespace RapidField.SolidInstruments.Command.UnitTests { /// /// Represents a derivative that is used for testing. /// + [DataContract] internal class SimulatedCommand : Command { /// @@ -24,6 +26,7 @@ public SimulatedCommand() /// /// Gets or sets an identifier for the command. /// + [DataMember] public Guid Identifier { get; @@ -33,6 +36,7 @@ public Guid Identifier /// /// Gets or sets a value indicating whether or not the current has been processed. /// + [DataMember] public Boolean IsProcessed { get; diff --git a/test/RapidField.SolidInstruments.EventAuthoring.UnitTests/DomainEventTests.cs b/test/RapidField.SolidInstruments.EventAuthoring.UnitTests/DomainEventTests.cs new file mode 100644 index 00000000..e44e2320 --- /dev/null +++ b/test/RapidField.SolidInstruments.EventAuthoring.UnitTests/DomainEventTests.cs @@ -0,0 +1,91 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Serialization; +using System; + +namespace RapidField.SolidInstruments.EventAuthoring.UnitTests +{ + [TestClass] + public class DomainEventTests + { + [TestMethod] + public void ShouldBeSerializable_UsingBinaryFormat() + { + // Arrange. + var format = SerializationFormat.Binary; + + // Assert. + ShouldBeSerializable(format); + } + + [TestMethod] + public void ShouldBeSerializable_UsingCompressedJsonFormat() + { + // Arrange. + var format = SerializationFormat.CompressedJson; + + // Assert. + ShouldBeSerializable(format); + } + + [TestMethod] + public void ShouldBeSerializable_UsingCompressedXmlFormat() + { + // Arrange. + var format = SerializationFormat.CompressedXml; + + // Assert. + ShouldBeSerializable(format); + } + + [TestMethod] + public void ShouldBeSerializable_UsingJsonFormat() + { + // Arrange. + var format = SerializationFormat.Json; + + // Assert. + ShouldBeSerializable(format); + } + + [TestMethod] + public void ShouldBeSerializable_UsingXmlFormat() + { + // Arrange. + var format = SerializationFormat.Xml; + + // Assert. + ShouldBeSerializable(format); + } + + private static void ShouldBeSerializable(SerializationFormat format) + { + // Arrange. + var labels = new String[] { "foo", "bar" }; + var verbosity = EventVerbosity.Minimal; + var description = "description"; + var timeStamp = TimeStamp.Current; + var target = new DomainEvent(labels, verbosity, description, timeStamp); + var serializer = new DynamicSerializer(format); + + // Act. + var serializedTarget = serializer.Serialize(target); + var deserializedResult = serializer.Deserialize(serializedTarget); + + // Assert. + deserializedResult.Should().NotBeNull(); + deserializedResult.Labels.Should().NotBeNull(); + deserializedResult.Labels.Count.Should().Be(labels.Length); + deserializedResult.Labels.Should().BeEquivalentTo(labels); + deserializedResult.Category.Should().Be(target.Category); + deserializedResult.Description.Should().Be(target.Description); + deserializedResult.TimeStamp.Should().Be(target.TimeStamp); + deserializedResult.Verbosity.Should().Be(target.Verbosity); + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.EventAuthoring.UnitTests/GeneralInformationEventTests.cs b/test/RapidField.SolidInstruments.EventAuthoring.UnitTests/GeneralInformationEventTests.cs new file mode 100644 index 00000000..08b628d2 --- /dev/null +++ b/test/RapidField.SolidInstruments.EventAuthoring.UnitTests/GeneralInformationEventTests.cs @@ -0,0 +1,96 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Serialization; +using System; + +namespace RapidField.SolidInstruments.EventAuthoring.UnitTests +{ + [TestClass] + public class GeneralInformationEventTests + { + [TestMethod] + public void ShouldBeSerializable_UsingBinaryFormat() + { + // Arrange. + var format = SerializationFormat.Binary; + + // Assert. + ShouldBeSerializable(format); + } + + [TestMethod] + public void ShouldBeSerializable_UsingCompressedJsonFormat() + { + // Arrange. + var format = SerializationFormat.CompressedJson; + + // Assert. + ShouldBeSerializable(format); + } + + [TestMethod] + public void ShouldBeSerializable_UsingCompressedXmlFormat() + { + // Arrange. + var format = SerializationFormat.CompressedXml; + + // Assert. + ShouldBeSerializable(format); + } + + [TestMethod] + public void ShouldBeSerializable_UsingJsonFormat() + { + // Arrange. + var format = SerializationFormat.Json; + + // Assert. + ShouldBeSerializable(format); + } + + [TestMethod] + public void ShouldBeSerializable_UsingXmlFormat() + { + // Arrange. + var format = SerializationFormat.Xml; + + // Assert. + ShouldBeSerializable(format); + } + + private static void ShouldBeSerializable(SerializationFormat format) + { + // Arrange. + var labels = new String[] { "foo", "bar" }; + var verbosity = EventVerbosity.Minimal; + var description = "description"; + var timeStamp = TimeStamp.Current; + var target = new GeneralInformationEvent(labels, verbosity, description, timeStamp); + var serializer = new DynamicSerializer(format); + target.Metadata.Add("bar", "baz"); + target.Metadata.Add("fizz", "buzz"); + + // Act. + var serializedTarget = serializer.Serialize(target); + var deserializedResult = serializer.Deserialize(serializedTarget); + + // Assert. + deserializedResult.Should().NotBeNull(); + deserializedResult.Labels.Should().NotBeNull(); + deserializedResult.Labels.Count.Should().Be(labels.Length); + deserializedResult.Labels.Should().BeEquivalentTo(labels); + deserializedResult.Metadata.Should().NotBeNull(); + deserializedResult.Metadata.Count.Should().Be(target.Metadata.Count); + deserializedResult.Metadata.Should().BeEquivalentTo(target.Metadata); + deserializedResult.Category.Should().Be(target.Category); + deserializedResult.Description.Should().Be(target.Description); + deserializedResult.TimeStamp.Should().Be(target.TimeStamp); + deserializedResult.Verbosity.Should().Be(target.Verbosity); + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Customer/CreateDomainModelCommand.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Customer/CreateDomainModelCommand.cs new file mode 100644 index 00000000..47ce1f1a --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Customer/CreateDomainModelCommand.cs @@ -0,0 +1,13 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System.Runtime.Serialization; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.Customer +{ + [DataContract] + internal sealed class CreateDomainModelCommand + { + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Customer/DomainModelCreatedEvent.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Customer/DomainModelCreatedEvent.cs new file mode 100644 index 00000000..268b6335 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Customer/DomainModelCreatedEvent.cs @@ -0,0 +1,85 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.EventAuthoring; +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer.DomainModel; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.Customer +{ + /// + /// Represents information about the creation of a . + /// + [DataContract(Name = "CustomerCreatedEvent")] + internal sealed class DomainModelCreatedEvent : DomainModelCreatedEvent + { + /// + /// Initializes a new instance of the class. + /// + public DomainModelCreatedEvent() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// is . + /// + public DomainModelCreatedEvent(DomainModel model) + : base(model) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// is -or- is . + /// + public DomainModelCreatedEvent(DomainModel model, IEnumerable labels) + : base(model, labels) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public DomainModelCreatedEvent(DomainModel model, IEnumerable labels, EventVerbosity verbosity) + : base(model, labels, verbosity) + { + return; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Customer/DomainModelDeletedEvent.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Customer/DomainModelDeletedEvent.cs new file mode 100644 index 00000000..9cb0ba38 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Customer/DomainModelDeletedEvent.cs @@ -0,0 +1,85 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.EventAuthoring; +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer.DomainModel; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.Customer +{ + /// + /// Represents information about the deletion of a . + /// + [DataContract(Name = "CustomerDeletedEvent")] + internal sealed class DomainModelDeletedEvent : DomainModelDeletedEvent + { + /// + /// Initializes a new instance of the class. + /// + public DomainModelDeletedEvent() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// is . + /// + public DomainModelDeletedEvent(DomainModel model) + : base(model) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// is -or- is . + /// + public DomainModelDeletedEvent(DomainModel model, IEnumerable labels) + : base(model, labels) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public DomainModelDeletedEvent(DomainModel model, IEnumerable labels, EventVerbosity verbosity) + : base(model, labels, verbosity) + { + return; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Customer/DomainModelUpdatedEvent.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Customer/DomainModelUpdatedEvent.cs new file mode 100644 index 00000000..339124d7 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Customer/DomainModelUpdatedEvent.cs @@ -0,0 +1,85 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.EventAuthoring; +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer.DomainModel; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.Customer +{ + /// + /// Represents information about an update to a . + /// + [DataContract(Name = "CustomerUpdatedEvent")] + internal sealed class DomainModelUpdatedEvent : DomainModelUpdatedEvent + { + /// + /// Initializes a new instance of the class. + /// + public DomainModelUpdatedEvent() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// is . + /// + public DomainModelUpdatedEvent(DomainModel model) + : base(model) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// is -or- is . + /// + public DomainModelUpdatedEvent(DomainModel model, IEnumerable labels) + : base(model, labels) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public DomainModelUpdatedEvent(DomainModel model, IEnumerable labels, EventVerbosity verbosity) + : base(model, labels, verbosity) + { + return; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/CustomerOrder/DomainModelCreatedEvent.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/CustomerOrder/DomainModelCreatedEvent.cs new file mode 100644 index 00000000..c7e8a0c5 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/CustomerOrder/DomainModelCreatedEvent.cs @@ -0,0 +1,85 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.EventAuthoring; +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.CustomerOrder.DomainModel; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.CustomerOrder +{ + /// + /// Represents information about the creation of a . + /// + [DataContract(Name = "CustomerOrderCreatedEvent")] + internal sealed class DomainModelCreatedEvent : DomainModelCreatedEvent + { + /// + /// Initializes a new instance of the class. + /// + public DomainModelCreatedEvent() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// is . + /// + public DomainModelCreatedEvent(DomainModel model) + : base(model) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// is -or- is . + /// + public DomainModelCreatedEvent(DomainModel model, IEnumerable labels) + : base(model, labels) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public DomainModelCreatedEvent(DomainModel model, IEnumerable labels, EventVerbosity verbosity) + : base(model, labels, verbosity) + { + return; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/CustomerOrder/DomainModelDeletedEvent.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/CustomerOrder/DomainModelDeletedEvent.cs new file mode 100644 index 00000000..2eef8924 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/CustomerOrder/DomainModelDeletedEvent.cs @@ -0,0 +1,85 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.EventAuthoring; +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.CustomerOrder.DomainModel; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.CustomerOrder +{ + /// + /// Represents information about the deletion of a . + /// + [DataContract(Name = "CustomerOrderDeletedEvent")] + internal sealed class DomainModelDeletedEvent : DomainModelDeletedEvent + { + /// + /// Initializes a new instance of the class. + /// + public DomainModelDeletedEvent() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// is . + /// + public DomainModelDeletedEvent(DomainModel model) + : base(model) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// is -or- is . + /// + public DomainModelDeletedEvent(DomainModel model, IEnumerable labels) + : base(model, labels) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public DomainModelDeletedEvent(DomainModel model, IEnumerable labels, EventVerbosity verbosity) + : base(model, labels, verbosity) + { + return; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/CustomerOrder/DomainModelUpdatedEvent.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/CustomerOrder/DomainModelUpdatedEvent.cs new file mode 100644 index 00000000..88e028c4 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/CustomerOrder/DomainModelUpdatedEvent.cs @@ -0,0 +1,85 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.EventAuthoring; +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.CustomerOrder.DomainModel; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.CustomerOrder +{ + /// + /// Represents information about an update to a . + /// + [DataContract(Name = "CustomerOrderUpdatedEvent")] + internal sealed class DomainModelUpdatedEvent : DomainModelUpdatedEvent + { + /// + /// Initializes a new instance of the class. + /// + public DomainModelUpdatedEvent() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// is . + /// + public DomainModelUpdatedEvent(DomainModel model) + : base(model) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// is -or- is . + /// + public DomainModelUpdatedEvent(DomainModel model, IEnumerable labels) + : base(model, labels) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public DomainModelUpdatedEvent(DomainModel model, IEnumerable labels, EventVerbosity verbosity) + : base(model, labels, verbosity) + { + return; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Product/DomainModelCreatedEvent.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Product/DomainModelCreatedEvent.cs new file mode 100644 index 00000000..c040758b --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Product/DomainModelCreatedEvent.cs @@ -0,0 +1,85 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.EventAuthoring; +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Product.DomainModel; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.Product +{ + /// + /// Represents information about the creation of a . + /// + [DataContract(Name = "ProductCreatedEvent")] + internal sealed class DomainModelCreatedEvent : DomainModelCreatedEvent + { + /// + /// Initializes a new instance of the class. + /// + public DomainModelCreatedEvent() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// is . + /// + public DomainModelCreatedEvent(DomainModel model) + : base(model) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// is -or- is . + /// + public DomainModelCreatedEvent(DomainModel model, IEnumerable labels) + : base(model, labels) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public DomainModelCreatedEvent(DomainModel model, IEnumerable labels, EventVerbosity verbosity) + : base(model, labels, verbosity) + { + return; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Product/DomainModelDeletedEvent.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Product/DomainModelDeletedEvent.cs new file mode 100644 index 00000000..97765e48 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Product/DomainModelDeletedEvent.cs @@ -0,0 +1,85 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.EventAuthoring; +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Product.DomainModel; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.Product +{ + /// + /// Represents information about the deletion of a . + /// + [DataContract(Name = "ProductDeletedEvent")] + internal sealed class DomainModelDeletedEvent : DomainModelDeletedEvent + { + /// + /// Initializes a new instance of the class. + /// + public DomainModelDeletedEvent() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// is . + /// + public DomainModelDeletedEvent(DomainModel model) + : base(model) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// is -or- is . + /// + public DomainModelDeletedEvent(DomainModel model, IEnumerable labels) + : base(model, labels) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public DomainModelDeletedEvent(DomainModel model, IEnumerable labels, EventVerbosity verbosity) + : base(model, labels, verbosity) + { + return; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Product/DomainModelUpdatedEvent.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Product/DomainModelUpdatedEvent.cs new file mode 100644 index 00000000..a097ba97 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Product/DomainModelUpdatedEvent.cs @@ -0,0 +1,85 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.EventAuthoring; +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Product.DomainModel; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.Product +{ + /// + /// Represents information about an update to a . + /// + [DataContract(Name = "ProductUpdatedEvent")] + internal sealed class DomainModelUpdatedEvent : DomainModelUpdatedEvent + { + /// + /// Initializes a new instance of the class. + /// + public DomainModelUpdatedEvent() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// is . + /// + public DomainModelUpdatedEvent(DomainModel model) + : base(model) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// is -or- is . + /// + public DomainModelUpdatedEvent(DomainModel model, IEnumerable labels) + : base(model, labels) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public DomainModelUpdatedEvent(DomainModel model, IEnumerable labels, EventVerbosity verbosity) + : base(model, labels, verbosity) + { + return; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Customer/DomainModelCreatedEventMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Customer/DomainModelCreatedEventMessage.cs new file mode 100644 index 00000000..8342969e --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Customer/DomainModelCreatedEventMessage.cs @@ -0,0 +1,64 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Runtime.Serialization; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer.DomainModel; +using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.Customer.DomainModelCreatedEvent; +using DomainModelEventMessage = RapidField.SolidInstruments.Messaging.EventMessages.DomainModelCreatedEventMessage; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Event.ModelState.Customer +{ + /// + /// Represents a message that provides notification about the creation of a . + /// + [DataContract(Name = "CustomerCreatedEventMessage")] + internal sealed class DomainModelCreatedEventMessage : DomainModelEventMessage + { + /// + /// Initializes a new instance of the class. + /// + public DomainModelCreatedEventMessage() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated event. + /// + /// + /// is . + /// + public DomainModelCreatedEventMessage(DomainModelEvent eventObject) + : base(eventObject) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated event. + /// + /// + /// A unique identifier that is assigned to related messages. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public DomainModelCreatedEventMessage(DomainModelEvent eventObject, Guid correlationIdentifier) + : base(eventObject, correlationIdentifier) + { + return; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Customer/DomainModelDeletedEventMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Customer/DomainModelDeletedEventMessage.cs new file mode 100644 index 00000000..507b39c1 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Customer/DomainModelDeletedEventMessage.cs @@ -0,0 +1,64 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Runtime.Serialization; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer.DomainModel; +using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.Customer.DomainModelDeletedEvent; +using DomainModelEventMessage = RapidField.SolidInstruments.Messaging.EventMessages.DomainModelDeletedEventMessage; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Event.ModelState.Customer +{ + /// + /// Represents a message that provides notification about the deletion of a . + /// + [DataContract(Name = "CustomerDeletedEventMessage")] + internal sealed class DomainModelDeletedEventMessage : DomainModelEventMessage + { + /// + /// Initializes a new instance of the class. + /// + public DomainModelDeletedEventMessage() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated event. + /// + /// + /// is . + /// + public DomainModelDeletedEventMessage(DomainModelEvent eventObject) + : base(eventObject) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated event. + /// + /// + /// A unique identifier that is assigned to related messages. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public DomainModelDeletedEventMessage(DomainModelEvent eventObject, Guid correlationIdentifier) + : base(eventObject, correlationIdentifier) + { + return; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Customer/DomainModelUpdatedEventMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Customer/DomainModelUpdatedEventMessage.cs new file mode 100644 index 00000000..f98905d5 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Customer/DomainModelUpdatedEventMessage.cs @@ -0,0 +1,64 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Runtime.Serialization; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer.DomainModel; +using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.Customer.DomainModelUpdatedEvent; +using DomainModelEventMessage = RapidField.SolidInstruments.Messaging.EventMessages.DomainModelUpdatedEventMessage; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Event.ModelState.Customer +{ + /// + /// Represents a message that provides notification about an update to a . + /// + [DataContract(Name = "CustomerUpdatedEventMessage")] + internal sealed class DomainModelUpdatedEventMessage : DomainModelEventMessage + { + /// + /// Initializes a new instance of the class. + /// + public DomainModelUpdatedEventMessage() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated event. + /// + /// + /// is . + /// + public DomainModelUpdatedEventMessage(DomainModelEvent eventObject) + : base(eventObject) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated event. + /// + /// + /// A unique identifier that is assigned to related messages. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public DomainModelUpdatedEventMessage(DomainModelEvent eventObject, Guid correlationIdentifier) + : base(eventObject, correlationIdentifier) + { + return; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/CustomerOrder/DomainModelCreatedEventMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/CustomerOrder/DomainModelCreatedEventMessage.cs new file mode 100644 index 00000000..3e8f2aaa --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/CustomerOrder/DomainModelCreatedEventMessage.cs @@ -0,0 +1,64 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Runtime.Serialization; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.CustomerOrder.DomainModel; +using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.CustomerOrder.DomainModelCreatedEvent; +using DomainModelEventMessage = RapidField.SolidInstruments.Messaging.EventMessages.DomainModelCreatedEventMessage; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Event.ModelState.CustomerOrder +{ + /// + /// Represents a message that provides notification about the creation of a . + /// + [DataContract(Name = "CustomerOrderCreatedEventMessage")] + internal sealed class DomainModelCreatedEventMessage : DomainModelEventMessage + { + /// + /// Initializes a new instance of the class. + /// + public DomainModelCreatedEventMessage() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated event. + /// + /// + /// is . + /// + public DomainModelCreatedEventMessage(DomainModelEvent eventObject) + : base(eventObject) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated event. + /// + /// + /// A unique identifier that is assigned to related messages. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public DomainModelCreatedEventMessage(DomainModelEvent eventObject, Guid correlationIdentifier) + : base(eventObject, correlationIdentifier) + { + return; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/CustomerOrder/DomainModelDeletedEventMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/CustomerOrder/DomainModelDeletedEventMessage.cs new file mode 100644 index 00000000..3df77587 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/CustomerOrder/DomainModelDeletedEventMessage.cs @@ -0,0 +1,64 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Runtime.Serialization; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.CustomerOrder.DomainModel; +using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.CustomerOrder.DomainModelDeletedEvent; +using DomainModelEventMessage = RapidField.SolidInstruments.Messaging.EventMessages.DomainModelDeletedEventMessage; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Event.ModelState.CustomerOrder +{ + /// + /// Represents a message that provides notification about the deletion of a . + /// + [DataContract(Name = "CustomerOrderDeletedEventMessage")] + internal sealed class DomainModelDeletedEventMessage : DomainModelEventMessage + { + /// + /// Initializes a new instance of the class. + /// + public DomainModelDeletedEventMessage() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated event. + /// + /// + /// is . + /// + public DomainModelDeletedEventMessage(DomainModelEvent eventObject) + : base(eventObject) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated event. + /// + /// + /// A unique identifier that is assigned to related messages. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public DomainModelDeletedEventMessage(DomainModelEvent eventObject, Guid correlationIdentifier) + : base(eventObject, correlationIdentifier) + { + return; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/CustomerOrder/DomainModelUpdatedEventMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/CustomerOrder/DomainModelUpdatedEventMessage.cs new file mode 100644 index 00000000..bb5e8041 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/CustomerOrder/DomainModelUpdatedEventMessage.cs @@ -0,0 +1,64 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Runtime.Serialization; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.CustomerOrder.DomainModel; +using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.CustomerOrder.DomainModelUpdatedEvent; +using DomainModelEventMessage = RapidField.SolidInstruments.Messaging.EventMessages.DomainModelUpdatedEventMessage; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Event.ModelState.CustomerOrder +{ + /// + /// Represents a message that provides notification about an update to a . + /// + [DataContract(Name = "CustomerOrderUpdatedEventMessage")] + internal sealed class DomainModelUpdatedEventMessage : DomainModelEventMessage + { + /// + /// Initializes a new instance of the class. + /// + public DomainModelUpdatedEventMessage() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated event. + /// + /// + /// is . + /// + public DomainModelUpdatedEventMessage(DomainModelEvent eventObject) + : base(eventObject) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated event. + /// + /// + /// A unique identifier that is assigned to related messages. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public DomainModelUpdatedEventMessage(DomainModelEvent eventObject, Guid correlationIdentifier) + : base(eventObject, correlationIdentifier) + { + return; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Product/DomainModelCreatedEventMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Product/DomainModelCreatedEventMessage.cs new file mode 100644 index 00000000..d9cbdabf --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Product/DomainModelCreatedEventMessage.cs @@ -0,0 +1,64 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Runtime.Serialization; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Product.DomainModel; +using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.Product.DomainModelCreatedEvent; +using DomainModelEventMessage = RapidField.SolidInstruments.Messaging.EventMessages.DomainModelCreatedEventMessage; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Event.ModelState.Product +{ + /// + /// Represents a message that provides notification about the creation of a . + /// + [DataContract(Name = "ProductCreatedEventMessage")] + internal sealed class DomainModelCreatedEventMessage : DomainModelEventMessage + { + /// + /// Initializes a new instance of the class. + /// + public DomainModelCreatedEventMessage() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated event. + /// + /// + /// is . + /// + public DomainModelCreatedEventMessage(DomainModelEvent eventObject) + : base(eventObject) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated event. + /// + /// + /// A unique identifier that is assigned to related messages. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public DomainModelCreatedEventMessage(DomainModelEvent eventObject, Guid correlationIdentifier) + : base(eventObject, correlationIdentifier) + { + return; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Product/DomainModelDeletedEventMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Product/DomainModelDeletedEventMessage.cs new file mode 100644 index 00000000..d4eb334d --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Product/DomainModelDeletedEventMessage.cs @@ -0,0 +1,64 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Runtime.Serialization; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Product.DomainModel; +using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.Product.DomainModelDeletedEvent; +using DomainModelEventMessage = RapidField.SolidInstruments.Messaging.EventMessages.DomainModelDeletedEventMessage; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Event.ModelState.Product +{ + /// + /// Represents a message that provides notification about the deletion of a . + /// + [DataContract(Name = "ProductDeletedEventMessage")] + internal sealed class DomainModelDeletedEventMessage : DomainModelEventMessage + { + /// + /// Initializes a new instance of the class. + /// + public DomainModelDeletedEventMessage() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated event. + /// + /// + /// is . + /// + public DomainModelDeletedEventMessage(DomainModelEvent eventObject) + : base(eventObject) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated event. + /// + /// + /// A unique identifier that is assigned to related messages. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public DomainModelDeletedEventMessage(DomainModelEvent eventObject, Guid correlationIdentifier) + : base(eventObject, correlationIdentifier) + { + return; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Product/DomainModelUpdatedEventMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Product/DomainModelUpdatedEventMessage.cs new file mode 100644 index 00000000..7ca116ff --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Product/DomainModelUpdatedEventMessage.cs @@ -0,0 +1,64 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Runtime.Serialization; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Product.DomainModel; +using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.Product.DomainModelUpdatedEvent; +using DomainModelEventMessage = RapidField.SolidInstruments.Messaging.EventMessages.DomainModelUpdatedEventMessage; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Event.ModelState.Product +{ + /// + /// Represents a message that provides notification about an update to a . + /// + [DataContract(Name = "ProductUpdatedEventMessage")] + internal sealed class DomainModelUpdatedEventMessage : DomainModelEventMessage + { + /// + /// Initializes a new instance of the class. + /// + public DomainModelUpdatedEventMessage() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated event. + /// + /// + /// is . + /// + public DomainModelUpdatedEventMessage(DomainModelEvent eventObject) + : base(eventObject) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated event. + /// + /// + /// A unique identifier that is assigned to related messages. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public DomainModelUpdatedEventMessage(DomainModelEvent eventObject, Guid correlationIdentifier) + : base(eventObject, correlationIdentifier) + { + return; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Customer.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Customer.cs deleted file mode 100644 index f594854f..00000000 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Customer.cs +++ /dev/null @@ -1,51 +0,0 @@ -// ================================================================================================================================= -// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. -// ================================================================================================================================= - -using RapidField.SolidInstruments.Core.Domain; -using System; -using System.Runtime.Serialization; - -namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models -{ - /// - /// Represents a customer. - /// - [DataContract] - internal sealed class Customer : GlobalIdentityDomainModel - { - /// - /// Initializes a new instance of the class. - /// - public Customer() - : base() - { - Name = null; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// A value that uniquely identifies the model. The default value is equal to the default instance of . - /// - /// - /// The name of the customer. - /// - public Customer(Guid identifier, String name) - : base(identifier) - { - Name = name; - } - - /// - /// Gets or sets the name of the current . - /// - [DataMember] - public String Name - { - get; - set; - } - } -} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Customer/DomainModel.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Customer/DomainModel.cs new file mode 100644 index 00000000..94f995b2 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Customer/DomainModel.cs @@ -0,0 +1,104 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.Serialization; +using BaseDomainModel = RapidField.SolidInstruments.Core.Domain.GlobalIdentityDomainModel; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer +{ + /// + /// Represents a customer. + /// + [DataContract] + internal sealed class DomainModel : BaseDomainModel, IAggregate + { + /// + /// Initializes a new instance of the class. + /// + [DebuggerHidden] + internal DomainModel() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A value that uniquely identifies the domain model. + /// + [DebuggerHidden] + internal DomainModel(Guid identifier) + : base(identifier) + { + return; + } + + /// + /// Gets or sets the name of the current . + /// + /// + /// is empty. + /// + /// + /// is . + /// + [DataMember] + public String Name + { + get => NameValue; + set => NameValue = value.RejectIf().IsNullOrEmpty(nameof(Name)); + } + + /// + /// Represents the name of the current . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + [IgnoreDataMember] + private String NameValue; + + /// + /// Contains a collection of known instances. + /// + internal static class Named + { + /// + /// Returns a collection of all known instances. + /// + /// + /// A collection of all known instances. + /// + [DebuggerHidden] + internal static IEnumerable All() => new DomainModel[] + { + AcmeCo, + SmithIndustries + }; + + /// + /// Gets the ACME Co. customer. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal static DomainModel AcmeCo => new DomainModel(Guid.Parse("8e953a24-2d7f-4be1-96aa-c5288ad380ad")) + { + Name = "ACME Co." + }; + + /// + /// Gets the Smith Industries customer. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal static DomainModel SmithIndustries => new DomainModel(Guid.Parse("c2e497e2-ab27-457c-808d-8dfd74c9748d")) + { + Name = "Smith Industries" + }; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Customer/IAggregate.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Customer/IAggregate.cs new file mode 100644 index 00000000..7a7a5db8 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Customer/IAggregate.cs @@ -0,0 +1,31 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using System; +using IBaseDomainModel = RapidField.SolidInstruments.Core.Domain.IGlobalIdentityAggregateDomainModel; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer +{ + /// + /// Represents a customer. + /// + internal interface IAggregate : IBaseDomainModel, IValue + { + /// + /// Gets or sets the name of the current . + /// + /// + /// is empty. + /// + /// + /// is . + /// + public new String Name + { + get; + set; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Customer/IValue.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Customer/IValue.cs new file mode 100644 index 00000000..b412d3c4 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Customer/IValue.cs @@ -0,0 +1,23 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using IBaseDomainModel = RapidField.SolidInstruments.Core.Domain.IGlobalIdentityValueDomainModel; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer +{ + /// + /// Represents a customer. + /// + internal interface IValue : IBaseDomainModel + { + /// + /// Gets the name of the current . + /// + public String Name + { + get; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/CustomerOrder.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/CustomerOrder.cs deleted file mode 100644 index f0e92aab..00000000 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/CustomerOrder.cs +++ /dev/null @@ -1,116 +0,0 @@ -// ================================================================================================================================= -// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. -// ================================================================================================================================= - -using RapidField.SolidInstruments.Core; -using RapidField.SolidInstruments.Core.Domain; -using System; -using System.Collections.Generic; -using System.Runtime.Serialization; - -namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models -{ - /// - /// Represents an order placed by a customer. - /// - [DataContract] - internal sealed class CustomerOrder : GlobalIdentityDomainModel - { - /// - /// Initializes a new instance of the class. - /// - public CustomerOrder() - : base() - { - Customer = null; - PlacementTimeStamp = default; - Products = new List(); - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// A value that uniquely identifies the model. The default value is equal to the default instance of . - /// - /// - /// The customer that placed the order. - /// - public CustomerOrder(Guid identifier, Customer customer) - : this(identifier, customer, TimeStamp.Current) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// A value that uniquely identifies the model. The default value is equal to the default instance of . - /// - /// - /// The customer that placed the order. - /// - /// - /// The date and time when the order was placed. - /// - public CustomerOrder(Guid identifier, Customer customer, DateTime placementTimeStamp) - : this(identifier, customer, placementTimeStamp, null) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// A value that uniquely identifies the model. The default value is equal to the default instance of . - /// - /// - /// The customer that placed the order. - /// - /// - /// The date and time when the order was placed. - /// - /// - /// The constituent produces that are or were ordered by . - /// - public CustomerOrder(Guid identifier, Customer customer, DateTime placementTimeStamp, IEnumerable products) - : base(identifier) - { - Customer = customer; - PlacementTimeStamp = placementTimeStamp; - Products = new List(products ?? Array.Empty()); - } - - /// - /// Gets or sets the customer that placed the current . - /// - [DataMember] - public Customer Customer - { - get; - set; - } - - /// - /// Gets or sets the date and time when the current was placed. - /// - [DataMember] - public DateTime PlacementTimeStamp - { - get; - set; - } - - /// - /// Gets or sets the constituent products that are or were ordered by the associated . - /// - [DataMember] - public ICollection Products - { - get; - set; - } - } -} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/CustomerOrder/DomainModel.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/CustomerOrder/DomainModel.cs new file mode 100644 index 00000000..cd791724 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/CustomerOrder/DomainModel.cs @@ -0,0 +1,116 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.ArgumentValidation; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.Serialization; +using BaseDomainModel = RapidField.SolidInstruments.Core.Domain.GlobalIdentityDomainModel; +using CustomerModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer.DomainModel; +using ProductModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Product.DomainModel; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.CustomerOrder +{ + /// + /// Represents an order placed by a customer. + /// + [DataContract] + internal sealed class DomainModel : BaseDomainModel, IAggregate + { + /// + /// Initializes a new instance of the class. + /// + [DebuggerHidden] + internal DomainModel() + : base() + { + Products = new List(); + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The domain model to replicate. + /// + /// + /// is . + /// + [DebuggerHidden] + internal DomainModel(IValue model) + : this(model.RejectIf().IsNull(nameof(model)).TargetArgument.Identifier) + { + PlacementTimeStamp = model.PlacementTimeStamp; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A value that uniquely identifies the domain model. + /// + /// + /// is equal to . + /// + [DebuggerHidden] + private DomainModel(Guid identifier) + : this() + { + Identifier = identifier.RejectIf().IsEqualToValue(default, nameof(identifier)); + } + + /// + /// Gets or sets the customer that placed the current . + /// + [DataMember] + public CustomerModel Customer + { + get; + set; + } + + /// + /// Gets or sets the date and time when the current was placed. + /// + [DataMember] + public DateTime PlacementTimeStamp + { + get; + set; + } + + /// + /// Gets or sets the constituent products that are or were ordered by the associated . + /// + [DataMember] + public ICollection Products + { + get; + set; + } + + /// + /// Gets the total cost of the current . + /// + [IgnoreDataMember] + public Decimal TotalCost => Products.Select(product => product.Price ?? 0m).Sum(); + + /// + /// Contains a collection of known instances. + /// + internal static class Named + { + /// + /// Returns a collection of all known instances. + /// + /// + /// A collection of all known instances. + /// + [DebuggerHidden] + internal static IEnumerable All() => Array.Empty(); + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/CustomerOrder/IAggregate.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/CustomerOrder/IAggregate.cs new file mode 100644 index 00000000..a137cf0e --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/CustomerOrder/IAggregate.cs @@ -0,0 +1,51 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Collections.Generic; +using CustomerModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer.DomainModel; +using IBaseDomainModel = RapidField.SolidInstruments.Core.Domain.IGlobalIdentityAggregateDomainModel; +using ProductModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Product.DomainModel; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.CustomerOrder +{ + /// + /// Represents an order placed by a customer. + /// + internal interface IAggregate : IBaseDomainModel, IValue + { + /// + /// Gets the customer that placed the current . + /// + public CustomerModel Customer + { + get; + } + + /// + /// Gets or sets the date and time when the current was placed. + /// + public new DateTime PlacementTimeStamp + { + get; + set; + } + + /// + /// Gets the constituent products that are or were ordered by the associated . + /// + public ICollection Products + { + get; + } + + /// + /// Gets the total cost of the current . + /// + public Decimal TotalCost + { + get; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/CustomerOrder/IValue.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/CustomerOrder/IValue.cs new file mode 100644 index 00000000..57956941 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/CustomerOrder/IValue.cs @@ -0,0 +1,23 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using IBaseDomainModel = RapidField.SolidInstruments.Core.Domain.IGlobalIdentityValueDomainModel; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.CustomerOrder +{ + /// + /// Represents an order placed by a customer. + /// + internal interface IValue : IBaseDomainModel + { + /// + /// Gets the date and time when the current was placed. + /// + public DateTime PlacementTimeStamp + { + get; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Product.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Product.cs deleted file mode 100644 index fc248216..00000000 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Product.cs +++ /dev/null @@ -1,81 +0,0 @@ -// ================================================================================================================================= -// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. -// ================================================================================================================================= - -using RapidField.SolidInstruments.Core.Domain; -using System; -using System.Runtime.Serialization; - -namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models -{ - /// - /// Represents an item of merchandise. - /// - [DataContract] - internal sealed class Product : GlobalIdentityDomainModel - { - /// - /// Initializes a new instance of the class. - /// - public Product() - : base() - { - Name = null; - Price = null; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// A value that uniquely identifies the model. The default value is equal to the default instance of . - /// - /// - /// The name of the product. - /// - public Product(Guid identifier, String name) - : this(identifier, name, null) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// A value that uniquely identifies the model. The default value is equal to the default instance of . - /// - /// - /// The name of the product. - /// - /// - /// The price of the product, or if the price is unknown. - /// - public Product(Guid identifier, String name, Decimal? price) - : base(identifier) - { - Name = name; - Price = price; - } - - /// - /// Gets or sets the name of the current . - /// - [DataMember] - public String Name - { - get; - set; - } - - /// - /// Gets or sets the price of the current , or if the price is unknown. - /// - [DataMember] - public Decimal? Price - { - get; - set; - } - } -} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Product/DomainModel.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Product/DomainModel.cs new file mode 100644 index 00000000..80ce83af --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Product/DomainModel.cs @@ -0,0 +1,129 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.Serialization; +using BaseDomainModel = RapidField.SolidInstruments.Core.Domain.GlobalIdentityDomainModel; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Product +{ + /// + /// Represents an item of merchandise. + /// + [DataContract] + internal sealed class DomainModel : BaseDomainModel, IAggregate + { + /// + /// Initializes a new instance of the class. + /// + [DebuggerHidden] + internal DomainModel() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A value that uniquely identifies the domain model. + /// + /// + /// is equal to . + /// + [DebuggerHidden] + internal DomainModel(Guid identifier) + : this() + { + Identifier = identifier.RejectIf().IsEqualToValue(default, nameof(identifier)); + } + + /// + /// Gets or sets the name of the current . + /// + /// + /// is empty. + /// + /// + /// is . + /// + [DataMember] + public String Name + { + get => NameValue; + set => NameValue = value.RejectIf().IsNullOrEmpty(nameof(Name)); + } + + /// + /// Gets or sets the price of the current , or if the price is unknown. + /// + /// + /// is less than zero. + /// + [DataMember] + public Decimal? Price + { + get => PriceValue; + set => PriceValue = value?.RejectIf().IsLessThan(0, nameof(Price)); + } + + /// + /// Represents the name of the current . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + [IgnoreDataMember] + private String NameValue; + + /// + /// Represents the price of the current , or if the price is unknown. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + [IgnoreDataMember] + private Decimal? PriceValue; + + /// + /// Contains a collection of known instances. + /// + internal static class Named + { + /// + /// Returns a collection of all known instances. + /// + /// + /// A collection of all known instances. + /// + [DebuggerHidden] + internal static IEnumerable All() => new DomainModel[] + { + Fidget, + Widget + }; + + /// + /// Gets the Fidget product. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal static DomainModel Fidget => new DomainModel(Guid.Parse("dc177df0-2b54-44e6-a166-55c3b4403d5f")) + { + Name = "Fidget", + Price = 1.99m + }; + + /// + /// Gets the Widget product. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal static DomainModel Widget => new DomainModel(Guid.Parse("acec6baf-66e2-49c1-92e5-37f1b9722e73")) + { + Name = "Widget", + Price = 13.69m + }; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Product/IAggregate.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Product/IAggregate.cs new file mode 100644 index 00000000..668ebb28 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Product/IAggregate.cs @@ -0,0 +1,43 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using System; +using IBaseDomainModel = RapidField.SolidInstruments.Core.Domain.IGlobalIdentityAggregateDomainModel; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Product +{ + /// + /// Represents an item of merchandise. + /// + internal interface IAggregate : IBaseDomainModel, IValue + { + /// + /// Gets or sets the name of the current . + /// + /// + /// is empty. + /// + /// + /// is . + /// + public new String Name + { + get; + set; + } + + /// + /// Gets or sets the price of the current , or if the price is unknown. + /// + /// + /// is less than zero. + /// + public new Decimal? Price + { + get; + set; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Product/IValue.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Product/IValue.cs new file mode 100644 index 00000000..78a4688b --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Product/IValue.cs @@ -0,0 +1,31 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using IBaseDomainModel = RapidField.SolidInstruments.Core.Domain.IGlobalIdentityValueDomainModel; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Product +{ + /// + /// Represents an item of merchandise. + /// + internal interface IValue : IBaseDomainModel + { + /// + /// Gets the name of the current . + /// + public String Name + { + get; + } + + /// + /// Gets the price of the current , or if the price is unknown. + /// + public Decimal? Price + { + get; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/RapidField.SolidInstruments.Messaging.InMemory.UnitTests.csproj b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/RapidField.SolidInstruments.Messaging.InMemory.UnitTests.csproj index 00af06d6..76131cd1 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/RapidField.SolidInstruments.Messaging.InMemory.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/RapidField.SolidInstruments.Messaging.InMemory.UnitTests.csproj @@ -29,7 +29,12 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - + + + + + + + \ No newline at end of file From 62f9f592c940b3c02b7f12878cd3eb5851040081 Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Wed, 20 May 2020 20:23:58 -0500 Subject: [PATCH 28/55] Improving nomenclature. --- ...RapidField.SolidInstruments.Collections.md | 10 +- ...apidField.SolidInstruments.Cryptography.md | 8 +- .../{IPinnedBuffer.cs => IPinnedMemory.cs} | 10 +- ...nnedBuffer.cs => IReadOnlyPinnedMemory.cs} | 14 +-- .../{PinnedBuffer.cs => PinnedMemory.cs} | 99 ++++++++++--------- .../README.md | 2 +- ...dField.SolidInstruments.Collections.csproj | 4 +- ...innedBuffer.cs => ReadOnlyPinnedMemory.cs} | 70 ++++++------- .../ISymmetricEncryptorDecryptorExtensions.cs | 4 +- .../HardenedRandomNumberGenerator.cs | 6 +- .../Hashing/HashingProcessor.cs | 2 +- .../{ISecureBuffer.cs => ISecureMemory.cs} | 10 +- .../Secrets/CascadingSymmetricKeySecret.cs | 26 ++--- .../Secrets/GuidSecret.cs | 10 +- .../Secrets/IReadOnlySecret.cs | 2 +- .../Secrets/ISecretVault.cs | 2 +- .../Secrets/NumericSecret.cs | 10 +- .../Secrets/Secret.cs | 46 ++++----- .../Secrets/SecretVault.cs | 2 +- .../Secrets/StringSecret.cs | 10 +- .../Secrets/SymmetricKeySecret.cs | 26 ++--- .../Secrets/X509CertificateSecret.cs | 10 +- .../{SecureBuffer.cs => SecureMemory.cs} | 70 ++++++------- .../Symmetric/CascadingSymmetricKey.cs | 22 ++--- .../Symmetric/ICascadingSymmetricKey.cs | 2 +- .../Symmetric/ISymmetricKey.cs | 4 +- .../Symmetric/ISymmetricProcessor.cs | 4 +- .../Symmetric/SymmetricKey.cs | 54 +++++----- .../Symmetric/SymmetricKeyCipher.cs | 24 ++--- .../Symmetric/SymmetricProcessor.cs | 20 ++-- ...nedBufferTests.cs => PinnedMemoryTests.cs} | 16 +-- .../CascadingSymmetricKeySecretTests.cs | 2 +- .../Secrets/SecretTests.cs | 8 +- .../Secrets/SecretVaultTests.cs | 6 +- .../Secrets/SymmetricKeySecretTests.cs | 2 +- ...ureBufferTests.cs => SecureMemoryTests.cs} | 56 +++++------ .../SymmetricBinaryProcessorTests.cs | 2 +- .../Symmetric/SymmetricKeyTests.cs | 6 +- .../ObjectFactoryTests.cs | 2 +- .../SimulatedInstrumentFactory.cs | 2 +- 40 files changed, 344 insertions(+), 341 deletions(-) rename src/RapidField.SolidInstruments.Collections/{IPinnedBuffer.cs => IPinnedMemory.cs} (86%) rename src/RapidField.SolidInstruments.Collections/{IReadOnlyPinnedBuffer.cs => IReadOnlyPinnedMemory.cs} (82%) rename src/RapidField.SolidInstruments.Collections/{PinnedBuffer.cs => PinnedMemory.cs} (70%) rename src/RapidField.SolidInstruments.Collections/{ReadOnlyPinnedBuffer.cs => ReadOnlyPinnedMemory.cs} (82%) rename src/RapidField.SolidInstruments.Cryptography/{ISecureBuffer.cs => ISecureMemory.cs} (73%) rename src/RapidField.SolidInstruments.Cryptography/{SecureBuffer.cs => SecureMemory.cs} (74%) rename test/RapidField.SolidInstruments.Collections.UnitTests/{PinnedBufferTests.cs => PinnedMemoryTests.cs} (91%) rename test/RapidField.SolidInstruments.Cryptography.UnitTests/{SecureBufferTests.cs => SecureMemoryTests.cs} (80%) diff --git a/doc/namespaces/RapidField.SolidInstruments.Collections.md b/doc/namespaces/RapidField.SolidInstruments.Collections.md index 377c6463..210669fe 100644 --- a/doc/namespaces/RapidField.SolidInstruments.Collections.md +++ b/doc/namespaces/RapidField.SolidInstruments.Collections.md @@ -110,9 +110,9 @@ public class ExampleClass
-#### Pinned buffers +#### Pinned memory -[PinnedBuffer](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Collections.PinnedBuffer.html) represents a fixed-length bit field that is pinned in memory. +[PinnedMemory](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Collections.PinnedMemory.html) represents a fixed-length bit field that is pinned in memory. ###### C# @@ -120,11 +120,11 @@ public class ExampleClass var field = new byte[8]; var overwriteWithZerosOnDispose = true; -using (var pinnedBuffer = new PinnedBuffer(field, overwriteWithZerosOnDispose)) +using (var pinnedMemory = new PinnedMemory(field, overwriteWithZerosOnDispose)) { // The field is now pinned in memory and will not be paged to disk. - pinnedBuffer[3] = 0xf6; - pinnedBuffer[5] = 0x4a; + pinnedMemory[3] = 0xf6; + pinnedMemory[5] = 0x4a; } // Optionally, zeros are written over the field upon disposal to diff --git a/doc/namespaces/RapidField.SolidInstruments.Cryptography.md b/doc/namespaces/RapidField.SolidInstruments.Cryptography.md index f18bec23..d191e2eb 100644 --- a/doc/namespaces/RapidField.SolidInstruments.Cryptography.md +++ b/doc/namespaces/RapidField.SolidInstruments.Cryptography.md @@ -36,17 +36,17 @@ The examples below are provided to help you get started using the features of th #### In-memory security -[SecureBuffer](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Cryptography.SecureBuffer.html) represents a fixed-length bit field that is pinned in memory and encrypted at rest. +[SecureMemory](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Cryptography.SecureMemory.html) represents a fixed-length bit field that is pinned in memory and encrypted at rest. ###### C# ```csharp // Allocates 1,024 bytes that are encrypted and pinned in memory. -using (var secureBuffer = new SecureBuffer(1024)) +using (var secureMemory = new SecureMemory(1024)) { - secureBuffer.Access(buffer => + secureMemory.Access(memory => { - // In this context, buffer is an unencrypted bit field. + // In this context, memory is an unencrypted bit field. }); } ``` diff --git a/src/RapidField.SolidInstruments.Collections/IPinnedBuffer.cs b/src/RapidField.SolidInstruments.Collections/IPinnedMemory.cs similarity index 86% rename from src/RapidField.SolidInstruments.Collections/IPinnedBuffer.cs rename to src/RapidField.SolidInstruments.Collections/IPinnedMemory.cs index a0883c75..881079c4 100644 --- a/src/RapidField.SolidInstruments.Collections/IPinnedBuffer.cs +++ b/src/RapidField.SolidInstruments.Collections/IPinnedMemory.cs @@ -11,9 +11,9 @@ namespace RapidField.SolidInstruments.Collections /// Represents a fixed-length bit field that is pinned in memory. ///
/// - /// The element type of the buffer. + /// The element type of the memory field. /// - public interface IPinnedBuffer : IEnumerable, IPinnedBuffer, IReadOnlyPinnedBuffer + public interface IPinnedMemory : IEnumerable, IPinnedMemory, IReadOnlyPinnedMemory where T : struct, IComparable, IEquatable { /// @@ -35,7 +35,7 @@ public interface IPinnedBuffer : IEnumerable, IPinnedBuffer, IReadOnlyPinn } /// - /// Gets a for the current . + /// Gets a for the current . /// /// /// The object is disposed. @@ -49,10 +49,10 @@ public Span Span /// /// Represents a fixed-length bit field that is pinned in memory. /// - public interface IPinnedBuffer : IReadOnlyPinnedBuffer + public interface IPinnedMemory : IReadOnlyPinnedMemory { /// - /// Overwrites the current with default values. + /// Overwrites the current with default values. /// /// /// The object is disposed. diff --git a/src/RapidField.SolidInstruments.Collections/IReadOnlyPinnedBuffer.cs b/src/RapidField.SolidInstruments.Collections/IReadOnlyPinnedMemory.cs similarity index 82% rename from src/RapidField.SolidInstruments.Collections/IReadOnlyPinnedBuffer.cs rename to src/RapidField.SolidInstruments.Collections/IReadOnlyPinnedMemory.cs index 70c3ac3e..2ad3d137 100644 --- a/src/RapidField.SolidInstruments.Collections/IReadOnlyPinnedBuffer.cs +++ b/src/RapidField.SolidInstruments.Collections/IReadOnlyPinnedMemory.cs @@ -11,9 +11,9 @@ namespace RapidField.SolidInstruments.Collections /// Represents a read-only, fixed-length bit field that is pinned in memory. /// /// - /// The element type of the buffer. + /// The element type of the memory field. /// - public interface IReadOnlyPinnedBuffer : IEnumerable, IReadOnlyPinnedBuffer + public interface IReadOnlyPinnedMemory : IEnumerable, IReadOnlyPinnedMemory where T : struct, IComparable, IEquatable { /// @@ -34,7 +34,7 @@ public T this[Int32 index] } /// - /// Gets a for the current . + /// Gets a for the current . /// /// /// The object is disposed. @@ -48,10 +48,10 @@ public ReadOnlySpan ReadOnlySpan /// /// Represents a read-only, fixed-length bit field that is pinned in memory. /// - public interface IReadOnlyPinnedBuffer : IAsyncDisposable, IDisposable + public interface IReadOnlyPinnedMemory : IAsyncDisposable, IDisposable { /// - /// Gets a value indicating whether or not the buffer is empty. + /// Gets a value indicating whether or not the memory field is empty. /// public Boolean IsEmpty { @@ -59,7 +59,7 @@ public Boolean IsEmpty } /// - /// Gets the number of elements comprising the buffer. + /// Gets the number of elements comprising the memory field. /// public Int32 Length { @@ -67,7 +67,7 @@ public Int32 Length } /// - /// Gets the length of the buffer, in bytes. + /// Gets the length of the memory field, in bytes. /// public Int32 LengthInBytes { diff --git a/src/RapidField.SolidInstruments.Collections/PinnedBuffer.cs b/src/RapidField.SolidInstruments.Collections/PinnedMemory.cs similarity index 70% rename from src/RapidField.SolidInstruments.Collections/PinnedBuffer.cs rename to src/RapidField.SolidInstruments.Collections/PinnedMemory.cs index 555f1174..65475aab 100644 --- a/src/RapidField.SolidInstruments.Collections/PinnedBuffer.cs +++ b/src/RapidField.SolidInstruments.Collections/PinnedMemory.cs @@ -13,12 +13,12 @@ namespace RapidField.SolidInstruments.Collections /// Represents a fixed-length bit field that is pinned in memory. /// /// - /// is the default implementation of . + /// is the default implementation of . /// - public class PinnedBuffer : PinnedBuffer + public class PinnedMemory : PinnedMemory { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// The length of the array. @@ -26,67 +26,67 @@ public class PinnedBuffer : PinnedBuffer /// /// is less than zero. /// - public PinnedBuffer(Int32 length) + public PinnedMemory(Int32 length) : base(length) { return; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// - /// The structure collection comprising the buffer. + /// The structure collection comprising the memory field. /// /// /// is . /// - public PinnedBuffer(Byte[] field) + public PinnedMemory(Byte[] field) : base(field) { return; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// - /// The structure collection comprising the buffer. + /// The structure collection comprising the memory field. /// /// - /// A value indicating whether or not to overwrite the buffer with zeros upon disposal. The default value is + /// A value indicating whether or not to overwrite the memory field with zeros upon disposal. The default value is /// . /// /// /// is less than zero. /// - public PinnedBuffer(Int32 length, Boolean overwriteWithZerosOnDispose) + public PinnedMemory(Int32 length, Boolean overwriteWithZerosOnDispose) : base(length, overwriteWithZerosOnDispose) { return; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// - /// The structure collection comprising the buffer. + /// The structure collection comprising the memory field. /// /// - /// A value indicating whether or not to overwrite the buffer with zeros upon disposal. The default value is + /// A value indicating whether or not to overwrite the memory field with zeros upon disposal. The default value is /// . /// /// /// is . /// - public PinnedBuffer(Byte[] field, Boolean overwriteWithZerosOnDispose) + public PinnedMemory(Byte[] field, Boolean overwriteWithZerosOnDispose) : base(field, overwriteWithZerosOnDispose) { return; } /// - /// Releases all resources consumed by the current . + /// Releases all resources consumed by the current . /// /// /// A value indicating whether or not managed resources should be released. @@ -98,16 +98,16 @@ public PinnedBuffer(Byte[] field, Boolean overwriteWithZerosOnDispose) /// Represents a fixed-length bit field that is pinned in memory. ///
/// - /// is the default implementation of . + /// is the default implementation of . /// /// - /// The element type of the buffer. + /// The element type of the memory field. /// - public class PinnedBuffer : ReadOnlyPinnedBuffer, IPinnedBuffer + public class PinnedMemory : ReadOnlyPinnedMemory, IPinnedMemory where T : struct, IComparable, IComparable, IConvertible, IEquatable, IFormattable { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// The length of the array. @@ -115,60 +115,60 @@ public class PinnedBuffer : ReadOnlyPinnedBuffer, IPinnedBuffer /// /// is less than or equal to zero. /// - public PinnedBuffer(Int32 length) + public PinnedMemory(Int32 length) : this(length, false) { return; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// - /// The structure collection comprising the buffer. + /// The structure collection comprising the memory field. /// /// /// is . /// - public PinnedBuffer(T[] field) + public PinnedMemory(T[] field) : this(field, false) { return; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// The length of the array. /// /// - /// A value indicating whether or not to overwrite the buffer with default values upon disposal. The default value is + /// A value indicating whether or not to overwrite the memory field with default values upon disposal. The default value is /// . /// /// /// is less than or equal to zero. /// - public PinnedBuffer(Int32 length, Boolean overwriteWithZerosOnDispose) + public PinnedMemory(Int32 length, Boolean overwriteWithZerosOnDispose) : this(new T[length.RejectIf().IsLessThanOrEqualTo(0, nameof(length))], overwriteWithZerosOnDispose) { return; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// - /// The structure collection comprising the buffer. + /// The structure collection comprising the memory field. /// /// - /// A value indicating whether or not to overwrite the buffer with default values upon disposal. The default value is + /// A value indicating whether or not to overwrite the memory field with default values upon disposal. The default value is /// . /// /// /// is . /// - public PinnedBuffer(T[] field, Boolean overwriteWithZerosOnDispose) + public PinnedMemory(T[] field, Boolean overwriteWithZerosOnDispose) : base(field) { OverwriteWithZerosOnDispose = overwriteWithZerosOnDispose; @@ -193,47 +193,47 @@ public PinnedBuffer(T[] field, Boolean overwriteWithZerosOnDispose) } /// - /// Facilitates implicit array to casting. + /// Facilitates implicit array to casting. /// /// /// The object to cast from. /// - public static implicit operator PinnedBuffer(T[] target) => target is null ? null : new PinnedBuffer(target); + public static implicit operator PinnedMemory(T[] target) => target is null ? null : new PinnedMemory(target); /// - /// Facilitates implicit to casting. + /// Facilitates implicit to casting. /// /// /// The object to cast from. /// - public static implicit operator ReadOnlySpan(PinnedBuffer target) => target is null ? ReadOnlySpan.Empty : target.ReadOnlySpan; + public static implicit operator ReadOnlySpan(PinnedMemory target) => target is null ? ReadOnlySpan.Empty : target.ReadOnlySpan; /// - /// Facilitates implicit to casting. + /// Facilitates implicit to casting. /// /// /// The object to cast from. /// - public static implicit operator Span(PinnedBuffer target) => target is null ? Span.Empty : target.Span; + public static implicit operator Span(PinnedMemory target) => target is null ? Span.Empty : target.Span; /// - /// Facilitates implicit to array casting. + /// Facilitates implicit to array casting. /// /// /// The object to cast from. /// - public static implicit operator T[](PinnedBuffer target) => target?.Field; + public static implicit operator T[](PinnedMemory target) => target?.Field; /// - /// Returns an enumerator that iterates through the elements of the current . + /// Returns an enumerator that iterates through the elements of the current . /// /// - /// An enumerator that iterates through the elements of the current . + /// An enumerator that iterates through the elements of the current . /// IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// - /// Overwrites the current with default values. + /// Overwrites the current with default values. /// /// /// The object is disposed. @@ -245,7 +245,7 @@ public void OverwriteWithZeros() } /// - /// Releases all resources consumed by the current . + /// Releases all resources consumed by the current . /// /// /// A value indicating whether or not managed resources should be released. @@ -266,17 +266,20 @@ protected override void Dispose(Boolean disposing) } /// - /// Overwrites the specified with default values. + /// Overwrites the specified with default values. /// /// - /// The element type of the buffer. + /// The element type of the memory field. /// + /// + /// The memory field to overwrite. + /// [DebuggerHidden] - private static void OverwriteWithZeros(PinnedBuffer buffer) - where TElement : struct, IComparable, IComparable, IConvertible, IEquatable, IFormattable => Array.Clear(buffer.Field, 0, buffer.Length); + private static void OverwriteWithZeros(PinnedMemory memory) + where TElement : struct, IComparable, IComparable, IConvertible, IEquatable, IFormattable => Array.Clear(memory.Field, 0, memory.Length); /// - /// Gets a for the current . + /// Gets a for the current . /// /// /// The object is disposed. diff --git a/src/RapidField.SolidInstruments.Collections/README.md b/src/RapidField.SolidInstruments.Collections/README.md index 45a4ed61..939552ec 100644 --- a/src/RapidField.SolidInstruments.Collections/README.md +++ b/src/RapidField.SolidInstruments.Collections/README.md @@ -17,7 +17,7 @@ This library exposes useful collection types and features, including: - circular buffers - infinite sequences -- pinned buffers +- pinned memory - tree composition and traversal ## License diff --git a/src/RapidField.SolidInstruments.Collections/RapidField.SolidInstruments.Collections.csproj b/src/RapidField.SolidInstruments.Collections/RapidField.SolidInstruments.Collections.csproj index 1ea0255d..dd043d1b 100644 --- a/src/RapidField.SolidInstruments.Collections/RapidField.SolidInstruments.Collections.csproj +++ b/src/RapidField.SolidInstruments.Collections/RapidField.SolidInstruments.Collections.csproj @@ -8,7 +8,7 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in RapidField Copyright (c) RapidField LLC. All rights reserved. Solid Instruments - This library exposes useful collection types and features, including: circular buffers, infinite sequences, pinned buffers, tree composition and traversal. + This library exposes useful collection types and features, including: circular buffers, infinite sequences, pinned memory, tree composition and traversal. $(BuildVersion) netstandard2.1 latest @@ -19,7 +19,7 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in LICENSE.txt https://www.solidinstruments.com Icon.Collections.128w.png - solid-instruments;collections;circular-buffer;infinite-sequence;pinned-buffer;tree + solid-instruments;collections;circular-buffer;infinite-sequence;pinned-memory;tree bin\Debug\netstandard2.0\RapidField.SolidInstruments.Collections.xml diff --git a/src/RapidField.SolidInstruments.Collections/ReadOnlyPinnedBuffer.cs b/src/RapidField.SolidInstruments.Collections/ReadOnlyPinnedMemory.cs similarity index 82% rename from src/RapidField.SolidInstruments.Collections/ReadOnlyPinnedBuffer.cs rename to src/RapidField.SolidInstruments.Collections/ReadOnlyPinnedMemory.cs index c5c10c0d..139a39b0 100644 --- a/src/RapidField.SolidInstruments.Collections/ReadOnlyPinnedBuffer.cs +++ b/src/RapidField.SolidInstruments.Collections/ReadOnlyPinnedMemory.cs @@ -19,12 +19,12 @@ namespace RapidField.SolidInstruments.Collections /// Represents a read-only, fixed-length bit field that is pinned in memory. ///
/// - /// is the default implementation of . + /// is the default implementation of . /// - public class ReadOnlyPinnedBuffer : ReadOnlyPinnedBuffer + public class ReadOnlyPinnedMemory : ReadOnlyPinnedMemory { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// The length of the array. @@ -32,29 +32,29 @@ public class ReadOnlyPinnedBuffer : ReadOnlyPinnedBuffer /// /// is less than zero. /// - public ReadOnlyPinnedBuffer(Int32 length) + public ReadOnlyPinnedMemory(Int32 length) : base(length) { return; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// - /// The structure collection comprising the buffer. + /// The structure collection comprising the memory field. /// /// /// is . /// - public ReadOnlyPinnedBuffer(Byte[] field) + public ReadOnlyPinnedMemory(Byte[] field) : base(field) { return; } /// - /// Releases all resources consumed by the current . + /// Releases all resources consumed by the current . /// /// /// A value indicating whether or not managed resources should be released. @@ -66,16 +66,16 @@ public ReadOnlyPinnedBuffer(Byte[] field) /// Represents a read-only, fixed-length bit field that is pinned in memory. ///
/// - /// is the default implementation of . + /// is the default implementation of . /// /// - /// The element type of the buffer. + /// The element type of the memory field. /// - public class ReadOnlyPinnedBuffer : Instrument, IReadOnlyPinnedBuffer + public class ReadOnlyPinnedMemory : Instrument, IReadOnlyPinnedMemory where T : struct, IComparable, IComparable, IConvertible, IEquatable, IFormattable { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// The length of the array. @@ -83,22 +83,22 @@ public class ReadOnlyPinnedBuffer : Instrument, IReadOnlyPinnedBuffer /// /// is less than zero. /// - public ReadOnlyPinnedBuffer(Int32 length) + public ReadOnlyPinnedMemory(Int32 length) : this(new T[length.RejectIf().IsLessThan(0, nameof(length))]) { return; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// - /// The structure collection comprising the buffer. + /// The structure collection comprising the memory field. /// /// /// is . /// - public ReadOnlyPinnedBuffer(T[] field) + public ReadOnlyPinnedMemory(T[] field) : base(ConcurrencyControlMode.SingleThreadLock) { Handle = GCHandle.Alloc(field.RejectIf().IsNull(nameof(field)).TargetArgument, GCHandleType.Pinned); @@ -123,34 +123,34 @@ public ReadOnlyPinnedBuffer(T[] field) public T this[Int32 index] => Field[index]; /// - /// Facilitates implicit array to casting. + /// Facilitates implicit array to casting. /// /// /// The object to cast from. /// - public static implicit operator ReadOnlyPinnedBuffer(T[] target) => target is null ? null : new ReadOnlyPinnedBuffer(target); + public static implicit operator ReadOnlyPinnedMemory(T[] target) => target is null ? null : new ReadOnlyPinnedMemory(target); /// - /// Facilitates implicit to casting. + /// Facilitates implicit to casting. /// /// /// The object to cast from. /// - public static implicit operator ReadOnlySpan(ReadOnlyPinnedBuffer target) => target is null ? ReadOnlySpan.Empty : target.ReadOnlySpan; + public static implicit operator ReadOnlySpan(ReadOnlyPinnedMemory target) => target is null ? ReadOnlySpan.Empty : target.ReadOnlySpan; /// - /// Facilitates implicit to array casting. + /// Facilitates implicit to array casting. /// /// /// The object to cast from. /// - public static implicit operator T[](ReadOnlyPinnedBuffer target) => target?.Field; + public static implicit operator T[](ReadOnlyPinnedMemory target) => target?.Field; /// - /// Returns an enumerator that iterates through the elements of the current . + /// Returns an enumerator that iterates through the elements of the current . /// /// - /// An enumerator that iterates through the elements of the current . + /// An enumerator that iterates through the elements of the current . /// public IEnumerator GetEnumerator() { @@ -161,10 +161,10 @@ public IEnumerator GetEnumerator() } /// - /// Returns an enumerator that iterates through the elements of the current . + /// Returns an enumerator that iterates through the elements of the current . /// /// - /// An enumerator that iterates through the elements of the current . + /// An enumerator that iterates through the elements of the current . /// IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); @@ -182,9 +182,9 @@ public override Int32 GetHashCode() { hashCode ^= element.GetHashCode() ^ 0x66666666; - using (var buffer = new ReadOnlyPinnedBuffer(hashCode.ToByteArray())) + using (var memory = new ReadOnlyPinnedMemory(hashCode.ToByteArray())) { - hashCode = buffer.ComputeThirtyTwoBitHash() ^ 0x33333333; + hashCode = memory.ComputeThirtyTwoBitHash() ^ 0x33333333; } } @@ -192,15 +192,15 @@ public override Int32 GetHashCode() } /// - /// Converts the value of the current to its equivalent string representation. + /// Converts the value of the current to its equivalent string representation. /// /// - /// A string representation of the current . + /// A string representation of the current . /// public override String ToString() => $"{{ {nameof(Length)}: {Length}, {nameof(LengthInBytes)}: {LengthInBytes} }}"; /// - /// Releases all resources consumed by the current . + /// Releases all resources consumed by the current . /// /// /// A value indicating whether or not managed resources should be released. @@ -228,12 +228,12 @@ protected override void Dispose(Boolean disposing) private Memory InitializeFieldMemory() => new Memory(Field); /// - /// Gets a value indicating whether or not the buffer is empty. + /// Gets a value indicating whether or not the memory field is empty. /// public Boolean IsEmpty => Length == 0; /// - /// Gets the number of elements comprising the buffer. + /// Gets the number of elements comprising the memory field. /// public Int32 Length { @@ -241,12 +241,12 @@ public Int32 Length } /// - /// Gets the length of the buffer, in bytes. + /// Gets the length of the memory field, in bytes. /// public Int32 LengthInBytes => (Length * StructureSize); /// - /// Gets a for the current . + /// Gets a for the current . /// /// /// The object is disposed. diff --git a/src/RapidField.SolidInstruments.Cryptography/Extensions/ISymmetricEncryptorDecryptorExtensions.cs b/src/RapidField.SolidInstruments.Cryptography/Extensions/ISymmetricEncryptorDecryptorExtensions.cs index f57a9bc1..3e092334 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Extensions/ISymmetricEncryptorDecryptorExtensions.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Extensions/ISymmetricEncryptorDecryptorExtensions.cs @@ -66,7 +66,7 @@ public static class ISymmetricProcessorExtensions /// /// An exception was raised during decryption or deserialization. /// - public static T DecryptFromBase64String(this ISymmetricProcessor target, String ciphertext, ISecureBuffer key, SymmetricAlgorithmSpecification algorithm) => target.Decrypt(Convert.FromBase64String(ciphertext), key, algorithm); + public static T DecryptFromBase64String(this ISymmetricProcessor target, String ciphertext, ISecureMemory key, SymmetricAlgorithmSpecification algorithm) => target.Decrypt(Convert.FromBase64String(ciphertext), key, algorithm); /// /// Encrypts the specified plaintext object to a Base64 string. @@ -115,6 +115,6 @@ public static class ISymmetricProcessorExtensions /// /// An exception was raised during encryption or serialization. /// - public static String EncryptToBase64String(this ISymmetricProcessor target, T plaintextObject, ISecureBuffer key, SymmetricAlgorithmSpecification algorithm) => Convert.ToBase64String(target.Encrypt(plaintextObject, key, algorithm)); + public static String EncryptToBase64String(this ISymmetricProcessor target, T plaintextObject, ISecureMemory key, SymmetricAlgorithmSpecification algorithm) => Convert.ToBase64String(target.Encrypt(plaintextObject, key, algorithm)); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/HardenedRandomNumberGenerator.cs b/src/RapidField.SolidInstruments.Cryptography/HardenedRandomNumberGenerator.cs index 8a9933fb..c5b22038 100644 --- a/src/RapidField.SolidInstruments.Cryptography/HardenedRandomNumberGenerator.cs +++ b/src/RapidField.SolidInstruments.Cryptography/HardenedRandomNumberGenerator.cs @@ -131,15 +131,15 @@ protected override void Dispose(Boolean disposing) [DebuggerHidden] private void PermuteBuffer() { - using (var sourceBytes = new PinnedBuffer(BufferPermutationSourceLengthInBytes, true)) + using (var sourceBytes = new PinnedMemory(BufferPermutationSourceLengthInBytes, true)) { SourceRandomnessProvider.GetBytes(sourceBytes); - using (var privateKey = new PinnedBuffer(CipherKeyLengthInBytes, true)) + using (var privateKey = new PinnedMemory(CipherKeyLengthInBytes, true)) { SourceRandomnessProvider.GetBytes(privateKey); - using (var initializationVector = new PinnedBuffer(CipherBlockLengthInBytes, true)) + using (var initializationVector = new PinnedMemory(CipherBlockLengthInBytes, true)) { SourceRandomnessProvider.GetBytes(initializationVector); diff --git a/src/RapidField.SolidInstruments.Cryptography/Hashing/HashingProcessor.cs b/src/RapidField.SolidInstruments.Cryptography/Hashing/HashingProcessor.cs index d0e2be4c..49e0eaee 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Hashing/HashingProcessor.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Hashing/HashingProcessor.cs @@ -132,7 +132,7 @@ public Byte[] CalculateHash(Byte[] plaintextBinaryArray, HashingAlgorithmSpecifi using (var hashAlgorithm = algorithm.ToHashAlgorithm()) { - Array.Copy(hashAlgorithm.ComputeHash(plaintextBinaryArray), hashValue, digestLengthInBytes); + Array.Copy(hashAlgorithm.ComputeHash(plaintextBuffer), hashValue, digestLengthInBytes); } if (applySalt) diff --git a/src/RapidField.SolidInstruments.Cryptography/ISecureBuffer.cs b/src/RapidField.SolidInstruments.Cryptography/ISecureMemory.cs similarity index 73% rename from src/RapidField.SolidInstruments.Cryptography/ISecureBuffer.cs rename to src/RapidField.SolidInstruments.Cryptography/ISecureMemory.cs index 9f5e55b0..0db248ef 100644 --- a/src/RapidField.SolidInstruments.Cryptography/ISecureBuffer.cs +++ b/src/RapidField.SolidInstruments.Cryptography/ISecureMemory.cs @@ -8,12 +8,12 @@ namespace RapidField.SolidInstruments.Cryptography { /// - /// Represents a fixed-length bit field that is pinned in memory and encrypted at rest. + /// Represents a fixed-length bit field that is pinned in bit field and encrypted at rest. /// - public interface ISecureBuffer : IAsyncDisposable, IDisposable + public interface ISecureMemory : IAsyncDisposable, IDisposable { /// - /// Decrypts the buffer, performs the specified operation against the pinned plaintext and encrypts the buffer as a + /// Decrypts the bit field, performs the specified operation against the pinned plaintext and encrypts the bit field as a /// thread-safe, atomic operation. /// /// @@ -25,10 +25,10 @@ public interface ISecureBuffer : IAsyncDisposable, IDisposable /// /// The object is disposed. /// - public void Access(Action action); + public void Access(Action action); /// - /// Gets the length of the buffer, in bytes. + /// Gets the length of the bit field, in bytes. /// public Int32 LengthInBytes { diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/CascadingSymmetricKeySecret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/CascadingSymmetricKeySecret.cs index 873c6705..1beedd02 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/CascadingSymmetricKeySecret.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/CascadingSymmetricKeySecret.cs @@ -65,7 +65,7 @@ public static CascadingSymmetricKeySecret FromValue(String name, CascadingSymmet /// Creates a using the provided bytes. /// /// - /// A pinned buffer representing a . + /// A pinned bit field representing a . /// /// /// A token that represents and manages contextual thread safety. @@ -73,18 +73,18 @@ public static CascadingSymmetricKeySecret FromValue(String name, CascadingSymmet /// /// The resulting . /// - protected sealed override CascadingSymmetricKey ConvertBytesToValue(IReadOnlyPinnedBuffer bytes, ConcurrencyControlToken controlToken) + protected sealed override CascadingSymmetricKey ConvertBytesToValue(IReadOnlyPinnedMemory bytes, ConcurrencyControlToken controlToken) { var result = (CascadingSymmetricKey)null; - using (var secureBuffer = new SecureBuffer(bytes.Length)) + using (var secureMemory = new SecureMemory(bytes.Length)) { - secureBuffer.Access(buffer => + secureMemory.Access(memory => { - bytes.ReadOnlySpan.CopyTo(buffer); + bytes.ReadOnlySpan.CopyTo(memory); }); - result = CascadingSymmetricKey.FromBuffer(secureBuffer); + result = CascadingSymmetricKey.FromBuffer(secureMemory); } return result; @@ -92,7 +92,7 @@ protected sealed override CascadingSymmetricKey ConvertBytesToValue(IReadOnlyPin /// /// Gets the bytes of , pins them in memory and returns the resulting - /// . + /// . /// /// /// The secret value. @@ -101,17 +101,17 @@ protected sealed override CascadingSymmetricKey ConvertBytesToValue(IReadOnlyPin /// A token that represents and manages contextual thread safety. /// /// - /// as a pinned buffer. + /// as a pinned memory. /// - protected sealed override IReadOnlyPinnedBuffer ConvertValueToBytes(CascadingSymmetricKey value, ConcurrencyControlToken controlToken) + protected sealed override IReadOnlyPinnedMemory ConvertValueToBytes(CascadingSymmetricKey value, ConcurrencyControlToken controlToken) { - var result = (ReadOnlyPinnedBuffer)null; + var result = (ReadOnlyPinnedMemory)null; - using (var secureBuffer = value.ToBuffer()) + using (var secureMemory = value.ToBuffer()) { - secureBuffer.Access(buffer => + secureMemory.Access(memory => { - result = new ReadOnlyPinnedBuffer(buffer.ToArray()); + result = new ReadOnlyPinnedMemory(memory.ToArray()); }); } diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/GuidSecret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/GuidSecret.cs index c1cfe206..c0f16293 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/GuidSecret.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/GuidSecret.cs @@ -61,7 +61,7 @@ public static GuidSecret FromValue(String name, Guid value) /// Creates a using the provided bytes. ///
/// - /// A pinned buffer representing a . + /// Pinned memory representing a . /// /// /// A token that represents and manages contextual thread safety. @@ -69,11 +69,11 @@ public static GuidSecret FromValue(String name, Guid value) /// /// The resulting . /// - protected sealed override Guid ConvertBytesToValue(IReadOnlyPinnedBuffer bytes, ConcurrencyControlToken controlToken) => new Guid(bytes.ReadOnlySpan); + protected sealed override Guid ConvertBytesToValue(IReadOnlyPinnedMemory bytes, ConcurrencyControlToken controlToken) => new Guid(bytes.ReadOnlySpan); /// /// Gets the bytes of , pins them in memory and returns the resulting - /// . + /// . /// /// /// The secret value. @@ -82,9 +82,9 @@ public static GuidSecret FromValue(String name, Guid value) /// A token that represents and manages contextual thread safety. /// /// - /// as a pinned buffer. + /// as pinned memory. /// - protected sealed override IReadOnlyPinnedBuffer ConvertValueToBytes(Guid value, ConcurrencyControlToken controlToken) => new PinnedBuffer(value.ToByteArray(), true); + protected sealed override IReadOnlyPinnedMemory ConvertValueToBytes(Guid value, ConcurrencyControlToken controlToken) => new PinnedMemory(value.ToByteArray(), true); /// /// Releases all resources consumed by the current . diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/IReadOnlySecret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/IReadOnlySecret.cs index bb7ab6e3..0cc9bdab 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/IReadOnlySecret.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/IReadOnlySecret.cs @@ -63,7 +63,7 @@ public interface IReadOnlySecret : IAsyncDisposable, IDisposable /// /// raised an exception. /// - public void Read(Action> readAction); + public void Read(Action> readAction); /// /// Gets a value indicating whether or not the current has a value. diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretVault.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretVault.cs index 5384cf64..4378bf92 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretVault.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretVault.cs @@ -201,7 +201,7 @@ public interface ISecretVault : IAsyncDisposable, IDisposable /// raised an exception -or- the secret vault does not contain a valid secret of the /// specified type. /// - public Task ReadAsync(String name, Action> readAction); + public Task ReadAsync(String name, Action> readAction); /// /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/NumericSecret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/NumericSecret.cs index b9eed005..e1a4d339 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/NumericSecret.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/NumericSecret.cs @@ -62,7 +62,7 @@ public static NumericSecret FromValue(String name, Double value) /// Creates a using the provided bytes. /// /// - /// A pinned buffer representing a . + /// Pinned memory representing a . /// /// /// A token that represents and manages contextual thread safety. @@ -70,11 +70,11 @@ public static NumericSecret FromValue(String name, Double value) /// /// The resulting . /// - protected sealed override Double ConvertBytesToValue(IReadOnlyPinnedBuffer bytes, ConcurrencyControlToken controlToken) => BitConverter.ToDouble(bytes.ReadOnlySpan); + protected sealed override Double ConvertBytesToValue(IReadOnlyPinnedMemory bytes, ConcurrencyControlToken controlToken) => BitConverter.ToDouble(bytes.ReadOnlySpan); /// /// Gets the bytes of , pins them in memory and returns the resulting - /// . + /// . /// /// /// The secret value. @@ -83,9 +83,9 @@ public static NumericSecret FromValue(String name, Double value) /// A token that represents and manages contextual thread safety. /// /// - /// as a pinned buffer. + /// as pinned memory. /// - protected sealed override IReadOnlyPinnedBuffer ConvertValueToBytes(Double value, ConcurrencyControlToken controlToken) => new PinnedBuffer(value.ToByteArray(), true); + protected sealed override IReadOnlyPinnedMemory ConvertValueToBytes(Double value, ConcurrencyControlToken controlToken) => new PinnedMemory(value.ToByteArray(), true); /// /// Releases all resources consumed by the current . diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/Secret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/Secret.cs index a9579263..2a40df3b 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/Secret.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/Secret.cs @@ -14,7 +14,7 @@ namespace RapidField.SolidInstruments.Cryptography.Secrets /// /// Represents a named secret bit field that is pinned in memory and encrypted at rest. /// - public sealed class Secret : Secret> + public sealed class Secret : Secret> { /// /// Initializes a new instance of the class. @@ -56,27 +56,27 @@ public static Secret FromValue(String name, Byte[] value) { value = value.RejectIf().IsNull(nameof(value)); var secret = new Secret(name); - secret.Write(() => new PinnedBuffer(value)); + secret.Write(() => new PinnedMemory(value)); return secret; } /// - /// Creates a using the provided bytes. + /// Creates a using the provided bytes. /// /// - /// A pinned buffer. + /// Pinned memory. /// /// /// A token that represents and manages contextual thread safety. /// /// - /// The resulting . + /// The resulting . /// - protected sealed override IReadOnlyPinnedBuffer ConvertBytesToValue(IReadOnlyPinnedBuffer bytes, ConcurrencyControlToken controlToken) => bytes; + protected sealed override IReadOnlyPinnedMemory ConvertBytesToValue(IReadOnlyPinnedMemory bytes, ConcurrencyControlToken controlToken) => bytes; /// /// Gets the bytes of , pins them in memory and returns the resulting - /// . + /// . /// /// /// The secret value. @@ -85,9 +85,9 @@ public static Secret FromValue(String name, Byte[] value) /// A token that represents and manages contextual thread safety. /// /// - /// as a pinned buffer. + /// as pinned memory. /// - protected sealed override IReadOnlyPinnedBuffer ConvertValueToBytes(IReadOnlyPinnedBuffer value, ConcurrencyControlToken controlToken) => value; + protected sealed override IReadOnlyPinnedMemory ConvertValueToBytes(IReadOnlyPinnedMemory value, ConcurrencyControlToken controlToken) => value; /// /// Releases all resources consumed by the current . @@ -167,7 +167,7 @@ public override Int32 GetHashCode() /// /// raised an exception. /// - public void Read(Action> readAction) + public void Read(Action> readAction) { using (var controlToken = StateControl.Enter()) { @@ -241,7 +241,7 @@ public void Write(Func writeFunction) /// Creates a using the provided bytes. /// /// - /// A pinned buffer representing a . + /// Pinned memory representing a . /// /// /// A token that represents and manages contextual thread safety. @@ -249,11 +249,11 @@ public void Write(Func writeFunction) /// /// The resulting . /// - protected abstract TValue ConvertBytesToValue(IReadOnlyPinnedBuffer bytes, ConcurrencyControlToken controlToken); + protected abstract TValue ConvertBytesToValue(IReadOnlyPinnedMemory bytes, ConcurrencyControlToken controlToken); /// /// Gets the bytes of , pins them in memory and returns the resulting - /// . + /// . /// /// /// The secret value. @@ -262,9 +262,9 @@ public void Write(Func writeFunction) /// A token that represents and manages contextual thread safety. /// /// - /// as a pinned buffer. + /// as pinned memory. /// - protected abstract IReadOnlyPinnedBuffer ConvertValueToBytes(TValue value, ConcurrencyControlToken controlToken); + protected abstract IReadOnlyPinnedMemory ConvertValueToBytes(TValue value, ConcurrencyControlToken controlToken); /// /// Releases all resources consumed by the current . @@ -304,7 +304,7 @@ protected override void Dispose(Boolean disposing) /// raised an exception. /// [DebuggerHidden] - private void Read(Action> readAction, ConcurrencyControlToken controlToken) + private void Read(Action> readAction, ConcurrencyControlToken controlToken) { if (HasValue == false) { @@ -315,11 +315,11 @@ private void Read(Action> readAction, ConcurrencyCon { if (SecureValueBuffer is null) { - using (var pinnedBuffer = new ReadOnlyPinnedBuffer(0)) + using (var memory = new ReadOnlyPinnedMemory(0)) { // Because secure buffers cannot have length zero, this is here to handle cases in which write operations // produce empty buffers. - readAction(pinnedBuffer); + readAction(memory); } return; @@ -365,9 +365,9 @@ private void Read(Action readAction, ConcurrencyControlToken controlToke { try { - Read((pinnedBuffer) => + Read((memory) => { - readAction(ConvertBytesToValue(pinnedBuffer, controlToken)); + readAction(ConvertBytesToValue(memory, controlToken)); }, controlToken); } catch (ObjectDisposedException) @@ -424,12 +424,12 @@ private void Write(Func writeFunction, ConcurrencyControlToken controlTo if (SecureValueBuffer is null) { - SecureValueBuffer = new SecureBuffer(valueBuffer.LengthInBytes); + SecureValueBuffer = new SecureMemory(valueBuffer.LengthInBytes); } else if (SecureValueBuffer.LengthInBytes != valueBuffer.LengthInBytes) { SecureValueBuffer.Dispose(); - SecureValueBuffer = new SecureBuffer(valueBuffer.LengthInBytes); + SecureValueBuffer = new SecureMemory(valueBuffer.LengthInBytes); } SecureValueBuffer.Access(secureBuffer => @@ -480,6 +480,6 @@ public String Name /// Represents the encrypted field in which the secure value is stored. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private ISecureBuffer SecureValueBuffer; + private ISecureMemory SecureValueBuffer; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretVault.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretVault.cs index 79fc7e95..b82f1a95 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretVault.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretVault.cs @@ -242,7 +242,7 @@ public void Clear() /// raised an exception -or- the secret vault does not contain a valid secret of the /// specified type. /// - public Task ReadAsync(String name, Action> readAction) => ReadAsync>(name, readAction.RejectIf().IsNull(nameof(readAction))); + public Task ReadAsync(String name, Action> readAction) => ReadAsync>(name, readAction.RejectIf().IsNull(nameof(readAction))); /// /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/StringSecret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/StringSecret.cs index 3480e268..65f42ca9 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/StringSecret.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/StringSecret.cs @@ -64,7 +64,7 @@ public static StringSecret FromValue(String name, String value) /// Creates a using the provided bytes. /// /// - /// A pinned buffer representing a . + /// Pinned memory representing a . /// /// /// A token that represents and manages contextual thread safety. @@ -72,11 +72,11 @@ public static StringSecret FromValue(String name, String value) /// /// The resulting . /// - protected sealed override String ConvertBytesToValue(IReadOnlyPinnedBuffer bytes, ConcurrencyControlToken controlToken) => Encoding.Unicode.GetString(bytes.ReadOnlySpan); + protected sealed override String ConvertBytesToValue(IReadOnlyPinnedMemory bytes, ConcurrencyControlToken controlToken) => Encoding.Unicode.GetString(bytes.ReadOnlySpan); /// /// Gets the bytes of , pins them in memory and returns the resulting - /// . + /// . /// /// /// The secret value. @@ -85,9 +85,9 @@ public static StringSecret FromValue(String name, String value) /// A token that represents and manages contextual thread safety. /// /// - /// as a pinned buffer. + /// as pinned memory. /// - protected sealed override IReadOnlyPinnedBuffer ConvertValueToBytes(String value, ConcurrencyControlToken controlToken) => new PinnedBuffer(Encoding.Unicode.GetBytes(value), true); + protected sealed override IReadOnlyPinnedMemory ConvertValueToBytes(String value, ConcurrencyControlToken controlToken) => new PinnedMemory(Encoding.Unicode.GetBytes(value), true); /// /// Releases all resources consumed by the current . diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/SymmetricKeySecret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/SymmetricKeySecret.cs index 9384cf32..7eee1a4d 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/SymmetricKeySecret.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/SymmetricKeySecret.cs @@ -65,7 +65,7 @@ public static SymmetricKeySecret FromValue(String name, SymmetricKey value) /// Creates a using the provided bytes. /// /// - /// A pinned buffer representing a . + /// A pinned bit field representing a . /// /// /// A token that represents and manages contextual thread safety. @@ -73,18 +73,18 @@ public static SymmetricKeySecret FromValue(String name, SymmetricKey value) /// /// The resulting . /// - protected sealed override SymmetricKey ConvertBytesToValue(IReadOnlyPinnedBuffer bytes, ConcurrencyControlToken controlToken) + protected sealed override SymmetricKey ConvertBytesToValue(IReadOnlyPinnedMemory bytes, ConcurrencyControlToken controlToken) { var result = (SymmetricKey)null; - using (var secureBuffer = new SecureBuffer(bytes.Length)) + using (var secureMemory = new SecureMemory(bytes.Length)) { - secureBuffer.Access(buffer => + secureMemory.Access(memory => { - bytes.ReadOnlySpan.CopyTo(buffer); + bytes.ReadOnlySpan.CopyTo(memory); }); - result = SymmetricKey.FromBuffer(secureBuffer); + result = SymmetricKey.FromBuffer(secureMemory); } return result; @@ -92,7 +92,7 @@ protected sealed override SymmetricKey ConvertBytesToValue(IReadOnlyPinnedBuffer /// /// Gets the bytes of , pins them in memory and returns the resulting - /// . + /// . /// /// /// The secret value. @@ -101,17 +101,17 @@ protected sealed override SymmetricKey ConvertBytesToValue(IReadOnlyPinnedBuffer /// A token that represents and manages contextual thread safety. /// /// - /// as a pinned buffer. + /// as a pinned memory. /// - protected sealed override IReadOnlyPinnedBuffer ConvertValueToBytes(SymmetricKey value, ConcurrencyControlToken controlToken) + protected sealed override IReadOnlyPinnedMemory ConvertValueToBytes(SymmetricKey value, ConcurrencyControlToken controlToken) { - var result = (ReadOnlyPinnedBuffer)null; + var result = (ReadOnlyPinnedMemory)null; - using (var secureBuffer = value.ToBuffer()) + using (var secureMemory = value.ToBuffer()) { - secureBuffer.Access(buffer => + secureMemory.Access(memory => { - result = new ReadOnlyPinnedBuffer(buffer.ToArray()); + result = new ReadOnlyPinnedMemory(memory.ToArray()); }); } diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/X509CertificateSecret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/X509CertificateSecret.cs index 54faaab2..cb3e25b3 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/X509CertificateSecret.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/X509CertificateSecret.cs @@ -65,7 +65,7 @@ public static X509CertificateSecret FromValue(String name, X509Certificate2 valu /// Creates a using the provided bytes. /// /// - /// A pinned buffer representing a . + /// Pinned memory representing a . /// /// /// A token that represents and manages contextual thread safety. @@ -73,11 +73,11 @@ public static X509CertificateSecret FromValue(String name, X509Certificate2 valu /// /// The resulting . /// - protected sealed override X509Certificate2 ConvertBytesToValue(IReadOnlyPinnedBuffer bytes, ConcurrencyControlToken controlToken) => new X509Certificate2(bytes.ToArray()); + protected sealed override X509Certificate2 ConvertBytesToValue(IReadOnlyPinnedMemory bytes, ConcurrencyControlToken controlToken) => new X509Certificate2(bytes.ToArray()); /// /// Gets the bytes of , pins them in memory and returns the resulting - /// . + /// . /// /// /// The secret value. @@ -86,9 +86,9 @@ public static X509CertificateSecret FromValue(String name, X509Certificate2 valu /// A token that represents and manages contextual thread safety. /// /// - /// as a pinned buffer. + /// as pinned memory. /// - protected sealed override IReadOnlyPinnedBuffer ConvertValueToBytes(X509Certificate2 value, ConcurrencyControlToken controlToken) => new PinnedBuffer(value.RawData); + protected sealed override IReadOnlyPinnedMemory ConvertValueToBytes(X509Certificate2 value, ConcurrencyControlToken controlToken) => new PinnedMemory(value.RawData); /// /// Releases all resources consumed by the current . diff --git a/src/RapidField.SolidInstruments.Cryptography/SecureBuffer.cs b/src/RapidField.SolidInstruments.Cryptography/SecureMemory.cs similarity index 74% rename from src/RapidField.SolidInstruments.Cryptography/SecureBuffer.cs rename to src/RapidField.SolidInstruments.Cryptography/SecureMemory.cs index 16d1cdc3..b8bdc6d5 100644 --- a/src/RapidField.SolidInstruments.Cryptography/SecureBuffer.cs +++ b/src/RapidField.SolidInstruments.Cryptography/SecureMemory.cs @@ -16,32 +16,32 @@ namespace RapidField.SolidInstruments.Cryptography { /// - /// Represents a fixed-length bit field that is pinned in memory and encrypted at rest. + /// Represents a fixed-length bit field that is pinned in bit field and encrypted at rest. /// - public class SecureBuffer : Instrument, ISecureBuffer + public class SecureMemory : Instrument, ISecureMemory { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// - /// The length of the buffer, in bytes. + /// The length of the bit field, in bytes. /// /// /// is less than or equal to zero. /// - public SecureBuffer(Int32 lengthInBytes) + public SecureMemory(Int32 lengthInBytes) : base(ConcurrencyControlMode.SingleThreadLock) { Cipher = new Aes128CbcCipher(RandomnessProvider); LengthInBytes = lengthInBytes.RejectIf().IsLessThanOrEqualTo(0, nameof(lengthInBytes)); - PrivateKey = new PinnedBuffer(Cipher.KeySizeInBytes, true); + PrivateKey = new PinnedMemory(Cipher.KeySizeInBytes, true); RandomnessProvider.GetBytes(PrivateKey); - using (var initializationVector = new PinnedBuffer(Cipher.BlockSizeInBytes, true)) + using (var initializationVector = new PinnedMemory(Cipher.BlockSizeInBytes, true)) { RandomnessProvider.GetBytes(initializationVector); - using (var plaintext = new PinnedBuffer(lengthInBytes)) + using (var plaintext = new PinnedMemory(lengthInBytes)) { Ciphertext = Cipher.Encrypt(plaintext, PrivateKey, initializationVector); } @@ -49,30 +49,30 @@ public SecureBuffer(Int32 lengthInBytes) } /// - /// Generates cryptographically secure pseudo-random bytes that are pinned in memory and encrypted at rest. + /// Generates cryptographically secure pseudo-random bytes that are pinned in bit field and encrypted at rest. /// /// /// This method is useful for generating keys and other sensitive random values. /// /// - /// The length of the random buffer, in bytes. + /// The length of the random bit field, in bytes. /// /// /// A cryptographically secure pseudo-random byte array of specified length. /// - public static ISecureBuffer GenerateHardenedRandomBytes(Int32 lengthInBytes) + public static ISecureMemory GenerateHardenedRandomBytes(Int32 lengthInBytes) { - var hardenedRandomBytes = new SecureBuffer(lengthInBytes); - hardenedRandomBytes.Access((pinnedBuffer) => + var hardenedRandomBytes = new SecureMemory(lengthInBytes); + hardenedRandomBytes.Access((memory) => { - RandomnessProvider.GetBytes(pinnedBuffer); + RandomnessProvider.GetBytes(memory); }); return hardenedRandomBytes; } /// - /// Decrypts the buffer, performs the specified operation against the pinned plaintext and encrypts the buffer as a + /// Decrypts the bit field, performs the specified operation against the pinned plaintext and encrypts the bit field as a /// thread-safe, atomic operation. /// /// @@ -84,7 +84,7 @@ public static ISecureBuffer GenerateHardenedRandomBytes(Int32 lengthInBytes) /// /// The object is disposed. /// - public void Access(Action action) + public void Access(Action action) { action = action.RejectIf().IsNull(nameof(action)); @@ -92,7 +92,7 @@ public void Access(Action action) { RejectIfDisposed(); - using (var plaintext = new PinnedBuffer(LengthInBytes, true)) + using (var plaintext = new PinnedMemory(LengthInBytes, true)) { DecryptField(plaintext); @@ -117,15 +117,15 @@ public void Access(Action action) public override Int32 GetHashCode() => Ciphertext.ComputeThirtyTwoBitHash() ^ 0x3a566a5c; /// - /// Converts the value of the current to its equivalent string representation. + /// Converts the value of the current to its equivalent string representation. /// /// - /// A string representation of the current . + /// A string representation of the current . /// public override String ToString() => $"{{ {nameof(LengthInBytes)}: {LengthInBytes} }}"; /// - /// Releases all resources consumed by the current . + /// Releases all resources consumed by the current . /// /// /// A value indicating whether or not managed resources should be released. @@ -148,30 +148,30 @@ protected override void Dispose(Boolean disposing) } /// - /// Decrypts the current buffer and writes the result to the specified buffer. + /// Decrypts the current bit field and writes the result to the specified bit field. /// /// - /// The buffer to which the plaintext result is written. + /// The bit field to which the plaintext result is written. /// [DebuggerHidden] - private void DecryptField(PinnedBuffer plaintext) + private void DecryptField(PinnedMemory plaintext) { - using (var buffer = Cipher.Decrypt(Ciphertext, PrivateKey)) + using (var memory = Cipher.Decrypt(Ciphertext, PrivateKey)) { - buffer.Span.CopyTo(plaintext); + memory.Span.CopyTo(plaintext); } } /// - /// Encrypts the specified plaintext and writes the result to the current buffer. + /// Encrypts the specified plaintext and writes the result to the current bit field. /// /// - /// The buffer containing the plaintext to encrypt. + /// The bit field containing the plaintext to encrypt. /// [DebuggerHidden] - private void EncryptField(PinnedBuffer plaintext) + private void EncryptField(PinnedMemory plaintext) { - using (var initializationVector = new PinnedBuffer(Cipher.BlockSizeInBytes, true)) + using (var initializationVector = new PinnedMemory(Cipher.BlockSizeInBytes, true)) { RandomnessProvider.GetBytes(initializationVector); @@ -189,7 +189,7 @@ private void EncryptField(PinnedBuffer plaintext) public static RandomNumberGenerator RandomnessProvider => HardenedRandomNumberGenerator.Instance; /// - /// Gets the length of the buffer, in bytes. + /// Gets the length of the bit field, in bytes. /// public Int32 LengthInBytes { @@ -197,21 +197,21 @@ public Int32 LengthInBytes } /// - /// Represents a cipher that is used to encrypt and decrypt the buffer field. + /// Represents a cipher that is used to encrypt and decrypt the bit field. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly SymmetricKeyCipher Cipher; /// - /// Represents the ciphertext bits for the buffer. + /// Represents the ciphertext bits for the bit field. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly PinnedBuffer Ciphertext; + private readonly PinnedMemory Ciphertext; /// - /// Represents a private key that is used to encrypt and decrypt the buffer field. + /// Represents a private key that is used to encrypt and decrypt the bit field. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly PinnedBuffer PrivateKey; + private readonly PinnedMemory PrivateKey; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/CascadingSymmetricKey.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/CascadingSymmetricKey.cs index a04e5a98..48fa8f7f 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Symmetric/CascadingSymmetricKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Symmetric/CascadingSymmetricKey.cs @@ -78,7 +78,7 @@ private CascadingSymmetricKey(ISymmetricKey[] keys) /// /// is . /// - public static CascadingSymmetricKey FromBuffer(ISecureBuffer buffer) + public static CascadingSymmetricKey FromBuffer(ISecureMemory buffer) { buffer.RejectIf().IsNull(nameof(buffer)).OrIf(argument => argument.LengthInBytes != SerializedLength, nameof(buffer), "The specified buffer is invalid."); @@ -86,21 +86,21 @@ public static CascadingSymmetricKey FromBuffer(ISecureBuffer buffer) { var keys = (ISymmetricKey[])null; - buffer.Access(pinnedBuffer => + buffer.Access(memory => { // Interrogate the final 16 bits to determine the depth. var keyLength = SymmetricKey.SerializedLength; - var depth = BitConverter.ToUInt16(pinnedBuffer, (SerializedLength - sizeof(UInt16))); + var depth = BitConverter.ToUInt16(memory, (SerializedLength - sizeof(UInt16))); keys = new ISymmetricKey[depth]; for (var i = 0; i < depth; i++) { - using (var secureBuffer = new SecureBuffer(keyLength)) + using (var secureBuffer = new SecureMemory(keyLength)) { - secureBuffer.Access(keyBuffer => + secureBuffer.Access(key => { // Copy out the key buffers. - Array.Copy(pinnedBuffer, (keyLength * i), keyBuffer, 0, keyLength); + Array.Copy(memory, (keyLength * i), key, 0, keyLength); }); keys[i] = SymmetricKey.FromBuffer(secureBuffer); @@ -339,9 +339,9 @@ public static CascadingSymmetricKey FromBuffer(ISecureBuffer buffer) /// /// A binary representation of the current . /// - public ISecureBuffer ToBuffer() + public ISecureMemory ToBuffer() { - var result = new SecureBuffer(SerializedLength); + var result = new SecureMemory(SerializedLength); try { @@ -357,10 +357,10 @@ public ISecureBuffer ToBuffer() { using (var keyBuffer = Keys.ElementAt(i).ToBuffer()) { - keyBuffer.Access(pinnedKeyBuffer => + keyBuffer.Access(key => { // Copy the key buffers out to the result buffer. - Array.Copy(pinnedKeyBuffer, 0, pinnedResultBuffer, (keyLength * i), keyLength); + Array.Copy(key, 0, pinnedResultBuffer, (keyLength * i), keyLength); }); } @@ -453,7 +453,7 @@ private static CascadingSymmetricKey FromPassword(String password, SymmetricKeyD { var algorithm = algorithms[i]; - using (var keySource = new PinnedBuffer(keySourceBytes.Span.Slice(i * singleKeySourceLengthInBytes, singleKeySourceLengthInBytes).ToArray())) + using (var keySource = new PinnedMemory(keySourceBytes.Span.Slice(i * singleKeySourceLengthInBytes, singleKeySourceLengthInBytes).ToArray())) { keys[i] = SymmetricKey.New(algorithm, derivationMode, keySource); } diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/ICascadingSymmetricKey.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/ICascadingSymmetricKey.cs index 05ccf829..7d0ff14e 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Symmetric/ICascadingSymmetricKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Symmetric/ICascadingSymmetricKey.cs @@ -19,7 +19,7 @@ public interface ICascadingSymmetricKey : IAsyncDisposable, IDisposable /// /// A binary representation of the current . /// - public ISecureBuffer ToBuffer(); + public ISecureMemory ToBuffer(); /// /// Gets the number of layers of encryption that a resulting transform will apply. diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/ISymmetricKey.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/ISymmetricKey.cs index db25699c..48ac3164 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Symmetric/ISymmetricKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Symmetric/ISymmetricKey.cs @@ -18,7 +18,7 @@ public interface ISymmetricKey : IAsyncDisposable, IDisposable /// /// A binary representation of the current . /// - public ISecureBuffer ToBuffer(); + public ISecureMemory ToBuffer(); /// /// Converts the current to symmetric-key plaintext with correct bit-length for the encryption @@ -27,7 +27,7 @@ public interface ISymmetricKey : IAsyncDisposable, IDisposable /// /// The derived key. /// - public ISecureBuffer ToDerivedKeyBytes(); + public ISecureMemory ToDerivedKeyBytes(); /// /// Gets the symmetric-key algorithm for which a key is derived. diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/ISymmetricProcessor.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/ISymmetricProcessor.cs index d27b6c8a..029827cc 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Symmetric/ISymmetricProcessor.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Symmetric/ISymmetricProcessor.cs @@ -67,7 +67,7 @@ public interface ISymmetricProcessor /// /// An exception was raised during decryption or deserialization. /// - public T Decrypt(Byte[] ciphertext, ISecureBuffer key, SymmetricAlgorithmSpecification algorithm); + public T Decrypt(Byte[] ciphertext, ISecureMemory key, SymmetricAlgorithmSpecification algorithm); /// /// Encrypts the specified plaintext object. @@ -121,6 +121,6 @@ public interface ISymmetricProcessor /// /// An exception was raised during encryption or serialization. /// - public Byte[] Encrypt(T plaintextObject, ISecureBuffer key, SymmetricAlgorithmSpecification algorithm); + public Byte[] Encrypt(T plaintextObject, ISecureMemory key, SymmetricAlgorithmSpecification algorithm); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKey.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKey.cs index aa8b9e90..364d6cb4 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKey.cs @@ -45,12 +45,12 @@ public sealed class SymmetricKey : Instrument, ISymmetricKey /// is equal to . /// [DebuggerHidden] - private SymmetricKey(SymmetricAlgorithmSpecification algorithm, SymmetricKeyDerivationMode derivationMode, PinnedBuffer keySource) + private SymmetricKey(SymmetricAlgorithmSpecification algorithm, SymmetricKeyDerivationMode derivationMode, PinnedMemory keySource) : base(ConcurrencyControlMode.SingleThreadLock) { Algorithm = algorithm.RejectIf().IsEqualToValue(SymmetricAlgorithmSpecification.Unspecified, nameof(algorithm)); DerivationMode = derivationMode.RejectIf().IsEqualToValue(SymmetricKeyDerivationMode.Unspecified, nameof(derivationMode)); - KeySource = new SecureBuffer(KeySourceLengthInBytes); + KeySource = new SecureMemory(KeySourceLengthInBytes); LazyPbkdf2Provider = new Lazy(InitializePbkdf2Algorithm, LazyThreadSafetyMode.ExecutionAndPublication); // Gather information about the derived key. @@ -78,7 +78,7 @@ private SymmetricKey(SymmetricAlgorithmSpecification algorithm, SymmetricKeyDeri /// /// is . /// - public static SymmetricKey FromBuffer(ISecureBuffer buffer) + public static SymmetricKey FromBuffer(ISecureMemory buffer) { buffer.RejectIf().IsNull(nameof(buffer)).OrIf(argument => argument.LengthInBytes != SerializedLength, nameof(buffer), "The specified buffer is invalid."); @@ -88,7 +88,7 @@ public static SymmetricKey FromBuffer(ISecureBuffer buffer) buffer.Access(pinnedCiphertext => { - using (var plaintextBuffer = new PinnedBuffer(SerializedPlaintextLength, true)) + using (var plaintextBuffer = new PinnedMemory(SerializedPlaintextLength, true)) { using (var cipher = BufferEncryptionAlgorithm.ToCipher(RandomnessProvider)) { @@ -98,7 +98,7 @@ public static SymmetricKey FromBuffer(ISecureBuffer buffer) } } - using (var keySource = new PinnedBuffer(KeySourceLengthInBytes, true)) + using (var keySource = new PinnedMemory(KeySourceLengthInBytes, true)) { Array.Copy(plaintextBuffer, KeySourceBufferIndex, keySource, 0, KeySourceLengthInBytes); var algorithm = (SymmetricAlgorithmSpecification)plaintextBuffer[AlgorithmBufferIndex]; @@ -246,7 +246,7 @@ public static SymmetricKey FromPassword(String password, SymmetricAlgorithmSpeci /// public static SymmetricKey New(SymmetricAlgorithmSpecification algorithm, SymmetricKeyDerivationMode derivationMode) { - using (var keySource = new PinnedBuffer(KeySourceLengthInBytes, true)) + using (var keySource = new PinnedMemory(KeySourceLengthInBytes, true)) { RandomnessProvider.GetBytes(keySource); return New(algorithm, derivationMode, keySource); @@ -260,15 +260,15 @@ public static SymmetricKey New(SymmetricAlgorithmSpecification algorithm, Symmet /// A binary representation of the current . /// [DebuggerHidden] - public ISecureBuffer ToBuffer() + public ISecureMemory ToBuffer() { - var resultBuffer = new SecureBuffer(SerializedLength); + var resultBuffer = new SecureMemory(SerializedLength); try { using (var controlToken = StateControl.Enter()) { - using (var plaintextBuffer = new PinnedBuffer(SerializedPlaintextLength, true)) + using (var plaintextBuffer = new PinnedMemory(SerializedPlaintextLength, true)) { KeySource.Access(pinnedKeySourceBuffer => { @@ -280,7 +280,7 @@ public ISecureBuffer ToBuffer() using (var cipher = BufferEncryptionAlgorithm.ToCipher(RandomnessProvider)) { - using (var initializationVector = new PinnedBuffer(cipher.BlockSizeInBytes, true)) + using (var initializationVector = new PinnedMemory(cipher.BlockSizeInBytes, true)) { RandomnessProvider.GetBytes(initializationVector); @@ -313,9 +313,9 @@ public ISecureBuffer ToBuffer() /// The derived key. /// [DebuggerHidden] - public ISecureBuffer ToDerivedKeyBytes() + public ISecureMemory ToDerivedKeyBytes() { - var result = new SecureBuffer(DerivedKeyLength); + var result = new SecureMemory(DerivedKeyLength); try { @@ -325,11 +325,11 @@ public ISecureBuffer ToDerivedKeyBytes() { try { - result.Access((buffer) => + result.Access(memory => { // Perform PBKDF2 key-derivation. var keyBytes = new Span(Pbkdf2Provider.GetBytes(DerivedKeyLength)); - keyBytes.CopyTo(buffer); + keyBytes.CopyTo(memory); keyBytes.Clear(); }); } @@ -341,15 +341,15 @@ public ISecureBuffer ToDerivedKeyBytes() return result; } - using (var sourceWords = new PinnedBuffer(KeySourceWordCount, true)) + using (var sourceWords = new PinnedMemory(KeySourceWordCount, true)) { - KeySource.Access((buffer) => + KeySource.Access(memory => { // Convert the source buffer to an array of 32-bit words. - Buffer.BlockCopy(buffer, 0, sourceWords, 0, KeySourceLengthInBytes); + Buffer.BlockCopy(memory, 0, sourceWords, 0, KeySourceLengthInBytes); }); - using (var transformedWords = new PinnedBuffer(BlockWordCount, true)) + using (var transformedWords = new PinnedMemory(BlockWordCount, true)) { // Copy out the first block. If nothing further is done, this satisfies truncation mode. Array.Copy(sourceWords, transformedWords, BlockWordCount); @@ -391,12 +391,12 @@ public ISecureBuffer ToDerivedKeyBytes() throw new UnsupportedSpecificationException($"The specified key derivation mode, {DerivationMode}, is not supported."); } - result.Access((buffer) => + result.Access(memory => { // Copy out the key bits. var keyBytes = new Byte[DerivedKeyLength]; Buffer.BlockCopy(transformedWords, 0, keyBytes, 0, DerivedKeyLength); - keyBytes.CopyTo(buffer); + keyBytes.CopyTo(memory); for (var i = 0; i < DerivedKeyLength; i++) { @@ -441,16 +441,16 @@ public ISecureBuffer ToDerivedKeyBytes() /// is less than or equal to zero. /// [DebuggerHidden] - internal static PinnedBuffer DeriveKeySourceBytesFromPassword(String password, Int32 keySourceLengthInBytes) + internal static PinnedMemory DeriveKeySourceBytesFromPassword(String password, Int32 keySourceLengthInBytes) { - using (var passwordBytes = new ReadOnlyPinnedBuffer(PasswordEncoding.GetBytes(password.RejectIf().IsNullOrEmpty(nameof(password)).OrIf(argument => argument.Length < MinimumPasswordLength, nameof(password), $"The specified password is shorter than {MinimumPasswordLength} characters.")))) + using (var passwordBytes = new ReadOnlyPinnedMemory(PasswordEncoding.GetBytes(password.RejectIf().IsNullOrEmpty(nameof(password)).OrIf(argument => argument.Length < MinimumPasswordLength, nameof(password), $"The specified password is shorter than {MinimumPasswordLength} characters.")))) { var hashingProcessor = new HashingBinaryProcessor(RandomnessProvider); - using (var saltBytes = new ReadOnlyPinnedBuffer(hashingProcessor.CalculateHash(passwordBytes, PasswordSaltHashingAlgorithm))) + using (var saltBytes = new ReadOnlyPinnedMemory(hashingProcessor.CalculateHash(passwordBytes, PasswordSaltHashingAlgorithm))) { var pbkdf2Provider = new Rfc2898DeriveBytes(passwordBytes, saltBytes, Pbkdf2MinimumIterationCount); - return new PinnedBuffer(pbkdf2Provider.GetBytes(keySourceLengthInBytes.RejectIf().IsLessThanOrEqualTo(0, nameof(keySourceLengthInBytes)))); + return new PinnedMemory(pbkdf2Provider.GetBytes(keySourceLengthInBytes.RejectIf().IsLessThanOrEqualTo(0, nameof(keySourceLengthInBytes)))); } } } @@ -479,7 +479,7 @@ internal static PinnedBuffer DeriveKeySourceBytesFromPassword(String password, I /// is equal to . /// [DebuggerHidden] - internal static SymmetricKey New(SymmetricAlgorithmSpecification algorithm, SymmetricKeyDerivationMode derivationMode, PinnedBuffer keySource) => new SymmetricKey(algorithm, derivationMode, keySource.RejectIf(argument => argument.Length != KeySourceLengthInBytes, nameof(keySource), $"The key source is not {KeySourceLengthInBytes} bytes in length.")); + internal static SymmetricKey New(SymmetricAlgorithmSpecification algorithm, SymmetricKeyDerivationMode derivationMode, PinnedMemory keySource) => new SymmetricKey(algorithm, derivationMode, keySource.RejectIf(argument => argument.Length != KeySourceLengthInBytes, nameof(keySource), $"The key source is not {KeySourceLengthInBytes} bytes in length.")); /// /// Releases all resources consumed by the current . @@ -700,7 +700,7 @@ public SymmetricAlgorithmSpecification Algorithm /// probably few good reasons to. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private static readonly PinnedBuffer BufferEncryptionKey = new PinnedBuffer(new Byte[] + private static readonly PinnedMemory BufferEncryptionKey = new PinnedMemory(new Byte[] { 0xaa, 0xf0, 0xcc, 0xff, 0x00, 0x33, 0x0f, 0x55, 0xff, 0xcc, 0xf0, 0xaa, 0x55, 0x0f, 0x33, 0x00 }); @@ -769,7 +769,7 @@ public SymmetricAlgorithmSpecification Algorithm /// Represents a buffer that is used to derive key bits from the current . /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly ISecureBuffer KeySource; + private readonly ISecureMemory KeySource; /// /// Represents the lazily-initialized PBKDF2 algorithm provider for the current . diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKeyCipher.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKeyCipher.cs index 988965d5..8812c472 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKeyCipher.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKeyCipher.cs @@ -72,7 +72,7 @@ protected SymmetricKeyCipher(Int32 blockSize, Int32 keySize, CipherMode mode, Pa /// . /// [DebuggerHidden] - internal PinnedBuffer Decrypt(PinnedBuffer ciphertext, PinnedBuffer privateKey) + internal PinnedMemory Decrypt(PinnedMemory ciphertext, PinnedMemory privateKey) { ciphertext.RejectIf().IsNull(nameof(ciphertext)).OrIf(argument => argument.Count() < BlockSizeInBytes, nameof(ciphertext), "The length of the specified ciphertext is invalid for the algorithm."); privateKey.RejectIf().IsNull(nameof(privateKey)).OrIf(argument => argument.Count() != KeySizeInBytes, nameof(privateKey), "The length of the specified key is invalid for the algorithm."); @@ -110,7 +110,7 @@ internal PinnedBuffer Decrypt(PinnedBuffer ciphertext, PinnedBuffer privateKey) /// . /// [DebuggerHidden] - internal PinnedBuffer Encrypt(PinnedBuffer plaintext, PinnedBuffer privateKey, PinnedBuffer initializationVector) + internal PinnedMemory Encrypt(PinnedMemory plaintext, PinnedMemory privateKey, PinnedMemory initializationVector) { plaintext.RejectIf().IsNull(nameof(plaintext)); privateKey.RejectIf().IsNull(nameof(privateKey)).OrIf(argument => argument.Count() != KeySizeInBytes, nameof(privateKey), "The length of the specified key is invalid for the algorithm."); @@ -153,13 +153,13 @@ internal PinnedBuffer Encrypt(PinnedBuffer plaintext, PinnedBuffer privateKey, P /// The plaintext result of the algorithm. /// [DebuggerHidden] - private PinnedBuffer DecryptInCbcMode(PinnedBuffer ciphertext, PinnedBuffer privateKey) + private PinnedMemory DecryptInCbcMode(PinnedMemory ciphertext, PinnedMemory privateKey) { - using (var initializationVector = new PinnedBuffer(BlockSizeInBytes, true)) + using (var initializationVector = new PinnedMemory(BlockSizeInBytes, true)) { Array.Copy(ciphertext, 0, initializationVector, 0, BlockSizeInBytes); - using (var cipherTextSansInitializationVector = new PinnedBuffer((ciphertext.Length - BlockSizeInBytes), true)) + using (var cipherTextSansInitializationVector = new PinnedMemory((ciphertext.Length - BlockSizeInBytes), true)) { Array.Copy(ciphertext, BlockSizeInBytes, cipherTextSansInitializationVector, 0, cipherTextSansInitializationVector.Length); @@ -180,7 +180,7 @@ private PinnedBuffer DecryptInCbcMode(PinnedBuffer ciphertext, PinnedBuffer priv { cryptographicStream.Write(cipherTextSansInitializationVector, 0, cipherTextSansInitializationVector.Length); cryptographicStream.FlushFinalBlock(); - return new PinnedBuffer(memoryStream.ToArray(), false); + return new PinnedMemory(memoryStream.ToArray(), false); } } } @@ -202,7 +202,7 @@ private PinnedBuffer DecryptInCbcMode(PinnedBuffer ciphertext, PinnedBuffer priv /// The plaintext result of the algorithm. /// [DebuggerHidden] - private PinnedBuffer DecryptInEcbMode(PinnedBuffer ciphertext, PinnedBuffer privateKey) + private PinnedMemory DecryptInEcbMode(PinnedMemory ciphertext, PinnedMemory privateKey) { using (var encryptionProvider = InitializeProvider()) { @@ -220,7 +220,7 @@ private PinnedBuffer DecryptInEcbMode(PinnedBuffer ciphertext, PinnedBuffer priv { cryptographicStream.Write(ciphertext, 0, ciphertext.Length); cryptographicStream.FlushFinalBlock(); - return new PinnedBuffer(memoryStream.ToArray(), false); + return new PinnedMemory(memoryStream.ToArray(), false); } } } @@ -249,7 +249,7 @@ private PinnedBuffer DecryptInEcbMode(PinnedBuffer ciphertext, PinnedBuffer priv /// is . /// [DebuggerHidden] - private PinnedBuffer EncryptInCbcMode(PinnedBuffer plaintext, PinnedBuffer privateKey, PinnedBuffer initializationVector) + private PinnedMemory EncryptInCbcMode(PinnedMemory plaintext, PinnedMemory privateKey, PinnedMemory initializationVector) { initializationVector.RejectIf().IsNull(nameof(initializationVector)).OrIf(argument => argument.Count() != BlockSizeInBytes, nameof(privateKey), "The length of the specified initialization vector is invalid for the algorithm."); @@ -270,7 +270,7 @@ private PinnedBuffer EncryptInCbcMode(PinnedBuffer plaintext, PinnedBuffer priva { cryptographicStream.Write(plaintext, 0, plaintext.Length); cryptographicStream.FlushFinalBlock(); - return new PinnedBuffer(initializationVector.Concat(memoryStream.ToArray()).ToArray(), false); + return new PinnedMemory(initializationVector.Concat(memoryStream.ToArray()).ToArray(), false); } } } @@ -290,7 +290,7 @@ private PinnedBuffer EncryptInCbcMode(PinnedBuffer plaintext, PinnedBuffer priva /// The ciphertext result of the algorithm. /// [DebuggerHidden] - private PinnedBuffer EncryptInEcbMode(PinnedBuffer plaintext, PinnedBuffer privateKey) + private PinnedMemory EncryptInEcbMode(PinnedMemory plaintext, PinnedMemory privateKey) { using (var encryptionProvider = InitializeProvider()) { @@ -308,7 +308,7 @@ private PinnedBuffer EncryptInEcbMode(PinnedBuffer plaintext, PinnedBuffer priva { cryptographicStream.Write(plaintext, 0, plaintext.Length); cryptographicStream.FlushFinalBlock(); - return new PinnedBuffer(memoryStream.ToArray(), false); + return new PinnedMemory(memoryStream.ToArray(), false); } } } diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricProcessor.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricProcessor.cs index 22ebb3ba..d3a40f57 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricProcessor.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricProcessor.cs @@ -130,15 +130,15 @@ public T Decrypt(Byte[] ciphertext, ICascadingSymmetricKey key) /// /// An exception was raised during decryption or deserialization. /// - public T Decrypt(Byte[] ciphertext, ISecureBuffer key, SymmetricAlgorithmSpecification algorithm) + public T Decrypt(Byte[] ciphertext, ISecureMemory key, SymmetricAlgorithmSpecification algorithm) { try { var plaintext = default(T); - key.Access(keyBuffer => + key.Access(memory => { - plaintext = Decrypt(ciphertext, keyBuffer, algorithm); + plaintext = Decrypt(ciphertext, memory, algorithm); }); return plaintext; @@ -254,7 +254,7 @@ public Byte[] Encrypt(T plaintextObject, ICascadingSymmetricKey key) /// /// An exception was raised during encryption or serialization. /// - public Byte[] Encrypt(T plaintextObject, ISecureBuffer key, SymmetricAlgorithmSpecification algorithm) => Encrypt(plaintextObject, key, algorithm, null); + public Byte[] Encrypt(T plaintextObject, ISecureMemory key, SymmetricAlgorithmSpecification algorithm) => Encrypt(plaintextObject, key, algorithm, null); /// /// Encrypts the specified plaintext object. @@ -278,7 +278,7 @@ public Byte[] Encrypt(T plaintextObject, ICascadingSymmetricKey key) /// /// An exception was raised during encryption or serialization. /// - public Byte[] Encrypt(T plaintextObject, ISecureBuffer key, SymmetricAlgorithmSpecification algorithm, Byte[] initializationVector) + public Byte[] Encrypt(T plaintextObject, ISecureMemory key, SymmetricAlgorithmSpecification algorithm, Byte[] initializationVector) { try { @@ -313,11 +313,11 @@ public Byte[] Encrypt(T plaintextObject, ISecureBuffer key, SymmetricAlgorithmSp /// The resulting plaintext object. /// [DebuggerHidden] - private T Decrypt(Byte[] ciphertext, PinnedBuffer key, SymmetricAlgorithmSpecification algorithm) + private T Decrypt(Byte[] ciphertext, PinnedMemory key, SymmetricAlgorithmSpecification algorithm) { using (var cipher = algorithm.ToCipher(RandomnessProvider)) { - using (var pinnedCiphertext = new PinnedBuffer(ciphertext, false)) + using (var pinnedCiphertext = new PinnedMemory(ciphertext, false)) { using (var plaintext = cipher.Decrypt(pinnedCiphertext, key)) { @@ -347,12 +347,12 @@ private T Decrypt(Byte[] ciphertext, PinnedBuffer key, SymmetricAlgorithmSpecifi /// The resulting ciphertext. /// [DebuggerHidden] - private Byte[] Encrypt(T plaintextObject, PinnedBuffer key, SymmetricAlgorithmSpecification algorithm, Byte[] initializationVector) + private Byte[] Encrypt(T plaintextObject, PinnedMemory key, SymmetricAlgorithmSpecification algorithm, Byte[] initializationVector) { var plaintext = BinarySerializer.Serialize(plaintextObject); var plaintextLength = plaintext.Length; - using (var pinnedPlaintext = new PinnedBuffer(plaintextLength, true)) + using (var pinnedPlaintext = new PinnedMemory(plaintextLength, true)) { Array.Copy(plaintext, pinnedPlaintext, plaintextLength); @@ -362,7 +362,7 @@ private Byte[] Encrypt(T plaintextObject, PinnedBuffer key, SymmetricAlgorithmSp { case CryptographicTransform.CipherModeCbc: - using (var processedInitializationVector = new PinnedBuffer(cipher.BlockSizeInBytes, true)) + using (var processedInitializationVector = new PinnedMemory(cipher.BlockSizeInBytes, true)) { if (initializationVector is null) { diff --git a/test/RapidField.SolidInstruments.Collections.UnitTests/PinnedBufferTests.cs b/test/RapidField.SolidInstruments.Collections.UnitTests/PinnedMemoryTests.cs similarity index 91% rename from test/RapidField.SolidInstruments.Collections.UnitTests/PinnedBufferTests.cs rename to test/RapidField.SolidInstruments.Collections.UnitTests/PinnedMemoryTests.cs index fb1d2666..028bae27 100644 --- a/test/RapidField.SolidInstruments.Collections.UnitTests/PinnedBufferTests.cs +++ b/test/RapidField.SolidInstruments.Collections.UnitTests/PinnedMemoryTests.cs @@ -9,7 +9,7 @@ namespace RapidField.SolidInstruments.Collections.UnitTests { [TestClass] - public class PinnedBufferTests + public class PinnedMemoryTests { [TestMethod] public void Constructor_ShouldRaiseArgumentOutOfRangeException_ForNegativeLengthArgument() @@ -20,7 +20,7 @@ public void Constructor_ShouldRaiseArgumentOutOfRangeException_ForNegativeLength // Act. var action = new Action(() => { - using (var target = new PinnedBuffer(length)) + using (var target = new PinnedMemory(length)) { return; } @@ -39,7 +39,7 @@ public void Constructor_ShouldRaiseArgumentOutOfRangeException_ForZeroLengthArgu // Act. var action = new Action(() => { - using (var target = new PinnedBuffer(length)) + using (var target = new PinnedMemory(length)) { return; } @@ -56,7 +56,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_UsingFieldConstru var length = 8; var field = new Byte[length]; - using (var target = new PinnedBuffer(field, true)) + using (var target = new PinnedMemory(field, true)) { // Assert. ReferenceEquals(field, target).Should().BeFalse(); @@ -86,7 +86,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_UsingLengthConstr var length = 8; var field = (Byte[])null; - using (var target = new PinnedBuffer(length, true)) + using (var target = new PinnedMemory(length, true)) { // Arrange. field = target; @@ -119,7 +119,7 @@ public void LengthInBytes_ShouldProduceDesiredResults() var length = 10; var field = new Int32[length]; - using (var target = new PinnedBuffer(field, false)) + using (var target = new PinnedMemory(field, false)) { // Assert. target.LengthInBytes.Should().Be(length * 4); @@ -133,7 +133,7 @@ public void OverwriteWithZeros_ShouldProduceDesiredResults_UsingFieldConstructor var length = 8; var field = new Byte[length]; - using (var target = new PinnedBuffer(field, false)) + using (var target = new PinnedMemory(field, false)) { // Arrange. target[0] = 0x01; @@ -162,7 +162,7 @@ public void OverwriteWithZeros_ShouldProduceDesiredResults_UsingLengthConstructo // Arrange. var length = 8; - using (var target = new PinnedBuffer(length, false)) + using (var target = new PinnedMemory(length, false)) { // Arrange. target[0] = 0x01; diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/CascadingSymmetricKeySecretTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/CascadingSymmetricKeySecretTests.cs index ca03a261..881bc4d7 100644 --- a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/CascadingSymmetricKeySecretTests.cs +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/CascadingSymmetricKeySecretTests.cs @@ -22,7 +22,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults() var valueTwo = CascadingSymmetricKey.New(); var valueThree = CascadingSymmetricKey.New(); var hashCode = 0; - var symmetricProcessor = new SymmetricStringProcessor(SecureBuffer.RandomnessProvider); + var symmetricProcessor = new SymmetricStringProcessor(SecureMemory.RandomnessProvider); var plaintextObject = "䆟`ಮ䷆ʘ‣⦸⏹ⰄͶa✰ṁ亡Zᨖ0༂⽔9㗰"; var ciphertextObject = Array.Empty(); diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SecretTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SecretTests.cs index 9e477042..df72261e 100644 --- a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SecretTests.cs +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SecretTests.cs @@ -31,10 +31,10 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults() hashCode.Should().Be(target.GetHashCode()); target.Name.Should().Be(name); target.HasValue.Should().BeFalse(); - target.ValueType.Should().Be(typeof(IReadOnlyPinnedBuffer)); + target.ValueType.Should().Be(typeof(IReadOnlyPinnedMemory)); // Act. - target.Write(() => new PinnedBuffer(valueOne)); + target.Write(() => new PinnedMemory(valueOne)); // Assert. hashCode.Should().NotBe(target.GetHashCode()); @@ -47,7 +47,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults() }); // Act. - target.Write(() => new PinnedBuffer(valueTwo)); + target.Write(() => new PinnedMemory(valueTwo)); // Assert. hashCode.Should().NotBe(target.GetHashCode()); @@ -59,7 +59,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults() }); // Act. - target.Write(() => new PinnedBuffer(valueThree)); + target.Write(() => new PinnedMemory(valueThree)); // Assert. hashCode.Should().NotBe(target.GetHashCode()); diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SecretVaultTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SecretVaultTests.cs index 373ed858..b027348e 100644 --- a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SecretVaultTests.cs +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SecretVaultTests.cs @@ -43,7 +43,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForBinarySecrets( // Assert. target.Count.Should().Be(1); target.Names.Should().Contain(secretOneName); - target.ReadAsync(secretOneName, (IReadOnlyPinnedBuffer value) => + target.ReadAsync(secretOneName, (IReadOnlyPinnedMemory value) => { value.Should().BeEquivalentTo(secretOne); }).Wait(); @@ -55,7 +55,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForBinarySecrets( target.Count.Should().Be(2); target.Names.Should().Contain(secretOneName); target.Names.Should().Contain(secretTwoName); - target.ReadAsync(secretTwoName, (IReadOnlyPinnedBuffer value) => + target.ReadAsync(secretTwoName, (IReadOnlyPinnedMemory value) => { value.Should().BeEquivalentTo(secretTwo); }).Wait(); @@ -66,7 +66,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForBinarySecrets( // Assert. target.Count.Should().Be(2); target.Names.Should().Contain(secretOneName); - target.ReadAsync(secretOneName, (IReadOnlyPinnedBuffer value) => + target.ReadAsync(secretOneName, (IReadOnlyPinnedMemory value) => { value.Should().BeEquivalentTo(secretThree); }).Wait(); diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SymmetricKeySecretTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SymmetricKeySecretTests.cs index 3fff14c5..f446ee10 100644 --- a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SymmetricKeySecretTests.cs +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SymmetricKeySecretTests.cs @@ -22,7 +22,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults() var valueTwo = SymmetricKey.New(); var valueThree = SymmetricKey.New(); var hashCode = 0; - var symmetricProcessor = new SymmetricStringProcessor(SecureBuffer.RandomnessProvider); + var symmetricProcessor = new SymmetricStringProcessor(SecureMemory.RandomnessProvider); var plaintextObject = "䆟`ಮ䷆ʘ‣⦸⏹ⰄͶa✰ṁ亡Zᨖ0༂⽔9㗰"; var ciphertextObject = Array.Empty(); diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/SecureBufferTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/SecureMemoryTests.cs similarity index 80% rename from test/RapidField.SolidInstruments.Cryptography.UnitTests/SecureBufferTests.cs rename to test/RapidField.SolidInstruments.Cryptography.UnitTests/SecureMemoryTests.cs index 89bde261..cbe0b197 100644 --- a/test/RapidField.SolidInstruments.Cryptography.UnitTests/SecureBufferTests.cs +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/SecureMemoryTests.cs @@ -9,7 +9,7 @@ namespace RapidField.SolidInstruments.Cryptography.UnitTests { [TestClass] - public class SecureBufferTests + public class SecureMemoryTests { [TestMethod] public void Constructor_ShouldRaiseArgumentOutOfRangeException_ForNegativePlaintextLengthArgument() @@ -20,7 +20,7 @@ public void Constructor_ShouldRaiseArgumentOutOfRangeException_ForNegativePlaint // Act. var action = new Action(() => { - using (var target = new SecureBuffer(length)) + using (var target = new SecureMemory(length)) { return; } @@ -39,7 +39,7 @@ public void Constructor_ShouldRaiseArgumentOutOfRangeException_ForZeroPlaintextL // Act. var action = new Action(() => { - using (var target = new SecureBuffer(length)) + using (var target = new SecureMemory(length)) { return; } @@ -54,45 +54,45 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults() { // Arrange. var length = 17; - var bufferReference = (Byte[])null; - var bufferSubsegment = new Byte[3]; + var memoryReference = (Byte[])null; + var memorySubsegment = new Byte[3]; - using (var target = new SecureBuffer(length)) + using (var target = new SecureMemory(length)) { // Act. - target.Access((buffer => + target.Access(memory => { // Assert. - buffer.Length.Should().Be(length); - buffer.Should().OnlyContain(value => value == 0x00); + memory.Length.Should().Be(length); + memory.Should().OnlyContain(value => value == 0x00); for (var i = 0; i < length; i++) { - buffer[i] = (Byte)i; + memory[i] = (Byte)i; } // Arrange. - bufferReference = buffer; - Array.Copy(buffer, 1, bufferSubsegment, 0, 3); - })); + memoryReference = memory; + Array.Copy(memory, 1, memorySubsegment, 0, 3); + }); // Act. - target.Access((buffer => + target.Access(memory => { // Assert. - buffer.Length.Should().Be(length); - buffer[0].Should().Be(0x00); - buffer[4].Should().Be(0x04); - buffer[8].Should().Be(0x08); - })); + memory.Length.Should().Be(length); + memory[0].Should().Be(0x00); + memory[4].Should().Be(0x04); + memory[8].Should().Be(0x08); + }); } // Assert. - bufferReference.Length.Should().Be(length); - bufferReference.Should().OnlyContain(value => value == 0x00); - bufferSubsegment[0].Should().Be(0x01); - bufferSubsegment[1].Should().Be(0x02); - bufferSubsegment[2].Should().Be(0x03); + memoryReference.Length.Should().Be(length); + memoryReference.Should().OnlyContain(value => value == 0x00); + memorySubsegment[0].Should().Be(0x01); + memorySubsegment[1].Should().Be(0x02); + memorySubsegment[2].Should().Be(0x03); } [TestMethod] @@ -117,11 +117,11 @@ public void GenerateHardenedRandomBytes_ShouldYieldHighEntropy() var valueCountUpperBoundary = Convert.ToInt64(meanTarget + (Convert.ToDecimal(standardDeviationTarget) * curveTolerance)); // Act. - using (var array = SecureBuffer.GenerateHardenedRandomBytes(Convert.ToInt32(length))) + using (var array = SecureMemory.GenerateHardenedRandomBytes(Convert.ToInt32(length))) { - array.Access(buffer => + array.Access(memory => { - target = new RandomByteArrayProfile(buffer); + target = new RandomByteArrayProfile(memory); }); } @@ -162,7 +162,7 @@ public void ShouldNotLeakMemory() for (var i = 0; i < iterationCount; i++) { // Act. - using (var target = new SecureBuffer(length)) + using (var target = new SecureMemory(length)) { continue; } diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/SymmetricBinaryProcessorTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/SymmetricBinaryProcessorTests.cs index cb46b311..51792b46 100644 --- a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/SymmetricBinaryProcessorTests.cs +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/SymmetricBinaryProcessorTests.cs @@ -52,7 +52,7 @@ private static void Encrypt_ShouldReproduceTestVector(SymmetricAlgorithmSpecific { using (var randomnessProvider = RandomNumberGenerator.Create()) { - using (var secureKeyBuffer = new SecureBuffer(key.Length)) + using (var secureKeyBuffer = new SecureMemory(key.Length)) { // Arrange. var blockLengthInBytes = (blockLengthInBits / 8); diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/SymmetricKeyTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/SymmetricKeyTests.cs index 371f2e83..14533f06 100644 --- a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/SymmetricKeyTests.cs +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/SymmetricKeyTests.cs @@ -183,7 +183,7 @@ public void New_ShouldRaiseArgumentOutOfRangeException_ForInvalidLengthKeySource var derivationMode = SymmetricKeyDerivationMode.Truncation; var keyLength = 3; - using (var keySource = new PinnedBuffer(keyLength)) + using (var keySource = new PinnedMemory(keyLength)) { // Act. var action = new Action(() => @@ -291,13 +291,13 @@ public void ToBuffer_ShouldReturnValidResult() // Act. using (var buffer = target.ToBuffer()) { - buffer.Access((plaintext => + buffer.Access(plaintext => { // Assert. plaintext.Should().NotBeNullOrEmpty(); plaintext.Length.Should().Be(keyLengthInBytes); plaintext.Count(value => value == 0x00).Should().NotBe((Int32)plaintext.Length); - })); + }); } } } diff --git a/test/RapidField.SolidInstruments.ObjectComposition.UnitTests/ObjectFactoryTests.cs b/test/RapidField.SolidInstruments.ObjectComposition.UnitTests/ObjectFactoryTests.cs index f6737e56..6af994b6 100644 --- a/test/RapidField.SolidInstruments.ObjectComposition.UnitTests/ObjectFactoryTests.cs +++ b/test/RapidField.SolidInstruments.ObjectComposition.UnitTests/ObjectFactoryTests.cs @@ -56,7 +56,7 @@ public void SupportedProductTypes_ShouldReturnConfiguredTypes() var supportedProductTypes = target.SupportedProductTypes; // Assert. - supportedProductTypes.Should().Contain(typeof(PinnedBuffer)); + supportedProductTypes.Should().Contain(typeof(PinnedMemory)); supportedProductTypes.Should().Contain(typeof(CircularBuffer)); } } diff --git a/test/RapidField.SolidInstruments.ObjectComposition.UnitTests/SimulatedInstrumentFactory.cs b/test/RapidField.SolidInstruments.ObjectComposition.UnitTests/SimulatedInstrumentFactory.cs index d0f93f18..40a7fb8c 100644 --- a/test/RapidField.SolidInstruments.ObjectComposition.UnitTests/SimulatedInstrumentFactory.cs +++ b/test/RapidField.SolidInstruments.ObjectComposition.UnitTests/SimulatedInstrumentFactory.cs @@ -50,7 +50,7 @@ protected override void Configure(ObjectFactoryConfiguration configu configuration.StateControlMode = ConcurrencyControlMode.SingleThreadSpinLock; configuration.ProductionFunctions .Add(() => new SimulatedInstrument(configuration.StateControlMode)) - .Add(() => new PinnedBuffer(3)) + .Add(() => new PinnedMemory(3)) .Add(() => new CircularBuffer(5)); } From 939caac99d602c27b8ae59224d1e82d63f7495d0 Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Mon, 25 May 2020 15:31:02 -0500 Subject: [PATCH 29/55] Various enhancements throughout the subsystems to accommodate planned integrations. --- .../IDomainCommand.cs | 23 ++ .../ILabeledCommand.cs | 25 ++ .../IMetadataEnrichedCommand.cs | 25 ++ .../ITextualCommand.cs | 23 ++ .../TextualCommand.cs | 126 ++++++++ .../Concurrency/ConcurrencyControlToken.cs | 5 +- .../Concurrency/IConcurrencyControlToken.cs | 116 ++++++++ .../ILabeledObject.cs | 24 ++ .../IMetadataEnrichedObject.cs | 23 ++ .../DataAccessRepository.cs | 231 +-------------- .../IDataAccessRepository.cs | 86 +----- .../IReadOnlyDataAccessRepository.cs | 113 +++++++ .../ReadOnlyDataAccessRepository.cs | 278 ++++++++++++++++++ .../ILabeledEvent.cs | 13 +- .../IMetadataEnrichedEvent.cs | 12 +- 15 files changed, 786 insertions(+), 337 deletions(-) create mode 100644 src/RapidField.SolidInstruments.Command/IDomainCommand.cs create mode 100644 src/RapidField.SolidInstruments.Command/ILabeledCommand.cs create mode 100644 src/RapidField.SolidInstruments.Command/IMetadataEnrichedCommand.cs create mode 100644 src/RapidField.SolidInstruments.Command/ITextualCommand.cs create mode 100644 src/RapidField.SolidInstruments.Command/TextualCommand.cs create mode 100644 src/RapidField.SolidInstruments.Core/Concurrency/IConcurrencyControlToken.cs create mode 100644 src/RapidField.SolidInstruments.Core/ILabeledObject.cs create mode 100644 src/RapidField.SolidInstruments.Core/IMetadataEnrichedObject.cs create mode 100644 src/RapidField.SolidInstruments.DataAccess/IReadOnlyDataAccessRepository.cs create mode 100644 src/RapidField.SolidInstruments.DataAccess/ReadOnlyDataAccessRepository.cs diff --git a/src/RapidField.SolidInstruments.Command/IDomainCommand.cs b/src/RapidField.SolidInstruments.Command/IDomainCommand.cs new file mode 100644 index 00000000..002ef135 --- /dev/null +++ b/src/RapidField.SolidInstruments.Command/IDomainCommand.cs @@ -0,0 +1,23 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +namespace RapidField.SolidInstruments.Command +{ + /// + /// Represents a command to perform a domain action. + /// + public interface IDomainCommand : ILabeledCommand + { + } + + /// + /// Represents a command to perform a domain action that emits a result when processed. + /// + /// + /// The type of the result that is emitted when processing the command. + /// + public interface IDomainommand : ILabeledCommand + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/ILabeledCommand.cs b/src/RapidField.SolidInstruments.Command/ILabeledCommand.cs new file mode 100644 index 00000000..5aeea2e2 --- /dev/null +++ b/src/RapidField.SolidInstruments.Command/ILabeledCommand.cs @@ -0,0 +1,25 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; + +namespace RapidField.SolidInstruments.Command +{ + /// + /// Represents a command that is labeled with categorical and/or contextual information. + /// + public interface ILabeledCommand : ICommand, ILabeledObject + { + } + + /// + /// Represents a command that emits a result when processed and that is labeled with categorical and/or contextual information. + /// + /// + /// The type of the result that is emitted when processing the command. + /// + public interface ILabeledCommand : ICommand, ILabeledObject + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/IMetadataEnrichedCommand.cs b/src/RapidField.SolidInstruments.Command/IMetadataEnrichedCommand.cs new file mode 100644 index 00000000..0ec20f81 --- /dev/null +++ b/src/RapidField.SolidInstruments.Command/IMetadataEnrichedCommand.cs @@ -0,0 +1,25 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; + +namespace RapidField.SolidInstruments.Command +{ + /// + /// Represents a command that is enriched with metadata information. + /// + public interface IMetadataEnrichedCommand : ICommand, IMetadataEnrichedObject + { + } + + /// + /// Represents a command that emits a result when processed and that is enriched with metadata information. + /// + /// + /// The type of the result that is emitted when processing the command. + /// + public interface IMetadataEnrichedCommand : ICommand, IMetadataEnrichedObject + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/ITextualCommand.cs b/src/RapidField.SolidInstruments.Command/ITextualCommand.cs new file mode 100644 index 00000000..8b9ed237 --- /dev/null +++ b/src/RapidField.SolidInstruments.Command/ITextualCommand.cs @@ -0,0 +1,23 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.Command +{ + /// + /// Represents a command that is described by textual information. + /// + public interface ITextualCommand : ILabeledCommand, IMetadataEnrichedCommand + { + /// + /// Gets or sets the textual command value. + /// + public String Value + { + get; + set; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/TextualCommand.cs b/src/RapidField.SolidInstruments.Command/TextualCommand.cs new file mode 100644 index 00000000..8fc9c535 --- /dev/null +++ b/src/RapidField.SolidInstruments.Command/TextualCommand.cs @@ -0,0 +1,126 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.ArgumentValidation; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.Serialization; + +namespace RapidField.SolidInstruments.Command +{ + /// + /// Represents a command that is described by textual information. + /// + /// + /// is the default implementation of . + /// + [DataContract] + public sealed class TextualCommand : Command, ITextualCommand + { + /// + /// Initializes a new instance of the class. + /// + public TextualCommand() + : this(String.Empty) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The textual command value. + /// + /// + /// is . + /// + public TextualCommand(String value) + : this(value, Array.Empty()) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The textual command value. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// is -or- is . + /// + public TextualCommand(String value, IEnumerable labels) + : base() + { + LabelsReference = new List((labels.RejectIf().IsNull(nameof(labels)).TargetArgument)); + MetadataReference = new Dictionary(); + Value = value.RejectIf().IsNull(nameof(value)); + } + + /// + /// Gets a collection of textual labels that provide categorical and/or contextual information about the current + /// . + /// + [DataMember] + public ICollection Labels + { + get + { + if (LabelsReference is null) + { + // This is necessary to accommodate specific serialization scenarios. + LabelsReference = new List(); + } + + return LabelsReference; + } + } + + /// + /// Gets a dictionary of metadata for the current . + /// + [DataMember] + public IDictionary Metadata + { + get + { + if (MetadataReference is null) + { + // This is necessary to accommodate specific serialization scenarios. + MetadataReference = new Dictionary(); + } + + return MetadataReference; + } + } + + /// + /// Gets or sets the textual command value. + /// + [DataMember] + public String Value + { + get; + set; + } + + /// + /// Represents a collection of textual labels that provide categorical and/or contextual information about the current + /// . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private ICollection LabelsReference; + + /// + /// Represents a dictionary of metadata for the current . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private IDictionary MetadataReference; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControlToken.cs b/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControlToken.cs index ab06303d..21e607bb 100644 --- a/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControlToken.cs +++ b/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControlToken.cs @@ -16,7 +16,10 @@ namespace RapidField.SolidInstruments.Core.Concurrency /// /// Represents exclusive or semi-exclusive control of a resource or block of code by a single thread. /// - public sealed class ConcurrencyControlToken : IAsyncDisposable, IComparable, IEquatable, IDisposable + /// + /// is the default implementation of . + /// + public sealed class ConcurrencyControlToken : IAsyncDisposable, IComparable, IConcurrencyControlToken, IEquatable, IDisposable { /// /// Initializes a new instance of the class. diff --git a/src/RapidField.SolidInstruments.Core/Concurrency/IConcurrencyControlToken.cs b/src/RapidField.SolidInstruments.Core/Concurrency/IConcurrencyControlToken.cs new file mode 100644 index 00000000..936984ba --- /dev/null +++ b/src/RapidField.SolidInstruments.Core/Concurrency/IConcurrencyControlToken.cs @@ -0,0 +1,116 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Threading.Tasks; +using SystemTimeoutException = System.TimeoutException; + +namespace RapidField.SolidInstruments.Core.Concurrency +{ + /// + /// Represents exclusive or semi-exclusive control of a resource or block of code by a single thread. + /// + public interface IConcurrencyControlToken + { + /// + /// Instructs the current to wait for the specified task to complete before + /// releasing control to another thread. + /// + /// + /// An action to wait upon. + /// + /// + /// is . + /// + /// + /// is . + /// + public void AttachTask(Action action); + + /// + /// Instructs the current to wait for the specified task to complete before + /// releasing control to another thread. + /// + /// + /// A task to wait upon. + /// + /// + /// is . + /// + /// + /// is . + /// + public void AttachTask(Task task); + + /// + /// Interrogates the state of the current . + /// + /// + /// Use this method for graceful handling of operation cancellation and control expiration. + /// + /// + /// if the token is active and not expired, otherwise . + /// + public Boolean Poll(); + + /// + /// Interrogates the state of the current . + /// + /// + /// Use this method for graceful handling of operation cancellation and control expiration. + /// + /// + /// A value specifying whether or not an exception should be raised if the token is inactive. The default value is + /// . + /// + /// + /// if the token is active and not expired, otherwise . + /// + /// + /// is equal to and the token is expired. + /// + public Boolean Poll(Boolean raiseExceptionIfInactive); + + /// + /// Interrogates the state of the current . + /// + /// + /// Use this method for graceful handling of operation cancellation and control expiration. + /// + /// + /// A value specifying whether or not an exception should be raised if the token is inactive. The default value is + /// . + /// + /// + /// A value specifying whether or not an exception should be raised if the token is expired. The default value is + /// . + /// + /// + /// if the token is active and not expired, otherwise . + /// + /// + /// is equal to and the token is expired. + /// + /// + /// is equal to and the token is inactive. + /// + public Boolean Poll(Boolean raiseExceptionIfInactive, Boolean raiseExceptionIfExpired); + + /// + /// Gets a value indicating whether or not the associated thread currently has control of the resource. + /// + public Boolean IsActive + { + get; + } + + /// + /// Gets a value indicating whether or not the expiration threshold for the token has been exceeded. + /// + public Boolean IsExpired + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Core/ILabeledObject.cs b/src/RapidField.SolidInstruments.Core/ILabeledObject.cs new file mode 100644 index 00000000..66c08253 --- /dev/null +++ b/src/RapidField.SolidInstruments.Core/ILabeledObject.cs @@ -0,0 +1,24 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Collections.Generic; + +namespace RapidField.SolidInstruments.Core +{ + /// + /// Represents an object that is labeled with categorical and/or contextual information. + /// + public interface ILabeledObject + { + /// + /// Gets a collection of textual labels that provide categorical and/or contextual information about the current + /// . + /// + public ICollection Labels + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Core/IMetadataEnrichedObject.cs b/src/RapidField.SolidInstruments.Core/IMetadataEnrichedObject.cs new file mode 100644 index 00000000..be9e90e0 --- /dev/null +++ b/src/RapidField.SolidInstruments.Core/IMetadataEnrichedObject.cs @@ -0,0 +1,23 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Collections.Generic; + +namespace RapidField.SolidInstruments.Core +{ + /// + /// Represents an object that is enriched with metadata information. + /// + public interface IMetadataEnrichedObject + { + /// + /// Gets a dictionary of metadata for the current . + /// + public IDictionary Metadata + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess/DataAccessRepository.cs b/src/RapidField.SolidInstruments.DataAccess/DataAccessRepository.cs index 56fff34a..62b2eb81 100644 --- a/src/RapidField.SolidInstruments.DataAccess/DataAccessRepository.cs +++ b/src/RapidField.SolidInstruments.DataAccess/DataAccessRepository.cs @@ -2,7 +2,6 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= -using RapidField.SolidInstruments.Core; using RapidField.SolidInstruments.Core.ArgumentValidation; using RapidField.SolidInstruments.Core.Concurrency; using System; @@ -21,7 +20,7 @@ namespace RapidField.SolidInstruments.DataAccess /// /// The type of the entity. /// - public abstract class DataAccessRepository : Instrument, IDataAccessRepository + public abstract class DataAccessRepository : ReadOnlyDataAccessRepository, IDataAccessRepository where TEntity : class { /// @@ -153,147 +152,6 @@ public void AddRange(IEnumerable entities) } } - /// - /// Returns all entities from the current . - /// - /// - /// All entities within the current . - /// - /// - /// The object is disposed. - /// - public IQueryable All() - { - using (var controlToken = StateControl.Enter()) - { - RejectIfDisposed(); - return All(controlToken); - } - } - - /// - /// Determines whether or not any entities exist in the current . - /// - /// - /// if any entities exist in the current , otherwise - /// . - /// - /// - /// The object is disposed. - /// - public Boolean Any() => (Count() > 0); - - /// - /// Determines whether or not the specified entity exists in the current . - /// - /// - /// The entity to evaluate. - /// - /// - /// if the specified entity exists in the current , - /// otherwise . - /// - /// - /// is . - /// - /// - /// The object is disposed. - /// - public Boolean Contains(TEntity entity) - { - using (var controlToken = StateControl.Enter()) - { - RejectIfDisposed(); - return Contains(entity.RejectIf().IsNull(nameof(entity)), controlToken); - } - } - - /// - /// Determines whether or not any entities matching the specified predicate exist in the current - /// . - /// - /// - /// An expression to test each entity for a condition. - /// - /// - /// if any entities matching the specified predicate exist in the current - /// , otherwise . - /// - /// - /// is . - /// - /// - /// The object is disposed. - /// - public Boolean ContainsWhere(Expression> predicate) => CountWhere(predicate) > 0; - - /// - /// Returns the number of entities in the current . - /// - /// - /// The number of entities in the current . - /// - /// - /// The object is disposed. - /// - public Int64 Count() - { - using (var controlToken = StateControl.Enter()) - { - RejectIfDisposed(); - return Count(controlToken); - } - } - - /// - /// Returns the number of entities matching the specified predicate in the current - /// . - /// - /// - /// An expression to test each entity for a condition. - /// - /// - /// The number of entities matching the specified predicate in the current . - /// - /// - /// is . - /// - /// - /// The object is disposed. - /// - public Int64 CountWhere(Expression> predicate) - { - using (var controlToken = StateControl.Enter()) - { - RejectIfDisposed(); - return CountWhere(predicate.RejectIf().IsNull(nameof(predicate)), controlToken); - } - } - - /// - /// Returns all entities matching the specified predicate from the current . - /// - /// - /// An expression to test each entity for a condition. - /// - /// - /// All entities matching the specified predicate within the current . - /// - /// - /// is . - /// - /// - /// The object is disposed. - /// - public IQueryable FindWhere(Expression> predicate) - { - using (var controlToken = StateControl.Enter()) - { - RejectIfDisposed(); - return FindWhere(predicate.RejectIf().IsNull(nameof(predicate)), controlToken); - } - } - /// /// Removes the specified entity from the current . /// @@ -432,74 +290,6 @@ public void UpdateRange(IEnumerable entities) /// protected abstract void AddRange(IEnumerable entities, ConcurrencyControlToken controlToken); - /// - /// Returns all entities from the current . - /// - /// - /// A token that represents and manages contextual thread safety. - /// - /// - /// All entities within the current . - /// - protected abstract IQueryable All(ConcurrencyControlToken controlToken); - - /// - /// Determines whether or not any entities matching the specified predicate exist in the current - /// . - /// - /// - /// An expression to test each entity for a condition. - /// - /// - /// A token that represents and manages contextual thread safety. - /// - /// - /// if any entities matching the specified predicate exist in the current - /// , otherwise . - /// - protected abstract Boolean AnyWhere(Expression> predicate, ConcurrencyControlToken controlToken); - - /// - /// Determines whether or not the specified entity exists in the current . - /// - /// - /// The entity to evaluate. - /// - /// - /// A token that represents and manages contextual thread safety. - /// - /// - /// if the specified entity exists in the current , - /// otherwise . - /// - protected abstract Boolean Contains(TEntity entity, ConcurrencyControlToken controlToken); - - /// - /// Returns the number of entities in the current . - /// - /// - /// A token that represents and manages contextual thread safety. - /// - /// - /// The number of entities in the current . - /// - protected abstract Int64 Count(ConcurrencyControlToken controlToken); - - /// - /// Returns the number of entities matching the specified predicate in the current - /// . - /// - /// - /// An expression to test each entity for a condition. - /// - /// - /// A token that represents and manages contextual thread safety. - /// - /// - /// The number of entities matching the specified predicate in the current . - /// - protected abstract Int64 CountWhere(Expression> predicate, ConcurrencyControlToken controlToken); - /// /// Releases all resources consumed by the current . /// @@ -508,20 +298,6 @@ public void UpdateRange(IEnumerable entities) /// protected override void Dispose(Boolean disposing) => base.Dispose(disposing); - /// - /// Returns all entities matching the specified predicate from the current . - /// - /// - /// An expression to test each entity for a condition. - /// - /// - /// A token that represents and manages contextual thread safety. - /// - /// - /// All entities matching the specified predicate within the current . - /// - protected abstract IQueryable FindWhere(Expression> predicate, ConcurrencyControlToken controlToken); - /// /// Removes the specified entity from the current . /// @@ -565,10 +341,5 @@ public void UpdateRange(IEnumerable entities) /// A token that represents and manages contextual thread safety. /// protected abstract void UpdateRange(IEnumerable entities, ConcurrencyControlToken controlToken); - - /// - /// Gets the entity type of the current . - /// - public Type EntityType => typeof(TEntity); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess/IDataAccessRepository.cs b/src/RapidField.SolidInstruments.DataAccess/IDataAccessRepository.cs index e2f67325..fea9fa37 100644 --- a/src/RapidField.SolidInstruments.DataAccess/IDataAccessRepository.cs +++ b/src/RapidField.SolidInstruments.DataAccess/IDataAccessRepository.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; namespace RapidField.SolidInstruments.DataAccess @@ -15,7 +14,7 @@ namespace RapidField.SolidInstruments.DataAccess /// /// The type of the entity. /// - public interface IDataAccessRepository : IDataAccessRepository + public interface IDataAccessRepository : IReadOnlyDataAccessRepository where TEntity : class { /// @@ -82,89 +81,6 @@ public interface IDataAccessRepository : IDataAccessRepository /// public void AddRange(IEnumerable entities); - /// - /// Returns all entities from the current . - /// - /// - /// All entities within the current . - /// - /// - /// The object is disposed. - /// - public IQueryable All(); - - /// - /// Determines whether or not the specified entity exists in the current . - /// - /// - /// The entity to evaluate. - /// - /// - /// if the specified entity exists in the current , - /// otherwise . - /// - /// - /// is . - /// - /// - /// The object is disposed. - /// - public Boolean Contains(TEntity entity); - - /// - /// Determines whether or not any entities matching the specified predicate exist in the current - /// . - /// - /// - /// An expression to test each entity for a condition. - /// - /// - /// if any entities matching the specified predicate exist in the current - /// , otherwise . - /// - /// - /// is . - /// - /// - /// The object is disposed. - /// - public Boolean ContainsWhere(Expression> predicate); - - /// - /// Returns the number of entities matching the specified predicate in the current - /// . - /// - /// - /// An expression to test each entity for a condition. - /// - /// - /// The number of entities matching the specified predicate in the current . - /// - /// - /// is . - /// - /// - /// The object is disposed. - /// - public Int64 CountWhere(Expression> predicate); - - /// - /// Returns all entities matching the specified predicate from the current . - /// - /// - /// An expression to test each entity for a condition. - /// - /// - /// All entities matching the specified predicate within the current . - /// - /// - /// is . - /// - /// - /// The object is disposed. - /// - public IQueryable FindWhere(Expression> predicate); - /// /// Removes the specified entity from the current . /// diff --git a/src/RapidField.SolidInstruments.DataAccess/IReadOnlyDataAccessRepository.cs b/src/RapidField.SolidInstruments.DataAccess/IReadOnlyDataAccessRepository.cs new file mode 100644 index 00000000..8871b582 --- /dev/null +++ b/src/RapidField.SolidInstruments.DataAccess/IReadOnlyDataAccessRepository.cs @@ -0,0 +1,113 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Linq; +using System.Linq.Expressions; + +namespace RapidField.SolidInstruments.DataAccess +{ + /// + /// Performs read-only data access operations for a specified entity type. + /// + /// + /// The type of the entity. + /// + public interface IReadOnlyDataAccessRepository : IReadOnlyDataAccessRepository + where TEntity : class + { + /// + /// Returns all entities from the current . + /// + /// + /// All entities within the current . + /// + /// + /// The object is disposed. + /// + public IQueryable All(); + + /// + /// Determines whether or not the specified entity exists in the current + /// . + /// + /// + /// The entity to evaluate. + /// + /// + /// if the specified entity exists in the current + /// , otherwise . + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public Boolean Contains(TEntity entity); + + /// + /// Determines whether or not any entities matching the specified predicate exist in the current + /// . + /// + /// + /// An expression to test each entity for a condition. + /// + /// + /// if any entities matching the specified predicate exist in the current + /// , otherwise . + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public Boolean ContainsWhere(Expression> predicate); + + /// + /// Returns the number of entities matching the specified predicate in the current + /// . + /// + /// + /// An expression to test each entity for a condition. + /// + /// + /// The number of entities matching the specified predicate in the current + /// . + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public Int64 CountWhere(Expression> predicate); + + /// + /// Returns all entities matching the specified predicate from the current + /// . + /// + /// + /// An expression to test each entity for a condition. + /// + /// + /// All entities matching the specified predicate within the current . + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public IQueryable FindWhere(Expression> predicate); + } + + /// + /// Performs read-only data access operations for a specified entity type. + /// + public interface IReadOnlyDataAccessRepository : IDataAccessRepository + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess/ReadOnlyDataAccessRepository.cs b/src/RapidField.SolidInstruments.DataAccess/ReadOnlyDataAccessRepository.cs new file mode 100644 index 00000000..6dc0a94d --- /dev/null +++ b/src/RapidField.SolidInstruments.DataAccess/ReadOnlyDataAccessRepository.cs @@ -0,0 +1,278 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Core.Concurrency; +using System; +using System.Linq; +using System.Linq.Expressions; + +namespace RapidField.SolidInstruments.DataAccess +{ + /// + /// Performs read-only data access operations for a specified entity type. + /// + /// + /// is the default implementation of + /// . + /// + /// + /// The type of the entity. + /// + public abstract class ReadOnlyDataAccessRepository : Instrument, IReadOnlyDataAccessRepository + where TEntity : class + { + /// + /// Initializes a new instance of the class. + /// + protected ReadOnlyDataAccessRepository() + : base() + { + return; + } + + /// + /// Returns all entities from the current . + /// + /// + /// All entities within the current . + /// + /// + /// The object is disposed. + /// + public IQueryable All() + { + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + return All(controlToken); + } + } + + /// + /// Determines whether or not any entities exist in the current . + /// + /// + /// if any entities exist in the current , + /// otherwise . + /// + /// + /// The object is disposed. + /// + public Boolean Any() => (Count() > 0); + + /// + /// Determines whether or not the specified entity exists in the current + /// . + /// + /// + /// The entity to evaluate. + /// + /// + /// if the specified entity exists in the current + /// , otherwise . + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public Boolean Contains(TEntity entity) + { + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + return Contains(entity.RejectIf().IsNull(nameof(entity)), controlToken); + } + } + + /// + /// Determines whether or not any entities matching the specified predicate exist in the current + /// . + /// + /// + /// An expression to test each entity for a condition. + /// + /// + /// if any entities matching the specified predicate exist in the current + /// , otherwise . + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public Boolean ContainsWhere(Expression> predicate) => CountWhere(predicate) > 0; + + /// + /// Returns the number of entities in the current . + /// + /// + /// The number of entities in the current . + /// + /// + /// The object is disposed. + /// + public Int64 Count() + { + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + return Count(controlToken); + } + } + + /// + /// Returns the number of entities matching the specified predicate in the current + /// . + /// + /// + /// An expression to test each entity for a condition. + /// + /// + /// The number of entities matching the specified predicate in the current + /// . + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public Int64 CountWhere(Expression> predicate) + { + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + return CountWhere(predicate.RejectIf().IsNull(nameof(predicate)), controlToken); + } + } + + /// + /// Returns all entities matching the specified predicate from the current + /// . + /// + /// + /// An expression to test each entity for a condition. + /// + /// + /// All entities matching the specified predicate within the current . + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public IQueryable FindWhere(Expression> predicate) + { + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + return FindWhere(predicate.RejectIf().IsNull(nameof(predicate)), controlToken); + } + } + + /// + /// Returns all entities from the current . + /// + /// + /// A token that represents and manages contextual thread safety. + /// + /// + /// All entities within the current . + /// + protected abstract IQueryable All(ConcurrencyControlToken controlToken); + + /// + /// Determines whether or not any entities matching the specified predicate exist in the current + /// . + /// + /// + /// An expression to test each entity for a condition. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + /// + /// if any entities matching the specified predicate exist in the current + /// , otherwise . + /// + protected abstract Boolean AnyWhere(Expression> predicate, ConcurrencyControlToken controlToken); + + /// + /// Determines whether or not the specified entity exists in the current + /// . + /// + /// + /// The entity to evaluate. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + /// + /// if the specified entity exists in the current + /// , otherwise . + /// + protected abstract Boolean Contains(TEntity entity, ConcurrencyControlToken controlToken); + + /// + /// Returns the number of entities in the current . + /// + /// + /// A token that represents and manages contextual thread safety. + /// + /// + /// The number of entities in the current . + /// + protected abstract Int64 Count(ConcurrencyControlToken controlToken); + + /// + /// Returns the number of entities matching the specified predicate in the current + /// . + /// + /// + /// An expression to test each entity for a condition. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + /// + /// The number of entities matching the specified predicate in the current + /// . + /// + protected abstract Int64 CountWhere(Expression> predicate, ConcurrencyControlToken controlToken); + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Returns all entities matching the specified predicate from the current + /// . + /// + /// + /// An expression to test each entity for a condition. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + /// + /// All entities matching the specified predicate within the current . + /// + protected abstract IQueryable FindWhere(Expression> predicate, ConcurrencyControlToken controlToken); + + /// + /// Gets the entity type of the current . + /// + public Type EntityType => typeof(TEntity); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring/ILabeledEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/ILabeledEvent.cs index eceb69c5..39d583dd 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/ILabeledEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/ILabeledEvent.cs @@ -2,23 +2,14 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= -using System; -using System.Collections.Generic; +using RapidField.SolidInstruments.Core; namespace RapidField.SolidInstruments.EventAuthoring { /// /// Represents an event that is labeled with categorical and/or contextual information. /// - public interface ILabeledEvent : IEvent + public interface ILabeledEvent : IEvent, ILabeledObject { - /// - /// Gets a collection of textual labels that provide categorical and/or contextual information about the current - /// . - /// - public ICollection Labels - { - get; - } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring/IMetadataEnrichedEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/IMetadataEnrichedEvent.cs index ea785bc5..484a4ea4 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/IMetadataEnrichedEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/IMetadataEnrichedEvent.cs @@ -2,22 +2,14 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= -using System; -using System.Collections.Generic; +using RapidField.SolidInstruments.Core; namespace RapidField.SolidInstruments.EventAuthoring { /// /// Represents an event that is enriched with metadata information. /// - public interface IMetadataEnrichedEvent : IEvent + public interface IMetadataEnrichedEvent : IEvent, IMetadataEnrichedObject { - /// - /// Gets a dictionary of metadata for the current . - /// - public IDictionary Metadata - { - get; - } } } \ No newline at end of file From f23ee023a37332b28d9a4f58efe46780569035a1 Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Tue, 26 May 2020 13:42:02 -0500 Subject: [PATCH 30/55] Expanding use of interfaces, tweaking ToString implementations. --- .../RapidField.SolidInstruments.Command.md | 2 +- .../AddFibonacciNumberCommandHandler.cs | 2 +- .../GetFibonacciNumberValuesCommandHandler.cs | 2 +- .../ApplicationStartedEventMessageListener.cs | 2 +- .../ApplicationStoppedEventMessageListener.cs | 2 +- .../ExceptionRaisedEventMessageListener.cs | 2 +- .../HeartbeatMessageListener.cs | 2 +- .../PingRequestMessageListener.cs | 2 +- .../CircularBuffer.cs | 2 +- .../InfiniteSequence.cs | 2 +- .../ReadOnlyPinnedMemory.cs | 2 +- .../TreeNode.cs | 2 +- .../Command.cs | 4 +- .../CommandHandler.cs | 4 +- .../TextualCommand.cs | 46 ++------- .../AssemblyAttributes.cs | 1 + .../Concurrency/ConcurrencyControl.cs | 10 +- .../Concurrency/ConcurrencyControlToken.cs | 56 ++++++----- .../Concurrency/IConcurrencyControl.cs | 4 +- .../Concurrency/IConcurrencyControlToken.cs | 10 +- src/RapidField.SolidInstruments.Core/Model.cs | 2 +- .../ObjectBuilder.cs | 2 +- .../ReferenceManager.cs | 2 +- .../Secrets/CascadingSymmetricKeySecret.cs | 4 +- .../Secrets/GuidSecret.cs | 4 +- .../Secrets/NumericSecret.cs | 4 +- .../Secrets/Secret.cs | 16 +-- .../Secrets/SecretVault.cs | 2 +- .../Secrets/StringSecret.cs | 4 +- .../Secrets/SymmetricKeySecret.cs | 4 +- .../Secrets/X509CertificateSecret.cs | 4 +- .../SecureMemory.cs | 2 +- .../EntityFrameworkRepository.cs | 24 ++--- .../EntityFrameworkTransaction.cs | 12 +-- .../DataAccessCommandHandler.cs | 8 +- .../DataAccessRepository.cs | 12 +-- .../DataAccessTransaction.cs | 12 +-- .../ReadOnlyDataAccessRepository.cs | 12 +-- .../ApplicationStateEvent.cs | 1 + .../DomainEvent.cs | 23 +---- .../Event.cs | 2 +- .../GeneralInformationEvent.cs | 45 ++------- .../SystemStateEvent.cs | 1 + .../Statistics/DescriptiveStatistics.cs | 2 +- .../AzureServiceBusListeningFacade.cs | 2 +- .../AzureServiceBusTransmittingFacade.cs | 2 +- .../InMemoryListeningFacade.cs | 2 +- .../InMemoryTransmittingFacade.cs | 2 +- .../IMessageBase.cs | 2 +- .../IMessageProcessingInformation.cs | 50 ++++++++++ .../Message.cs | 5 +- .../MessageHandler.cs | 4 +- .../MessageListeningFacade.cs | 4 +- .../MessageListeningFailurePolicy.cs | 9 ++ .../MessageListeningRetryPolicy.cs | 8 ++ .../MessageProcessingAttemptResult.cs | 8 ++ .../MessageProcessingInformation.cs | 24 +++-- .../MessageRequestingFacade.cs | 4 +- .../MessageTransmitter.cs | 4 +- .../MessageTransmittingFacade.cs | 2 +- .../QueueTransmitter.cs | 2 +- .../RequestTransmitter.cs | 2 +- .../Service/HeartbeatScheduleItem.cs | 2 +- .../TopicTransmitter.cs | 2 +- .../TransportPrimitives/PrimitiveMessage.cs | 2 +- .../CompositeObjectFactory.cs | 4 +- .../FactoryProducedInstanceGroup.cs | 2 +- .../IObjectContainerBuilder.cs | 97 +++++++++++++++++++ ...ObjectContainerConfigurationDefinitions.cs | 43 ++++++++ .../IObjectContainerDefinition.cs | 38 ++++++++ ...FactoryConfigurationProductionFunctions.cs | 43 ++++++++ .../IObjectFactoryProductionFunction.cs | 33 +++++++ .../ObjectContainer.cs | 36 ++++--- .../ObjectContainerBuilder.cs | 44 +++++---- .../ObjectContainerConfiguration.cs | 2 +- ...ObjectContainerConfigurationDefinitions.cs | 14 ++- .../ObjectContainerDefinition.cs | 73 +++++++------- .../ObjectFactory.cs | 10 +- .../ObjectFactoryConfiguration.cs | 6 +- ...FactoryConfigurationProductionFunctions.cs | 24 +++-- .../ObjectFactoryProductionFunction.cs | 8 +- .../DynamicSerializer.cs | 4 +- .../ServiceExecutor.cs | 2 +- .../Channel.cs | 3 +- .../ChannelCollection.cs | 2 +- .../DiscreteUnitOfOutput.cs | 2 +- .../SignalSample.cs | 2 +- .../SimulatedCommandHandler.cs | 2 +- .../SimulatedCommandWithResultHandler.cs | 2 +- .../Concurrency/ConcurrencyControlTests.cs | 2 +- .../SimulatedRepository.cs | 24 ++--- .../SimulatedTransaction.cs | 12 +-- .../SimulatedObject.cs | 11 +-- .../ObjectContainerTests.cs | 49 +++++----- .../SimulatedObject.cs | 11 +-- 95 files changed, 710 insertions(+), 400 deletions(-) create mode 100644 src/RapidField.SolidInstruments.Messaging/IMessageProcessingInformation.cs create mode 100644 src/RapidField.SolidInstruments.ObjectComposition/IObjectContainerBuilder.cs create mode 100644 src/RapidField.SolidInstruments.ObjectComposition/IObjectContainerConfigurationDefinitions.cs create mode 100644 src/RapidField.SolidInstruments.ObjectComposition/IObjectContainerDefinition.cs create mode 100644 src/RapidField.SolidInstruments.ObjectComposition/IObjectFactoryConfigurationProductionFunctions.cs create mode 100644 src/RapidField.SolidInstruments.ObjectComposition/IObjectFactoryProductionFunction.cs diff --git a/doc/namespaces/RapidField.SolidInstruments.Command.md b/doc/namespaces/RapidField.SolidInstruments.Command.md index 58a33db6..4df49a6a 100644 --- a/doc/namespaces/RapidField.SolidInstruments.Command.md +++ b/doc/namespaces/RapidField.SolidInstruments.Command.md @@ -61,7 +61,7 @@ public class SubtractionCommandHandler : CommandHandler { } - protected override int Process(SubtractionCommand command, ConcurrencyControlToken controlToken) + protected override int Process(SubtractionCommand command, IConcurrencyControlToken controlToken) { return (command.Minuend - command.Subtrahend); } diff --git a/example/RapidField.SolidInstruments.Example.DatabaseModel/CommandHandlers/AddFibonacciNumberCommandHandler.cs b/example/RapidField.SolidInstruments.Example.DatabaseModel/CommandHandlers/AddFibonacciNumberCommandHandler.cs index a7e28c26..78bebaf6 100644 --- a/example/RapidField.SolidInstruments.Example.DatabaseModel/CommandHandlers/AddFibonacciNumberCommandHandler.cs +++ b/example/RapidField.SolidInstruments.Example.DatabaseModel/CommandHandlers/AddFibonacciNumberCommandHandler.cs @@ -61,7 +61,7 @@ public AddFibonacciNumberCommandHandler(ICommandMediator mediator, ExampleReposi /// /// A token that represents and manages contextual thread safety. /// - protected override void Process(AddFibonacciNumberCommand command, IFactoryProducedInstanceGroup repositories, IDataAccessTransaction transaction, ConcurrencyControlToken controlToken) + protected override void Process(AddFibonacciNumberCommand command, IFactoryProducedInstanceGroup repositories, IDataAccessTransaction transaction, IConcurrencyControlToken controlToken) { var fibonacciNumberSeries = NumberSeries.Named.Fibonacci; var numberRepository = repositories.Get(); diff --git a/example/RapidField.SolidInstruments.Example.DatabaseModel/CommandHandlers/GetFibonacciNumberValuesCommandHandler.cs b/example/RapidField.SolidInstruments.Example.DatabaseModel/CommandHandlers/GetFibonacciNumberValuesCommandHandler.cs index 703024b2..3a50a434 100644 --- a/example/RapidField.SolidInstruments.Example.DatabaseModel/CommandHandlers/GetFibonacciNumberValuesCommandHandler.cs +++ b/example/RapidField.SolidInstruments.Example.DatabaseModel/CommandHandlers/GetFibonacciNumberValuesCommandHandler.cs @@ -65,7 +65,7 @@ public GetFibonacciNumberValuesCommandHandler(ICommandMediator mediator, Example /// /// The result that is emitted when processing the command. /// - protected sealed override IEnumerable Process(GetFibonacciNumberValuesCommand command, IFactoryProducedInstanceGroup repositories, IDataAccessTransaction transaction, ConcurrencyControlToken controlToken) + protected sealed override IEnumerable Process(GetFibonacciNumberValuesCommand command, IFactoryProducedInstanceGroup repositories, IDataAccessTransaction transaction, IConcurrencyControlToken controlToken) { var numberSeriesNumberRepository = repositories.Get(); return numberSeriesNumberRepository.FindWhere(entity => entity.NumberSeries.Identifier == NumberSeries.Named.Fibonacci.Identifier).Select(entity => entity.Number.Value).ToList(); diff --git a/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/ApplicationStartedEventMessageListener.cs b/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/ApplicationStartedEventMessageListener.cs index 0cfb4f8c..70747f6e 100644 --- a/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/ApplicationStartedEventMessageListener.cs +++ b/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/ApplicationStartedEventMessageListener.cs @@ -51,6 +51,6 @@ public ApplicationStartedEventMessageListener(ICommandMediator mediator) /// /// A token that represents and manages contextual thread safety. /// - protected override void Process(ApplicationStartedEventMessage command, ICommandMediator mediator, ConcurrencyControlToken controlToken) => Console.WriteLine($"{command.Event.Description}{Environment.NewLine}"); + protected override void Process(ApplicationStartedEventMessage command, ICommandMediator mediator, IConcurrencyControlToken controlToken) => Console.WriteLine($"{command.Event.Description}{Environment.NewLine}"); } } \ No newline at end of file diff --git a/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/ApplicationStoppedEventMessageListener.cs b/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/ApplicationStoppedEventMessageListener.cs index 648c5457..2533b894 100644 --- a/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/ApplicationStoppedEventMessageListener.cs +++ b/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/ApplicationStoppedEventMessageListener.cs @@ -51,6 +51,6 @@ public ApplicationStoppedEventMessageListener(ICommandMediator mediator) /// /// A token that represents and manages contextual thread safety. /// - protected override void Process(ApplicationStoppedEventMessage command, ICommandMediator mediator, ConcurrencyControlToken controlToken) => Console.WriteLine($"{command.Event.Description}{Environment.NewLine}"); + protected override void Process(ApplicationStoppedEventMessage command, ICommandMediator mediator, IConcurrencyControlToken controlToken) => Console.WriteLine($"{command.Event.Description}{Environment.NewLine}"); } } \ No newline at end of file diff --git a/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/ExceptionRaisedEventMessageListener.cs b/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/ExceptionRaisedEventMessageListener.cs index 549d6e61..931a89b3 100644 --- a/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/ExceptionRaisedEventMessageListener.cs +++ b/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/ExceptionRaisedEventMessageListener.cs @@ -51,6 +51,6 @@ public ExceptionRaisedEventMessageListener(ICommandMediator mediator) /// /// A token that represents and manages contextual thread safety. /// - protected override void Process(ExceptionRaisedEventMessage command, ICommandMediator mediator, ConcurrencyControlToken controlToken) => Console.WriteLine($"{command.Event.Description}{Environment.NewLine}"); + protected override void Process(ExceptionRaisedEventMessage command, ICommandMediator mediator, IConcurrencyControlToken controlToken) => Console.WriteLine($"{command.Event.Description}{Environment.NewLine}"); } } \ No newline at end of file diff --git a/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/HeartbeatMessageListener.cs b/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/HeartbeatMessageListener.cs index 61ffd0cb..59fe1082 100644 --- a/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/HeartbeatMessageListener.cs +++ b/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/HeartbeatMessageListener.cs @@ -53,7 +53,7 @@ public HeartbeatMessageListener(ICommandMediator mediator) /// /// A token that represents and manages contextual thread safety. /// - protected override void Process(HeartbeatMessage command, ICommandMediator mediator, ConcurrencyControlToken controlToken) + protected override void Process(HeartbeatMessage command, ICommandMediator mediator, IConcurrencyControlToken controlToken) { var pingRequest = new PingRequestMessage(); var pingResponse = (PingResponseMessage)null; diff --git a/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/PingRequestMessageListener.cs b/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/PingRequestMessageListener.cs index 7f3e3bcd..5d9935b0 100644 --- a/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/PingRequestMessageListener.cs +++ b/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/PingRequestMessageListener.cs @@ -54,6 +54,6 @@ public PingRequestMessageListener(ICommandMediator mediator) /// /// The result that is emitted when processing the command. /// - protected override PingResponseMessage Process(PingRequestMessage command, ICommandMediator mediator, ConcurrencyControlToken controlToken) => new PingResponseMessage(command.Identifier, command.CorrelationIdentifier); + protected override PingResponseMessage Process(PingRequestMessage command, ICommandMediator mediator, IConcurrencyControlToken controlToken) => new PingResponseMessage(command.Identifier, command.CorrelationIdentifier); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Collections/CircularBuffer.cs b/src/RapidField.SolidInstruments.Collections/CircularBuffer.cs index 6fd0e664..16540afd 100644 --- a/src/RapidField.SolidInstruments.Collections/CircularBuffer.cs +++ b/src/RapidField.SolidInstruments.Collections/CircularBuffer.cs @@ -122,7 +122,7 @@ public T Read() /// /// A string representation of the current . /// - public override String ToString() => $"{{ {nameof(Capacity)}: {Capacity}, {nameof(Length)}: {Length} }}"; + public override String ToString() => $"{{ \"{nameof(Capacity)}\": {Capacity}, \"{nameof(Length)}\": {Length} }}"; /// /// Writes an element at the tail of the current . diff --git a/src/RapidField.SolidInstruments.Collections/InfiniteSequence.cs b/src/RapidField.SolidInstruments.Collections/InfiniteSequence.cs index 5bbecd38..1255e3ee 100644 --- a/src/RapidField.SolidInstruments.Collections/InfiniteSequence.cs +++ b/src/RapidField.SolidInstruments.Collections/InfiniteSequence.cs @@ -184,7 +184,7 @@ public T[] ToArray(Int32 startIndex, Int32 count) /// /// A string representation of the current . /// - public override String ToString() => $"{{ {nameof(CalculatedTermCount)}: {CalculatedTermCount} }}"; + public override String ToString() => $"{{ \"{nameof(CalculatedTermCount)}\": {CalculatedTermCount} }}"; /// /// Calculates the next term in the sequence. diff --git a/src/RapidField.SolidInstruments.Collections/ReadOnlyPinnedMemory.cs b/src/RapidField.SolidInstruments.Collections/ReadOnlyPinnedMemory.cs index 139a39b0..b4d7e361 100644 --- a/src/RapidField.SolidInstruments.Collections/ReadOnlyPinnedMemory.cs +++ b/src/RapidField.SolidInstruments.Collections/ReadOnlyPinnedMemory.cs @@ -197,7 +197,7 @@ public override Int32 GetHashCode() /// /// A string representation of the current . /// - public override String ToString() => $"{{ {nameof(Length)}: {Length}, {nameof(LengthInBytes)}: {LengthInBytes} }}"; + public override String ToString() => $"{{ \"{nameof(Length)}\": {Length}, \"{nameof(LengthInBytes)}\": {LengthInBytes} }}"; /// /// Releases all resources consumed by the current . diff --git a/src/RapidField.SolidInstruments.Collections/TreeNode.cs b/src/RapidField.SolidInstruments.Collections/TreeNode.cs index f408552e..59a8b1dc 100644 --- a/src/RapidField.SolidInstruments.Collections/TreeNode.cs +++ b/src/RapidField.SolidInstruments.Collections/TreeNode.cs @@ -268,7 +268,7 @@ public Boolean RemoveChild(TreeNode childNode) /// /// A string representation of the current . /// - public override String ToString() => $"{{ {nameof(Depth)}: {Depth}, {nameof(Height)}: {Height}, {nameof(Value)}: {Value} }}"; + public override String ToString() => $"{{ \"{nameof(Depth)}\": {Depth}, \"{nameof(Height)}\": {Height}, \"{nameof(Value)}\": \"{Value}\" }}"; /// /// Adds the specified node to and sets its to the current node. diff --git a/src/RapidField.SolidInstruments.Command/Command.cs b/src/RapidField.SolidInstruments.Command/Command.cs index 15eebcef..d85f2710 100644 --- a/src/RapidField.SolidInstruments.Command/Command.cs +++ b/src/RapidField.SolidInstruments.Command/Command.cs @@ -35,7 +35,7 @@ protected Command() /// /// A string representation of the current . /// - public override String ToString() => $"{{ {nameof(ResultType)}: {ResultType.FullName} }}"; + public override String ToString() => $"{{ \"{nameof(ResultType)}\": \"{ResultType.FullName}\" }}"; /// /// Gets the type of the result that is emitted when processing the command. @@ -73,7 +73,7 @@ protected Command() /// /// A string representation of the current . /// - public override String ToString() => $"{{ {nameof(ResultType)}: {ResultType.FullName} }}"; + public override String ToString() => $"{{ \"{nameof(ResultType)}\": \"{ResultType.FullName}\" }}"; /// /// Gets the type of the result that is emitted when processing the command. diff --git a/src/RapidField.SolidInstruments.Command/CommandHandler.cs b/src/RapidField.SolidInstruments.Command/CommandHandler.cs index 7500f782..64e791d2 100644 --- a/src/RapidField.SolidInstruments.Command/CommandHandler.cs +++ b/src/RapidField.SolidInstruments.Command/CommandHandler.cs @@ -94,7 +94,7 @@ public Nix Process(TCommand command) /// /// A token that represents and manages contextual thread safety. /// - protected abstract void Process(TCommand command, ICommandMediator mediator, ConcurrencyControlToken controlToken); + protected abstract void Process(TCommand command, ICommandMediator mediator, IConcurrencyControlToken controlToken); /// /// Represents processing intermediary that is used to process sub-commands. @@ -195,7 +195,7 @@ public TResult Process(TCommand command) /// /// The result that is emitted when processing the command. /// - protected abstract TResult Process(TCommand command, ICommandMediator mediator, ConcurrencyControlToken controlToken); + protected abstract TResult Process(TCommand command, ICommandMediator mediator, IConcurrencyControlToken controlToken); /// /// Represents processing intermediary that is used to process sub-commands. diff --git a/src/RapidField.SolidInstruments.Command/TextualCommand.cs b/src/RapidField.SolidInstruments.Command/TextualCommand.cs index 8fc9c535..9623e92d 100644 --- a/src/RapidField.SolidInstruments.Command/TextualCommand.cs +++ b/src/RapidField.SolidInstruments.Command/TextualCommand.cs @@ -5,7 +5,6 @@ using RapidField.SolidInstruments.Core.ArgumentValidation; using System; using System.Collections.Generic; -using System.Diagnostics; using System.Runtime.Serialization; namespace RapidField.SolidInstruments.Command @@ -58,46 +57,30 @@ public TextualCommand(String value) public TextualCommand(String value, IEnumerable labels) : base() { - LabelsReference = new List((labels.RejectIf().IsNull(nameof(labels)).TargetArgument)); - MetadataReference = new Dictionary(); + Labels = new List((labels.RejectIf().IsNull(nameof(labels)).TargetArgument)); + Metadata = new Dictionary(); Value = value.RejectIf().IsNull(nameof(value)); } /// - /// Gets a collection of textual labels that provide categorical and/or contextual information about the current + /// Gets or sets a collection of textual labels that provide categorical and/or contextual information about the current /// . /// [DataMember] public ICollection Labels { - get - { - if (LabelsReference is null) - { - // This is necessary to accommodate specific serialization scenarios. - LabelsReference = new List(); - } - - return LabelsReference; - } + get; + set; } /// - /// Gets a dictionary of metadata for the current . + /// Gets or sets a dictionary of metadata for the current . /// [DataMember] public IDictionary Metadata { - get - { - if (MetadataReference is null) - { - // This is necessary to accommodate specific serialization scenarios. - MetadataReference = new Dictionary(); - } - - return MetadataReference; - } + get; + set; } /// @@ -109,18 +92,5 @@ public String Value get; set; } - - /// - /// Represents a collection of textual labels that provide categorical and/or contextual information about the current - /// . - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private ICollection LabelsReference; - - /// - /// Represents a dictionary of metadata for the current . - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private IDictionary MetadataReference; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Core/AssemblyAttributes.cs b/src/RapidField.SolidInstruments.Core/AssemblyAttributes.cs index 14c7c5b9..bdd21ef0 100644 --- a/src/RapidField.SolidInstruments.Core/AssemblyAttributes.cs +++ b/src/RapidField.SolidInstruments.Core/AssemblyAttributes.cs @@ -6,4 +6,5 @@ [assembly: DisablePrivateReflection()] [assembly: InternalsVisibleTo("RapidField.SolidInstruments.Collections.UnitTests")] +[assembly: InternalsVisibleTo("RapidField.SolidInstruments.Core.UnitTests")] [assembly: InternalsVisibleTo("RapidField.SolidInstruments.ObjectComposition")] \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControl.cs b/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControl.cs index aa5a0674..a708e175 100644 --- a/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControl.cs +++ b/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControl.cs @@ -114,7 +114,7 @@ public void Dispose() /// /// The object is disposed. /// - public ConcurrencyControlToken Enter() + public IConcurrencyControlToken Enter() { RejectIfDisposed(); @@ -153,7 +153,7 @@ public ConcurrencyControlToken Enter() /// /// The object is disposed. /// - public void Exit(ConcurrencyControlToken token) + public void Exit(IConcurrencyControlToken token) { RejectIfDisposed(); @@ -189,7 +189,7 @@ public void Exit(ConcurrencyControlToken token) /// /// A string representation of the current . /// - public override String ToString() => $"{{ {nameof(ConsumptionState)}: {ConsumptionState} }}"; + public override String ToString() => $"{{ \"{nameof(ConsumptionState)}\": \"{ConsumptionState}\" }}"; /// /// Releases all resources consumed by the current . @@ -269,7 +269,7 @@ protected virtual void Dispose(Boolean disposing) /// The is in an invalid state. /// [DebuggerHidden] - private ConcurrencyControlToken GetNextToken(SynchronizationContext context, Thread granteeThread, TimeSpan expirationThreshold, Stopwatch expirationStopwatch) + private IConcurrencyControlToken GetNextToken(SynchronizationContext context, Thread granteeThread, TimeSpan expirationThreshold, Stopwatch expirationStopwatch) { var identifier = Interlocked.Increment(ref NextTokenIdentifier); Interlocked.CompareExchange(ref NextTokenIdentifier, 0, MaximumTokenIdentifier); @@ -330,7 +330,7 @@ public ConcurrencyControlConsumptionState ConsumptionState /// Represents currently-in-use tokens that were issued by the current . /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly ConcurrentDictionary Tokens = new ConcurrentDictionary(); + private readonly ConcurrentDictionary Tokens = new ConcurrentDictionary(); /// /// Represents the consumption state of the current . diff --git a/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControlToken.cs b/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControlToken.cs index 21e607bb..6198ec79 100644 --- a/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControlToken.cs +++ b/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControlToken.cs @@ -3,6 +3,7 @@ // ================================================================================================================================= using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Core.Extensions; using System; using System.Collections.Generic; using System.Diagnostics; @@ -19,7 +20,7 @@ namespace RapidField.SolidInstruments.Core.Concurrency /// /// is the default implementation of . /// - public sealed class ConcurrencyControlToken : IAsyncDisposable, IComparable, IConcurrencyControlToken, IEquatable, IDisposable + public sealed class ConcurrencyControlToken : IConcurrencyControlToken { /// /// Initializes a new instance of the class. @@ -69,7 +70,7 @@ internal ConcurrencyControlToken(SynchronizationContext context, Thread granteeT /// /// A value indicating whether or not the specified instances are not equal. /// - public static Boolean operator !=(ConcurrencyControlToken a, ConcurrencyControlToken b) => (a == b) == false; + public static Boolean operator !=(ConcurrencyControlToken a, IConcurrencyControlToken b) => (a == b) == false; /// /// Determines whether or not a specified instance is less than another specified @@ -84,7 +85,7 @@ internal ConcurrencyControlToken(SynchronizationContext context, Thread granteeT /// /// if the second object is earlier than the first object, otherwise . /// - public static Boolean operator <(ConcurrencyControlToken a, ConcurrencyControlToken b) => a.CompareTo(b) == -1; + public static Boolean operator <(ConcurrencyControlToken a, IConcurrencyControlToken b) => a.CompareTo(b) == -1; /// /// Determines whether or not a specified instance is less than or equal to another @@ -100,7 +101,7 @@ internal ConcurrencyControlToken(SynchronizationContext context, Thread granteeT /// if the second object is earlier than or equal to the first object, otherwise /// . /// - public static Boolean operator <=(ConcurrencyControlToken a, ConcurrencyControlToken b) => a.CompareTo(b) < 1; + public static Boolean operator <=(ConcurrencyControlToken a, IConcurrencyControlToken b) => a.CompareTo(b) < 1; /// /// Determines whether or not two specified instances are equal. @@ -114,7 +115,7 @@ internal ConcurrencyControlToken(SynchronizationContext context, Thread granteeT /// /// A value indicating whether or not the specified instances are equal. /// - public static Boolean operator ==(ConcurrencyControlToken a, ConcurrencyControlToken b) + public static Boolean operator ==(ConcurrencyControlToken a, IConcurrencyControlToken b) { if (a is null && b is null) { @@ -141,7 +142,7 @@ internal ConcurrencyControlToken(SynchronizationContext context, Thread granteeT /// /// if the second object is later than the first object, otherwise . /// - public static Boolean operator >(ConcurrencyControlToken a, ConcurrencyControlToken b) => a.CompareTo(b) == 1; + public static Boolean operator >(ConcurrencyControlToken a, IConcurrencyControlToken b) => a.CompareTo(b) == 1; /// /// Determines whether or not a specified instance is greater than or equal to @@ -157,7 +158,7 @@ internal ConcurrencyControlToken(SynchronizationContext context, Thread granteeT /// if the second object is later than or equal to the first object, otherwise /// . /// - public static Boolean operator >=(ConcurrencyControlToken a, ConcurrencyControlToken b) => a.CompareTo(b) > -1; + public static Boolean operator >=(ConcurrencyControlToken a, IConcurrencyControlToken b) => a.CompareTo(b) > -1; /// /// Instructs the current to wait for the specified task to complete before releasing @@ -209,7 +210,7 @@ public void AttachTask(Task task) /// Negative one if this instance is earlier than the specified instance; one if this instance is later than the supplied /// instance; zero if they are equal. /// - public Int32 CompareTo(ConcurrencyControlToken other) => Identifier.CompareTo(other.Identifier); + public Int32 CompareTo(IConcurrencyControlToken other) => Identifier.CompareTo(other.Identifier); /// /// Releases all resources consumed by the current . @@ -259,9 +260,9 @@ public override Boolean Equals(Object obj) { return false; } - else if (obj is ConcurrencyControlToken) + else if (obj is IConcurrencyControlToken) { - return Equals((ConcurrencyControlToken)obj); + return Equals((IConcurrencyControlToken)obj); } return false; @@ -276,7 +277,7 @@ public override Boolean Equals(Object obj) /// /// A value indicating whether or not the specified instances are equal. /// - public Boolean Equals(ConcurrencyControlToken other) => (Identifier == other.Identifier); + public Boolean Equals(IConcurrencyControlToken other) => (Identifier == other.Identifier); /// /// Returns the hash code for this instance. @@ -363,6 +364,14 @@ public Boolean Poll(Boolean raiseExceptionIfInactive, Boolean raiseExceptionIfEx return false; } + /// + /// Converts the value of the current to its equivalent string representation. + /// + /// + /// A string representation of the current . + /// + public override String ToString() => $"{{ \"{nameof(Identifier)}\": {Identifier}, \"{nameof(IsActive)}\": {IsActive.ToSerializedString()} }}"; + /// /// Releases exclusive or semi-exclusive control of the associated resource if is /// . @@ -376,7 +385,8 @@ public Boolean Poll(Boolean raiseExceptionIfInactive, Boolean raiseExceptionIfEx /// /// The issuing is in an invalid state. /// - public void Release() + [DebuggerHidden] + internal void Release() { if (Interlocked.Exchange(ref IsActiveValue, IsActiveFalse) == IsActiveTrue) { @@ -417,14 +427,6 @@ public void Release() } } - /// - /// Converts the value of the current to its equivalent string representation. - /// - /// - /// A string representation of the current . - /// - public override String ToString() => $"{{ {nameof(Identifier)}: {Identifier}, {nameof(IsActive)}: {IsActive} }}"; - /// /// Informs the issuing control that the associated thread is finished consuming the resource. /// @@ -444,6 +446,14 @@ private static void ExitIssuingControl(Object state) token.Control.Exit(token); } + /// + /// Gets a unique value that identifies the token within the context of the issuing control. + /// + public Int32 Identifier + { + get; + } + /// /// Gets a value indicating whether or not the associated thread currently has control of the resource. /// @@ -466,12 +476,6 @@ private static void ExitIssuingControl(Object state) [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal readonly IConcurrencyControl Control; - /// - /// Represents a unique value that identifies the token within the context of the issuing control. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - internal readonly Int32 Identifier; - /// /// Represents an integer value indicating that the associated thread currently does not have control of the resource. /// diff --git a/src/RapidField.SolidInstruments.Core/Concurrency/IConcurrencyControl.cs b/src/RapidField.SolidInstruments.Core/Concurrency/IConcurrencyControl.cs index 1d988264..198b535e 100644 --- a/src/RapidField.SolidInstruments.Core/Concurrency/IConcurrencyControl.cs +++ b/src/RapidField.SolidInstruments.Core/Concurrency/IConcurrencyControl.cs @@ -20,7 +20,7 @@ public interface IConcurrencyControl : IAsyncDisposable, IDisposable /// /// The object is disposed. /// - public ConcurrencyControlToken Enter(); + public IConcurrencyControlToken Enter(); /// /// Informs the control that a thread is exiting a block of code or has finished consuming a resource. @@ -35,7 +35,7 @@ public interface IConcurrencyControl : IAsyncDisposable, IDisposable /// /// The object is disposed. /// - public void Exit(ConcurrencyControlToken token); + public void Exit(IConcurrencyControlToken token); /// /// Gets the consumption state of the current . diff --git a/src/RapidField.SolidInstruments.Core/Concurrency/IConcurrencyControlToken.cs b/src/RapidField.SolidInstruments.Core/Concurrency/IConcurrencyControlToken.cs index 936984ba..047823e3 100644 --- a/src/RapidField.SolidInstruments.Core/Concurrency/IConcurrencyControlToken.cs +++ b/src/RapidField.SolidInstruments.Core/Concurrency/IConcurrencyControlToken.cs @@ -11,7 +11,7 @@ namespace RapidField.SolidInstruments.Core.Concurrency /// /// Represents exclusive or semi-exclusive control of a resource or block of code by a single thread. /// - public interface IConcurrencyControlToken + public interface IConcurrencyControlToken : IAsyncDisposable, IComparable, IDisposable, IEquatable { /// /// Instructs the current to wait for the specified task to complete before @@ -97,6 +97,14 @@ public interface IConcurrencyControlToken /// public Boolean Poll(Boolean raiseExceptionIfInactive, Boolean raiseExceptionIfExpired); + /// + /// Gets a unique value that identifies the token within the context of the issuing control. + /// + public Int32 Identifier + { + get; + } + /// /// Gets a value indicating whether or not the associated thread currently has control of the resource. /// diff --git a/src/RapidField.SolidInstruments.Core/Model.cs b/src/RapidField.SolidInstruments.Core/Model.cs index 32cfee08..d45060cb 100644 --- a/src/RapidField.SolidInstruments.Core/Model.cs +++ b/src/RapidField.SolidInstruments.Core/Model.cs @@ -183,7 +183,7 @@ protected Model(TIdentifier identifier) /// /// A string representation of the current . /// - public override String ToString() => $"{{ {nameof(Identifier)}: {Identifier} }}"; + public override String ToString() => $"{{ \"{nameof(Identifier)}\": \"{Identifier}\" }}"; /// /// Gets or sets a value that uniquely identifies the current . diff --git a/src/RapidField.SolidInstruments.Core/ObjectBuilder.cs b/src/RapidField.SolidInstruments.Core/ObjectBuilder.cs index 532b3fe2..c3a896de 100644 --- a/src/RapidField.SolidInstruments.Core/ObjectBuilder.cs +++ b/src/RapidField.SolidInstruments.Core/ObjectBuilder.cs @@ -73,6 +73,6 @@ public TResult ToResult() /// /// The configured instance. /// - protected abstract TResult ToResult(ConcurrencyControlToken controlToken); + protected abstract TResult ToResult(IConcurrencyControlToken controlToken); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Core/ReferenceManager.cs b/src/RapidField.SolidInstruments.Core/ReferenceManager.cs index 002f8b49..5e06227f 100644 --- a/src/RapidField.SolidInstruments.Core/ReferenceManager.cs +++ b/src/RapidField.SolidInstruments.Core/ReferenceManager.cs @@ -60,7 +60,7 @@ public void AddObject(T reference) /// /// A string representation of the current . /// - public override String ToString() => $"{{ {nameof(ObjectCount)}: {ObjectCount} }}"; + public override String ToString() => $"{{ \"{nameof(ObjectCount)}\": {ObjectCount} }}"; /// /// Releases all resources consumed by the current . diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/CascadingSymmetricKeySecret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/CascadingSymmetricKeySecret.cs index 1beedd02..01e7c77a 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/CascadingSymmetricKeySecret.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/CascadingSymmetricKeySecret.cs @@ -73,7 +73,7 @@ public static CascadingSymmetricKeySecret FromValue(String name, CascadingSymmet /// /// The resulting . /// - protected sealed override CascadingSymmetricKey ConvertBytesToValue(IReadOnlyPinnedMemory bytes, ConcurrencyControlToken controlToken) + protected sealed override CascadingSymmetricKey ConvertBytesToValue(IReadOnlyPinnedMemory bytes, IConcurrencyControlToken controlToken) { var result = (CascadingSymmetricKey)null; @@ -103,7 +103,7 @@ protected sealed override CascadingSymmetricKey ConvertBytesToValue(IReadOnlyPin /// /// as a pinned memory. /// - protected sealed override IReadOnlyPinnedMemory ConvertValueToBytes(CascadingSymmetricKey value, ConcurrencyControlToken controlToken) + protected sealed override IReadOnlyPinnedMemory ConvertValueToBytes(CascadingSymmetricKey value, IConcurrencyControlToken controlToken) { var result = (ReadOnlyPinnedMemory)null; diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/GuidSecret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/GuidSecret.cs index c0f16293..435a8ab3 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/GuidSecret.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/GuidSecret.cs @@ -69,7 +69,7 @@ public static GuidSecret FromValue(String name, Guid value) /// /// The resulting . /// - protected sealed override Guid ConvertBytesToValue(IReadOnlyPinnedMemory bytes, ConcurrencyControlToken controlToken) => new Guid(bytes.ReadOnlySpan); + protected sealed override Guid ConvertBytesToValue(IReadOnlyPinnedMemory bytes, IConcurrencyControlToken controlToken) => new Guid(bytes.ReadOnlySpan); /// /// Gets the bytes of , pins them in memory and returns the resulting @@ -84,7 +84,7 @@ public static GuidSecret FromValue(String name, Guid value) /// /// as pinned memory. /// - protected sealed override IReadOnlyPinnedMemory ConvertValueToBytes(Guid value, ConcurrencyControlToken controlToken) => new PinnedMemory(value.ToByteArray(), true); + protected sealed override IReadOnlyPinnedMemory ConvertValueToBytes(Guid value, IConcurrencyControlToken controlToken) => new PinnedMemory(value.ToByteArray(), true); /// /// Releases all resources consumed by the current . diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/NumericSecret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/NumericSecret.cs index e1a4d339..2cf063ab 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/NumericSecret.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/NumericSecret.cs @@ -70,7 +70,7 @@ public static NumericSecret FromValue(String name, Double value) /// /// The resulting . /// - protected sealed override Double ConvertBytesToValue(IReadOnlyPinnedMemory bytes, ConcurrencyControlToken controlToken) => BitConverter.ToDouble(bytes.ReadOnlySpan); + protected sealed override Double ConvertBytesToValue(IReadOnlyPinnedMemory bytes, IConcurrencyControlToken controlToken) => BitConverter.ToDouble(bytes.ReadOnlySpan); /// /// Gets the bytes of , pins them in memory and returns the resulting @@ -85,7 +85,7 @@ public static NumericSecret FromValue(String name, Double value) /// /// as pinned memory. /// - protected sealed override IReadOnlyPinnedMemory ConvertValueToBytes(Double value, ConcurrencyControlToken controlToken) => new PinnedMemory(value.ToByteArray(), true); + protected sealed override IReadOnlyPinnedMemory ConvertValueToBytes(Double value, IConcurrencyControlToken controlToken) => new PinnedMemory(value.ToByteArray(), true); /// /// Releases all resources consumed by the current . diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/Secret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/Secret.cs index 2a40df3b..4b7ab108 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/Secret.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/Secret.cs @@ -72,7 +72,7 @@ public static Secret FromValue(String name, Byte[] value) /// /// The resulting . /// - protected sealed override IReadOnlyPinnedMemory ConvertBytesToValue(IReadOnlyPinnedMemory bytes, ConcurrencyControlToken controlToken) => bytes; + protected sealed override IReadOnlyPinnedMemory ConvertBytesToValue(IReadOnlyPinnedMemory bytes, IConcurrencyControlToken controlToken) => bytes; /// /// Gets the bytes of , pins them in memory and returns the resulting @@ -87,7 +87,7 @@ public static Secret FromValue(String name, Byte[] value) /// /// as pinned memory. /// - protected sealed override IReadOnlyPinnedMemory ConvertValueToBytes(IReadOnlyPinnedMemory value, ConcurrencyControlToken controlToken) => value; + protected sealed override IReadOnlyPinnedMemory ConvertValueToBytes(IReadOnlyPinnedMemory value, IConcurrencyControlToken controlToken) => value; /// /// Releases all resources consumed by the current . @@ -211,7 +211,7 @@ public void Read(Action readAction) /// /// A string representation of the current . /// - public override String ToString() => $"{{ {nameof(Name)}: {Name} }}"; + public override String ToString() => $"{{ \"{nameof(Name)}\": \"{Name}\" }}"; /// /// Performs the specified write operation and encrypts the resulting value as a thread-safe, atomic operation. @@ -249,7 +249,7 @@ public void Write(Func writeFunction) /// /// The resulting . /// - protected abstract TValue ConvertBytesToValue(IReadOnlyPinnedMemory bytes, ConcurrencyControlToken controlToken); + protected abstract TValue ConvertBytesToValue(IReadOnlyPinnedMemory bytes, IConcurrencyControlToken controlToken); /// /// Gets the bytes of , pins them in memory and returns the resulting @@ -264,7 +264,7 @@ public void Write(Func writeFunction) /// /// as pinned memory. /// - protected abstract IReadOnlyPinnedMemory ConvertValueToBytes(TValue value, ConcurrencyControlToken controlToken); + protected abstract IReadOnlyPinnedMemory ConvertValueToBytes(TValue value, IConcurrencyControlToken controlToken); /// /// Releases all resources consumed by the current . @@ -304,7 +304,7 @@ protected override void Dispose(Boolean disposing) /// raised an exception. /// [DebuggerHidden] - private void Read(Action> readAction, ConcurrencyControlToken controlToken) + private void Read(Action> readAction, IConcurrencyControlToken controlToken) { if (HasValue == false) { @@ -361,7 +361,7 @@ private void Read(Action> readAction, ConcurrencyCon /// raised an exception. /// [DebuggerHidden] - private void Read(Action readAction, ConcurrencyControlToken controlToken) + private void Read(Action readAction, IConcurrencyControlToken controlToken) { try { @@ -400,7 +400,7 @@ private void Read(Action readAction, ConcurrencyControlToken controlToke /// raised an exception or returned an invalid . /// [DebuggerHidden] - private void Write(Func writeFunction, ConcurrencyControlToken controlToken) + private void Write(Func writeFunction, IConcurrencyControlToken controlToken) { try { diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretVault.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretVault.cs index b82f1a95..37c0582e 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretVault.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretVault.cs @@ -436,7 +436,7 @@ public void Clear() /// /// A string representation of the current . /// - public override String ToString() => $"{{ {nameof(Count)}: {Count} }}"; + public override String ToString() => $"{{ \"{nameof(Count)}\": {Count} }}"; /// /// Attempts to remove a secret with the specified name. diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/StringSecret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/StringSecret.cs index 65f42ca9..04abe4db 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/StringSecret.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/StringSecret.cs @@ -72,7 +72,7 @@ public static StringSecret FromValue(String name, String value) /// /// The resulting . /// - protected sealed override String ConvertBytesToValue(IReadOnlyPinnedMemory bytes, ConcurrencyControlToken controlToken) => Encoding.Unicode.GetString(bytes.ReadOnlySpan); + protected sealed override String ConvertBytesToValue(IReadOnlyPinnedMemory bytes, IConcurrencyControlToken controlToken) => Encoding.Unicode.GetString(bytes.ReadOnlySpan); /// /// Gets the bytes of , pins them in memory and returns the resulting @@ -87,7 +87,7 @@ public static StringSecret FromValue(String name, String value) /// /// as pinned memory. /// - protected sealed override IReadOnlyPinnedMemory ConvertValueToBytes(String value, ConcurrencyControlToken controlToken) => new PinnedMemory(Encoding.Unicode.GetBytes(value), true); + protected sealed override IReadOnlyPinnedMemory ConvertValueToBytes(String value, IConcurrencyControlToken controlToken) => new PinnedMemory(Encoding.Unicode.GetBytes(value), true); /// /// Releases all resources consumed by the current . diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/SymmetricKeySecret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/SymmetricKeySecret.cs index 7eee1a4d..14ad1490 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/SymmetricKeySecret.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/SymmetricKeySecret.cs @@ -73,7 +73,7 @@ public static SymmetricKeySecret FromValue(String name, SymmetricKey value) /// /// The resulting . /// - protected sealed override SymmetricKey ConvertBytesToValue(IReadOnlyPinnedMemory bytes, ConcurrencyControlToken controlToken) + protected sealed override SymmetricKey ConvertBytesToValue(IReadOnlyPinnedMemory bytes, IConcurrencyControlToken controlToken) { var result = (SymmetricKey)null; @@ -103,7 +103,7 @@ protected sealed override SymmetricKey ConvertBytesToValue(IReadOnlyPinnedMemory /// /// as a pinned memory. /// - protected sealed override IReadOnlyPinnedMemory ConvertValueToBytes(SymmetricKey value, ConcurrencyControlToken controlToken) + protected sealed override IReadOnlyPinnedMemory ConvertValueToBytes(SymmetricKey value, IConcurrencyControlToken controlToken) { var result = (ReadOnlyPinnedMemory)null; diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/X509CertificateSecret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/X509CertificateSecret.cs index cb3e25b3..b3338c79 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/X509CertificateSecret.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/X509CertificateSecret.cs @@ -73,7 +73,7 @@ public static X509CertificateSecret FromValue(String name, X509Certificate2 valu /// /// The resulting . /// - protected sealed override X509Certificate2 ConvertBytesToValue(IReadOnlyPinnedMemory bytes, ConcurrencyControlToken controlToken) => new X509Certificate2(bytes.ToArray()); + protected sealed override X509Certificate2 ConvertBytesToValue(IReadOnlyPinnedMemory bytes, IConcurrencyControlToken controlToken) => new X509Certificate2(bytes.ToArray()); /// /// Gets the bytes of , pins them in memory and returns the resulting @@ -88,7 +88,7 @@ public static X509CertificateSecret FromValue(String name, X509Certificate2 valu /// /// as pinned memory. /// - protected sealed override IReadOnlyPinnedMemory ConvertValueToBytes(X509Certificate2 value, ConcurrencyControlToken controlToken) => new PinnedMemory(value.RawData); + protected sealed override IReadOnlyPinnedMemory ConvertValueToBytes(X509Certificate2 value, IConcurrencyControlToken controlToken) => new PinnedMemory(value.RawData); /// /// Releases all resources consumed by the current . diff --git a/src/RapidField.SolidInstruments.Cryptography/SecureMemory.cs b/src/RapidField.SolidInstruments.Cryptography/SecureMemory.cs index b8bdc6d5..8e5b0e43 100644 --- a/src/RapidField.SolidInstruments.Cryptography/SecureMemory.cs +++ b/src/RapidField.SolidInstruments.Cryptography/SecureMemory.cs @@ -122,7 +122,7 @@ public void Access(Action action) /// /// A string representation of the current . /// - public override String ToString() => $"{{ {nameof(LengthInBytes)}: {LengthInBytes} }}"; + public override String ToString() => $"{{ \"{nameof(LengthInBytes)}\": {LengthInBytes} }}"; /// /// Releases all resources consumed by the current . diff --git a/src/RapidField.SolidInstruments.DataAccess.EntityFramework/EntityFrameworkRepository.cs b/src/RapidField.SolidInstruments.DataAccess.EntityFramework/EntityFrameworkRepository.cs index 874990dd..a5d913f5 100644 --- a/src/RapidField.SolidInstruments.DataAccess.EntityFramework/EntityFrameworkRepository.cs +++ b/src/RapidField.SolidInstruments.DataAccess.EntityFramework/EntityFrameworkRepository.cs @@ -51,7 +51,7 @@ public EntityFrameworkRepository(TContext context) /// /// A token that represents and manages contextual thread safety. /// - protected override void Add(TEntity entity, ConcurrencyControlToken controlToken) => Set.Add(entity); + protected override void Add(TEntity entity, IConcurrencyControlToken controlToken) => Set.Add(entity); /// /// Adds the specified entities to the current . @@ -62,7 +62,7 @@ public EntityFrameworkRepository(TContext context) /// /// A token that represents and manages contextual thread safety. /// - protected override void AddRange(IEnumerable entities, ConcurrencyControlToken controlToken) => Set.AddRange(entities); + protected override void AddRange(IEnumerable entities, IConcurrencyControlToken controlToken) => Set.AddRange(entities); /// /// Returns all entities from the current . @@ -73,7 +73,7 @@ public EntityFrameworkRepository(TContext context) /// /// All entities within the current . /// - protected override IQueryable All(ConcurrencyControlToken controlToken) => Set.AsQueryable(); + protected override IQueryable All(IConcurrencyControlToken controlToken) => Set.AsQueryable(); /// /// Determines whether or not any entities matching the specified predicate exist in the current @@ -89,7 +89,7 @@ public EntityFrameworkRepository(TContext context) /// if any entities matching the specified predicate exist in the current /// , otherwise . /// - protected override Boolean AnyWhere(Expression> predicate, ConcurrencyControlToken controlToken) => Set.AsNoTracking().Any(predicate); + protected override Boolean AnyWhere(Expression> predicate, IConcurrencyControlToken controlToken) => Set.AsNoTracking().Any(predicate); /// /// Determines whether or not the specified entity exists in the current @@ -105,7 +105,7 @@ public EntityFrameworkRepository(TContext context) /// if the specified entity exists in the current /// , otherwise . /// - protected override Boolean Contains(TEntity entity, ConcurrencyControlToken controlToken) + protected override Boolean Contains(TEntity entity, IConcurrencyControlToken controlToken) { var entityType = Context.Model.FindEntityType(typeof(TEntity)); var primaryKey = entityType.FindPrimaryKey(); @@ -143,7 +143,7 @@ protected override Boolean Contains(TEntity entity, ConcurrencyControlToken cont /// /// The number of entities in the current . /// - protected override Int64 Count(ConcurrencyControlToken controlToken) => Set.AsNoTracking().Count(); + protected override Int64 Count(IConcurrencyControlToken controlToken) => Set.AsNoTracking().Count(); /// /// Returns the number of entities matching the specified predicate in the current @@ -159,7 +159,7 @@ protected override Boolean Contains(TEntity entity, ConcurrencyControlToken cont /// The number of entities matching the specified predicate in the current /// . /// - protected override Int64 CountWhere(Expression> predicate, ConcurrencyControlToken controlToken) => Set.AsNoTracking().Count(predicate); + protected override Int64 CountWhere(Expression> predicate, IConcurrencyControlToken controlToken) => Set.AsNoTracking().Count(predicate); /// /// Releases all resources consumed by the current . @@ -183,7 +183,7 @@ protected override Boolean Contains(TEntity entity, ConcurrencyControlToken cont /// All entities matching the specified predicate within the current /// . /// - protected override IQueryable FindWhere(Expression> predicate, ConcurrencyControlToken controlToken) => Set.Where(predicate); + protected override IQueryable FindWhere(Expression> predicate, IConcurrencyControlToken controlToken) => Set.Where(predicate); /// /// Removes the specified entity from the current . @@ -194,7 +194,7 @@ protected override Boolean Contains(TEntity entity, ConcurrencyControlToken cont /// /// A token that represents and manages contextual thread safety. /// - protected override void Remove(TEntity entity, ConcurrencyControlToken controlToken) => Set.Remove(entity); + protected override void Remove(TEntity entity, IConcurrencyControlToken controlToken) => Set.Remove(entity); /// /// Removes the specified entities from the current . @@ -205,7 +205,7 @@ protected override Boolean Contains(TEntity entity, ConcurrencyControlToken cont /// /// A token that represents and manages contextual thread safety. /// - protected override void RemoveRange(IEnumerable entities, ConcurrencyControlToken controlToken) => Set.RemoveRange(entities); + protected override void RemoveRange(IEnumerable entities, IConcurrencyControlToken controlToken) => Set.RemoveRange(entities); /// /// Updates the specified entity in the current . @@ -216,7 +216,7 @@ protected override Boolean Contains(TEntity entity, ConcurrencyControlToken cont /// /// A token that represents and manages contextual thread safety. /// - protected override void Update(TEntity entity, ConcurrencyControlToken controlToken) => Set.Update(entity); + protected override void Update(TEntity entity, IConcurrencyControlToken controlToken) => Set.Update(entity); /// /// Updates the specified entities in the current . @@ -227,7 +227,7 @@ protected override Boolean Contains(TEntity entity, ConcurrencyControlToken cont /// /// A token that represents and manages contextual thread safety. /// - protected override void UpdateRange(IEnumerable entities, ConcurrencyControlToken controlToken) => Set.UpdateRange(entities); + protected override void UpdateRange(IEnumerable entities, IConcurrencyControlToken controlToken) => Set.UpdateRange(entities); /// /// Represents the database session for the current . diff --git a/src/RapidField.SolidInstruments.DataAccess.EntityFramework/EntityFrameworkTransaction.cs b/src/RapidField.SolidInstruments.DataAccess.EntityFramework/EntityFrameworkTransaction.cs index 11fba1e2..b7dbf63b 100644 --- a/src/RapidField.SolidInstruments.DataAccess.EntityFramework/EntityFrameworkTransaction.cs +++ b/src/RapidField.SolidInstruments.DataAccess.EntityFramework/EntityFrameworkTransaction.cs @@ -77,7 +77,7 @@ public EntityFrameworkTransaction(TContext context, IsolationLevel isolationLeve /// /// A token that ensures tread safety for the operation. /// - protected override void Begin(ConcurrencyControlToken controlToken) => Transaction = Context.Database.BeginTransaction(IsolationLevel); + protected override void Begin(IConcurrencyControlToken controlToken) => Transaction = Context.Database.BeginTransaction(IsolationLevel); /// /// Asynchronously initiates the current . @@ -88,7 +88,7 @@ public EntityFrameworkTransaction(TContext context, IsolationLevel isolationLeve /// /// A task representing the asynchronous operation. /// - protected override Task BeginAsync(ConcurrencyControlToken controlToken) => Context.Database.BeginTransactionAsync(IsolationLevel).ContinueWith(beginTransactionTask => + protected override Task BeginAsync(IConcurrencyControlToken controlToken) => Context.Database.BeginTransactionAsync(IsolationLevel).ContinueWith(beginTransactionTask => { Transaction = beginTransactionTask.Result; }); @@ -99,7 +99,7 @@ protected override Task BeginAsync(ConcurrencyControlToken controlToken) => Cont /// /// A token that ensures tread safety for the operation. /// - protected override void Commit(ConcurrencyControlToken controlToken) + protected override void Commit(IConcurrencyControlToken controlToken) { Context.SaveChanges(true); Transaction?.Commit(); @@ -115,7 +115,7 @@ protected override void Commit(ConcurrencyControlToken controlToken) /// /// A task representing the asynchronous operation. /// - protected override Task CommitAsync(ConcurrencyControlToken controlToken) => Context.SaveChangesAsync(true).ContinueWith(saveChangesTask => + protected override Task CommitAsync(IConcurrencyControlToken controlToken) => Context.SaveChangesAsync(true).ContinueWith(saveChangesTask => { Transaction?.Commit(); }); @@ -147,7 +147,7 @@ protected override void Dispose(Boolean disposing) /// /// A token that ensures tread safety for the operation. /// - protected override void Reject(ConcurrencyControlToken controlToken) + protected override void Reject(IConcurrencyControlToken controlToken) { var entities = Context.ChangeTracker.Entries(); @@ -180,7 +180,7 @@ protected override void Reject(ConcurrencyControlToken controlToken) /// /// A task representing the asynchronous operation. /// - protected override Task RejectAsync(ConcurrencyControlToken controlToken) + protected override Task RejectAsync(IConcurrencyControlToken controlToken) { var entities = Context.ChangeTracker.Entries(); var reloadTasks = new List(); diff --git a/src/RapidField.SolidInstruments.DataAccess/DataAccessCommandHandler.cs b/src/RapidField.SolidInstruments.DataAccess/DataAccessCommandHandler.cs index a0cd8c5f..9f1502b3 100644 --- a/src/RapidField.SolidInstruments.DataAccess/DataAccessCommandHandler.cs +++ b/src/RapidField.SolidInstruments.DataAccess/DataAccessCommandHandler.cs @@ -91,7 +91,7 @@ protected override void Dispose(Boolean disposing) /// /// A token that represents and manages contextual thread safety. /// - protected sealed override void Process(TCommand command, ICommandMediator mediator, ConcurrencyControlToken controlToken) + protected sealed override void Process(TCommand command, ICommandMediator mediator, IConcurrencyControlToken controlToken) { if (Transaction.State == DataAccessTransactionState.Ready) { @@ -136,7 +136,7 @@ protected sealed override void Process(TCommand command, ICommandMediator mediat /// /// A token that represents and manages contextual thread safety. /// - protected abstract void Process(TCommand command, IFactoryProducedInstanceGroup repositories, IDataAccessTransaction transaction, ConcurrencyControlToken controlToken); + protected abstract void Process(TCommand command, IFactoryProducedInstanceGroup repositories, IDataAccessTransaction transaction, IConcurrencyControlToken controlToken); /// /// Represents an object that provides access to data access repositories. @@ -237,7 +237,7 @@ protected override void Dispose(Boolean disposing) /// /// The result that is emitted when processing the command. /// - protected sealed override TResult Process(TCommand command, ICommandMediator mediator, ConcurrencyControlToken controlToken) + protected sealed override TResult Process(TCommand command, ICommandMediator mediator, IConcurrencyControlToken controlToken) { if (Transaction.State == DataAccessTransactionState.Ready) { @@ -285,7 +285,7 @@ protected sealed override TResult Process(TCommand command, ICommandMediator med /// /// The result that is emitted when processing the command. /// - protected abstract TResult Process(TCommand command, IFactoryProducedInstanceGroup repositories, IDataAccessTransaction transaction, ConcurrencyControlToken controlToken); + protected abstract TResult Process(TCommand command, IFactoryProducedInstanceGroup repositories, IDataAccessTransaction transaction, IConcurrencyControlToken controlToken); /// /// Represents an object that provides access to data access repositories. diff --git a/src/RapidField.SolidInstruments.DataAccess/DataAccessRepository.cs b/src/RapidField.SolidInstruments.DataAccess/DataAccessRepository.cs index 62b2eb81..bd6331d7 100644 --- a/src/RapidField.SolidInstruments.DataAccess/DataAccessRepository.cs +++ b/src/RapidField.SolidInstruments.DataAccess/DataAccessRepository.cs @@ -277,7 +277,7 @@ public void UpdateRange(IEnumerable entities) /// /// A token that represents and manages contextual thread safety. /// - protected abstract void Add(TEntity entity, ConcurrencyControlToken controlToken); + protected abstract void Add(TEntity entity, IConcurrencyControlToken controlToken); /// /// Adds the specified entities to the current . @@ -288,7 +288,7 @@ public void UpdateRange(IEnumerable entities) /// /// A token that represents and manages contextual thread safety. /// - protected abstract void AddRange(IEnumerable entities, ConcurrencyControlToken controlToken); + protected abstract void AddRange(IEnumerable entities, IConcurrencyControlToken controlToken); /// /// Releases all resources consumed by the current . @@ -307,7 +307,7 @@ public void UpdateRange(IEnumerable entities) /// /// A token that represents and manages contextual thread safety. /// - protected abstract void Remove(TEntity entity, ConcurrencyControlToken controlToken); + protected abstract void Remove(TEntity entity, IConcurrencyControlToken controlToken); /// /// Removes the specified entities from the current . @@ -318,7 +318,7 @@ public void UpdateRange(IEnumerable entities) /// /// A token that represents and manages contextual thread safety. /// - protected abstract void RemoveRange(IEnumerable entities, ConcurrencyControlToken controlToken); + protected abstract void RemoveRange(IEnumerable entities, IConcurrencyControlToken controlToken); /// /// Updates the specified entity in the current . @@ -329,7 +329,7 @@ public void UpdateRange(IEnumerable entities) /// /// A token that represents and manages contextual thread safety. /// - protected abstract void Update(TEntity entity, ConcurrencyControlToken controlToken); + protected abstract void Update(TEntity entity, IConcurrencyControlToken controlToken); /// /// Updates the specified entities in the current . @@ -340,6 +340,6 @@ public void UpdateRange(IEnumerable entities) /// /// A token that represents and manages contextual thread safety. /// - protected abstract void UpdateRange(IEnumerable entities, ConcurrencyControlToken controlToken); + protected abstract void UpdateRange(IEnumerable entities, IConcurrencyControlToken controlToken); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess/DataAccessTransaction.cs b/src/RapidField.SolidInstruments.DataAccess/DataAccessTransaction.cs index 95dcbe7f..86a20a60 100644 --- a/src/RapidField.SolidInstruments.DataAccess/DataAccessTransaction.cs +++ b/src/RapidField.SolidInstruments.DataAccess/DataAccessTransaction.cs @@ -236,7 +236,7 @@ public Task RejectAsync() /// /// A token that represents and manages contextual thread safety. /// - protected abstract void Begin(ConcurrencyControlToken controlToken); + protected abstract void Begin(IConcurrencyControlToken controlToken); /// /// Asynchronously initiates the current . @@ -247,7 +247,7 @@ public Task RejectAsync() /// /// A task representing the asynchronous operation. /// - protected abstract Task BeginAsync(ConcurrencyControlToken controlToken); + protected abstract Task BeginAsync(IConcurrencyControlToken controlToken); /// /// Commits all changes made within the scope of the current . @@ -255,7 +255,7 @@ public Task RejectAsync() /// /// A token that represents and manages contextual thread safety. /// - protected abstract void Commit(ConcurrencyControlToken controlToken); + protected abstract void Commit(IConcurrencyControlToken controlToken); /// /// Asynchronously commits all changes made within the scope of the current . @@ -266,7 +266,7 @@ public Task RejectAsync() /// /// A task representing the asynchronous operation. /// - protected abstract Task CommitAsync(ConcurrencyControlToken controlToken); + protected abstract Task CommitAsync(IConcurrencyControlToken controlToken); /// /// Releases all resources consumed by the current . @@ -295,7 +295,7 @@ protected override void Dispose(Boolean disposing) /// /// A token that represents and manages contextual thread safety. /// - protected abstract void Reject(ConcurrencyControlToken controlToken); + protected abstract void Reject(IConcurrencyControlToken controlToken); /// /// Asynchronously all changes made within the scope of the current . @@ -306,7 +306,7 @@ protected override void Dispose(Boolean disposing) /// /// A token that represents and manages contextual thread safety. /// - protected abstract Task RejectAsync(ConcurrencyControlToken controlToken); + protected abstract Task RejectAsync(IConcurrencyControlToken controlToken); /// /// Gets the state of the current . diff --git a/src/RapidField.SolidInstruments.DataAccess/ReadOnlyDataAccessRepository.cs b/src/RapidField.SolidInstruments.DataAccess/ReadOnlyDataAccessRepository.cs index 6dc0a94d..90c013c6 100644 --- a/src/RapidField.SolidInstruments.DataAccess/ReadOnlyDataAccessRepository.cs +++ b/src/RapidField.SolidInstruments.DataAccess/ReadOnlyDataAccessRepository.cs @@ -186,7 +186,7 @@ public IQueryable FindWhere(Expression> predicat /// /// All entities within the current . /// - protected abstract IQueryable All(ConcurrencyControlToken controlToken); + protected abstract IQueryable All(IConcurrencyControlToken controlToken); /// /// Determines whether or not any entities matching the specified predicate exist in the current @@ -202,7 +202,7 @@ public IQueryable FindWhere(Expression> predicat /// if any entities matching the specified predicate exist in the current /// , otherwise . /// - protected abstract Boolean AnyWhere(Expression> predicate, ConcurrencyControlToken controlToken); + protected abstract Boolean AnyWhere(Expression> predicate, IConcurrencyControlToken controlToken); /// /// Determines whether or not the specified entity exists in the current @@ -218,7 +218,7 @@ public IQueryable FindWhere(Expression> predicat /// if the specified entity exists in the current /// , otherwise . /// - protected abstract Boolean Contains(TEntity entity, ConcurrencyControlToken controlToken); + protected abstract Boolean Contains(TEntity entity, IConcurrencyControlToken controlToken); /// /// Returns the number of entities in the current . @@ -229,7 +229,7 @@ public IQueryable FindWhere(Expression> predicat /// /// The number of entities in the current . /// - protected abstract Int64 Count(ConcurrencyControlToken controlToken); + protected abstract Int64 Count(IConcurrencyControlToken controlToken); /// /// Returns the number of entities matching the specified predicate in the current @@ -245,7 +245,7 @@ public IQueryable FindWhere(Expression> predicat /// The number of entities matching the specified predicate in the current /// . /// - protected abstract Int64 CountWhere(Expression> predicate, ConcurrencyControlToken controlToken); + protected abstract Int64 CountWhere(Expression> predicate, IConcurrencyControlToken controlToken); /// /// Releases all resources consumed by the current . @@ -268,7 +268,7 @@ public IQueryable FindWhere(Expression> predicat /// /// All entities matching the specified predicate within the current . /// - protected abstract IQueryable FindWhere(Expression> predicate, ConcurrencyControlToken controlToken); + protected abstract IQueryable FindWhere(Expression> predicate, IConcurrencyControlToken controlToken); /// /// Gets the entity type of the current . diff --git a/src/RapidField.SolidInstruments.EventAuthoring/ApplicationStateEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/ApplicationStateEvent.cs index 83e2a52c..106607b0 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/ApplicationStateEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/ApplicationStateEvent.cs @@ -146,6 +146,7 @@ public String ApplicationIdentity public IDictionary Metadata { get; + set; } /// diff --git a/src/RapidField.SolidInstruments.EventAuthoring/DomainEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/DomainEvent.cs index 9b62f8c4..2553e655 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/DomainEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/DomainEvent.cs @@ -113,26 +113,18 @@ public DomainEvent(IEnumerable labels, EventVerbosity verbosity, String public DomainEvent(IEnumerable labels, EventVerbosity verbosity, String description, DateTime timeStamp) : base(StaticCategory, verbosity, description, timeStamp) { - LabelsReference = new List((labels.RejectIf().IsNull(nameof(labels)).TargetArgument)); + Labels = new List((labels.RejectIf().IsNull(nameof(labels)).TargetArgument)); } /// - /// Gets a collection of textual labels that provide categorical and/or contextual information about the current + /// Gets or sets a collection of textual labels that provide categorical and/or contextual information about the current /// . /// [DataMember] public ICollection Labels { - get - { - if (LabelsReference is null) - { - // This is necessary to accommodate specific serialization scenarios. - LabelsReference = new List(); - } - - return LabelsReference; - } + get; + set; } /// @@ -140,12 +132,5 @@ public ICollection Labels /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] private const EventCategory StaticCategory = EventCategory.Domain; - - /// - /// Represents a collection of textual labels that provide categorical and/or contextual information about the current - /// . - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private ICollection LabelsReference; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring/Event.cs b/src/RapidField.SolidInstruments.EventAuthoring/Event.cs index 972ee9c9..6610fb47 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/Event.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/Event.cs @@ -334,7 +334,7 @@ public Boolean Equals(IEvent other) /// /// A string representation of the current . /// - public override String ToString() => $"{{ {nameof(Description)}: \"{Description}\" }}"; + public override String ToString() => $"{{ \"{nameof(Description)}\": \"{Description}\" }}"; /// /// Gets or sets the category of the event. diff --git a/src/RapidField.SolidInstruments.EventAuthoring/GeneralInformationEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/GeneralInformationEvent.cs index c6697de6..78f68653 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/GeneralInformationEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/GeneralInformationEvent.cs @@ -113,45 +113,29 @@ public GeneralInformationEvent(IEnumerable labels, EventVerbosity verbos public GeneralInformationEvent(IEnumerable labels, EventVerbosity verbosity, String description, DateTime timeStamp) : base(StaticCategory, verbosity, description, timeStamp) { - LabelsReference = new List((labels.RejectIf().IsNull(nameof(labels)).TargetArgument)); - MetadataReference = new Dictionary(); + Labels = new List((labels.RejectIf().IsNull(nameof(labels)).TargetArgument)); + Metadata = new Dictionary(); } /// - /// Gets a collection of textual labels that provide categorical and/or contextual information about the current + /// Gets or sets a collection of textual labels that provide categorical and/or contextual information about the current /// . /// [DataMember] public ICollection Labels { - get - { - if (LabelsReference is null) - { - // This is necessary to accommodate specific serialization scenarios. - LabelsReference = new List(); - } - - return LabelsReference; - } + get; + set; } /// - /// Gets a dictionary of metadata for the current . + /// Gets or sets a dictionary of metadata for the current . /// [DataMember] public IDictionary Metadata { - get - { - if (MetadataReference is null) - { - // This is necessary to accommodate specific serialization scenarios. - MetadataReference = new Dictionary(); - } - - return MetadataReference; - } + get; + set; } /// @@ -159,18 +143,5 @@ public IDictionary Metadata /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] private const EventCategory StaticCategory = EventCategory.GeneralInformation; - - /// - /// Represents a collection of textual labels that provide categorical and/or contextual information about the current - /// . - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private ICollection LabelsReference; - - /// - /// Represents a dictionary of metadata for the current . - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private IDictionary MetadataReference; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring/SystemStateEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/SystemStateEvent.cs index 7544988e..23d576fe 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/SystemStateEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/SystemStateEvent.cs @@ -136,6 +136,7 @@ public SystemStateEvent(String systemIdentity, EventVerbosity verbosity, String public IDictionary Metadata { get; + set; } /// diff --git a/src/RapidField.SolidInstruments.Mathematics/Statistics/DescriptiveStatistics.cs b/src/RapidField.SolidInstruments.Mathematics/Statistics/DescriptiveStatistics.cs index 409079ad..b1772380 100644 --- a/src/RapidField.SolidInstruments.Mathematics/Statistics/DescriptiveStatistics.cs +++ b/src/RapidField.SolidInstruments.Mathematics/Statistics/DescriptiveStatistics.cs @@ -68,7 +68,7 @@ internal DescriptiveStatistics(Int32 size, Decimal minimum, Decimal maximum, Dec /// /// A string representation of the current . /// - public override String ToString() => $"{{ {nameof(Size)}: {Size}, {nameof(Mean)}: {Mean.RoundedTo(3)}, {nameof(StandardDeviation)}: {StandardDeviation.RoundedTo(3)} }}"; + public override String ToString() => $"{{ \"{nameof(Size)}\": {Size}, \"{nameof(Mean)}\": {Mean.RoundedTo(3)}, \"{nameof(StandardDeviation)}\": {StandardDeviation.RoundedTo(3)} }}"; /// /// Represents the highest value of the numeric collection represented by the current . diff --git a/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusListeningFacade.cs b/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusListeningFacade.cs index 30ba5a34..55db51f5 100644 --- a/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusListeningFacade.cs +++ b/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusListeningFacade.cs @@ -54,7 +54,7 @@ public AzureServiceBusListeningFacade(AzureServiceBusTransmittingFacade transmit /// /// A token that represents and manages contextual thread safety. /// - protected sealed override void RegisterMessageHandler(Action messageHandler, IReceiverClient receiveClient, ConcurrencyControlToken controlToken) + protected sealed override void RegisterMessageHandler(Action messageHandler, IReceiverClient receiveClient, IConcurrencyControlToken controlToken) { var messageHandlerFunction = new Func((message, cancellationToken) => { diff --git a/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusTransmittingFacade.cs b/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusTransmittingFacade.cs index f97e5764..47d7f11a 100644 --- a/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusTransmittingFacade.cs +++ b/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusTransmittingFacade.cs @@ -57,6 +57,6 @@ public AzureServiceBusTransmittingFacade(IMessagingClientFactory /// A task representing the asynchronous operation. /// - protected sealed override Task TransmitAsync(AzureServiceBusMessage message, ISenderClient sendClient, ConcurrencyControlToken controlToken) => sendClient.SendAsync(message); + protected sealed override Task TransmitAsync(AzureServiceBusMessage message, ISenderClient sendClient, IConcurrencyControlToken controlToken) => sendClient.SendAsync(message); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryListeningFacade.cs b/src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryListeningFacade.cs index dea385ad..358b026f 100644 --- a/src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryListeningFacade.cs +++ b/src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryListeningFacade.cs @@ -48,7 +48,7 @@ public InMemoryListeningFacade(InMemoryTransmittingFacade transmittingFacade) /// /// A token that represents and manages contextual thread safety. /// - protected sealed override void RegisterMessageHandler(Action messageHandler, IMessagingEntityReceiveClient receiveClient, ConcurrencyControlToken controlToken) + protected sealed override void RegisterMessageHandler(Action messageHandler, IMessagingEntityReceiveClient receiveClient, IConcurrencyControlToken controlToken) { var messageHandlerAction = new Action((message) => { diff --git a/src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryTransmittingFacade.cs b/src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryTransmittingFacade.cs index 8febc15a..da9590cd 100644 --- a/src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryTransmittingFacade.cs +++ b/src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryTransmittingFacade.cs @@ -56,6 +56,6 @@ public InMemoryTransmittingFacade(IMessagingClientFactory /// A task representing the asynchronous operation. /// - protected sealed override Task TransmitAsync(PrimitiveMessage message, IMessagingEntitySendClient sendClient, ConcurrencyControlToken controlToken) => sendClient.SendAsync(message); + protected sealed override Task TransmitAsync(PrimitiveMessage message, IMessagingEntitySendClient sendClient, IConcurrencyControlToken controlToken) => sendClient.SendAsync(message); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/IMessageBase.cs b/src/RapidField.SolidInstruments.Messaging/IMessageBase.cs index 85714aaa..a232e367 100644 --- a/src/RapidField.SolidInstruments.Messaging/IMessageBase.cs +++ b/src/RapidField.SolidInstruments.Messaging/IMessageBase.cs @@ -33,7 +33,7 @@ public Guid Identifier /// /// Gets or sets instructions and contextual information relating to processing for the current . /// - public MessageProcessingInformation ProcessingInformation + public IMessageProcessingInformation ProcessingInformation { get; set; diff --git a/src/RapidField.SolidInstruments.Messaging/IMessageProcessingInformation.cs b/src/RapidField.SolidInstruments.Messaging/IMessageProcessingInformation.cs new file mode 100644 index 00000000..9d4068bc --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/IMessageProcessingInformation.cs @@ -0,0 +1,50 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Collections.Generic; + +namespace RapidField.SolidInstruments.Messaging +{ + /// + /// Represents instructions and contextual information relating to processing for an . + /// + public interface IMessageProcessingInformation + { + /// + /// Gets the number of times that processing has been attempted for the associated message, or zero if processing has not + /// yet been attempted. + /// + public Int32 AttemptCount + { + get; + } + + /// + /// Gets an ordered collection of processing attempt results for the associated message, or an empty collection if + /// processing has not yet been attempted. + /// + public ICollection AttemptResults + { + get; + } + + /// + /// Gets or sets instructions that guide failure behavior for the listener. + /// + public MessageListeningFailurePolicy FailurePolicy + { + get; + set; + } + + /// + /// Gets a value indicating whether or not the associated message has been processed successfully. + /// + public Boolean IsSuccessfullyProcessed + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/Message.cs b/src/RapidField.SolidInstruments.Messaging/Message.cs index 64123503..4939dfcb 100644 --- a/src/RapidField.SolidInstruments.Messaging/Message.cs +++ b/src/RapidField.SolidInstruments.Messaging/Message.cs @@ -86,6 +86,7 @@ protected Message(Guid correlationIdentifier, Guid identifier) /// is the default implementation of . /// [DataContract] + [KnownType(typeof(MessageProcessingInformation))] public abstract class Message : IMessage { /// @@ -182,7 +183,7 @@ public Guid Identifier /// Gets or sets instructions and contextual information relating to processing for the current . /// [DataMember] - public MessageProcessingInformation ProcessingInformation + public IMessageProcessingInformation ProcessingInformation { get { @@ -233,6 +234,6 @@ public MessageProcessingInformation ProcessingInformation /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] [IgnoreDataMember] - private MessageProcessingInformation ProcessingInformationField; + private IMessageProcessingInformation ProcessingInformationField; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/MessageHandler.cs b/src/RapidField.SolidInstruments.Messaging/MessageHandler.cs index 59d22f06..4e7bc7ae 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessageHandler.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessageHandler.cs @@ -52,7 +52,7 @@ protected MessageHandler(ICommandMediator mediator, MessageHandlerRole role, Mes /// /// A string representation of the current . /// - public override String ToString() => $"{{ {nameof(EntityType)}: {EntityType}, {nameof(Role)}: {Role} }}"; + public override String ToString() => $"{{ \"{nameof(EntityType)}\": {EntityType}, \"{nameof(Role)}\": \"{Role}\" }}"; /// /// Releases all resources consumed by the current . @@ -128,7 +128,7 @@ protected MessageHandler(ICommandMediator mediator, MessageHandlerRole role, Mes /// /// A string representation of the current . /// - public override String ToString() => $"{{ {nameof(EntityType)}: {EntityType}, {nameof(Role)}: {Role} }}"; + public override String ToString() => $"{{ \"{nameof(EntityType)}\": {EntityType}, \"{nameof(Role)}\": \"{Role}\" }}"; /// /// Releases all resources consumed by the current . diff --git a/src/RapidField.SolidInstruments.Messaging/MessageListeningFacade.cs b/src/RapidField.SolidInstruments.Messaging/MessageListeningFacade.cs index e8033393..cf4f7b4f 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessageListeningFacade.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessageListeningFacade.cs @@ -487,7 +487,7 @@ internal void RegisterMessageHandler(Action messageHandler, /// if the specified message type was added; if it was already present. /// [DebuggerHidden] - internal Boolean TryAddListenedMessageType(ConcurrencyControlToken controlToken) + internal Boolean TryAddListenedMessageType(IConcurrencyControlToken controlToken) where TMessage : IMessageBase { var messageType = typeof(TMessage); @@ -522,7 +522,7 @@ internal Boolean TryAddListenedMessageType(ConcurrencyControlToken con /// /// A token that represents and manages contextual thread safety. /// - protected abstract void RegisterMessageHandler(Action adaptedMessageHandler, TReceiver receiveClient, ConcurrencyControlToken controlToken); + protected abstract void RegisterMessageHandler(Action adaptedMessageHandler, TReceiver receiveClient, IConcurrencyControlToken controlToken); /// /// Asynchronously transmits the specified instance. diff --git a/src/RapidField.SolidInstruments.Messaging/MessageListeningFailurePolicy.cs b/src/RapidField.SolidInstruments.Messaging/MessageListeningFailurePolicy.cs index 40f59bb3..d2fc6c5e 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessageListeningFailurePolicy.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessageListeningFailurePolicy.cs @@ -3,6 +3,7 @@ // ================================================================================================================================= using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Core.Extensions; using RapidField.SolidInstruments.Messaging.EventMessages; using System; using System.Diagnostics; @@ -94,6 +95,14 @@ public MessageListeningFailurePolicy(MessageListeningRetryPolicy retryPolicy, Me TransmitExceptionRaisedEventMessage = transmitExceptionRaisedEventMessage; } + /// + /// Converts the value of the current to its equivalent string representation. + /// + /// + /// A string representation of the current . + /// + public override String ToString() => $"{{ \"{nameof(TransmitExceptionRaisedEventMessage)}\": {TransmitExceptionRaisedEventMessage.ToSerializedString()}, \"{nameof(SecondaryFailureBehavior)}\": \"{SecondaryFailureBehavior}\" }}"; + /// /// Gets or sets information that defines retry behavior that is employed before employing secondary failure behavior. /// diff --git a/src/RapidField.SolidInstruments.Messaging/MessageListeningRetryPolicy.cs b/src/RapidField.SolidInstruments.Messaging/MessageListeningRetryPolicy.cs index 8f9bb3e6..9e6f260d 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessageListeningRetryPolicy.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessageListeningRetryPolicy.cs @@ -86,6 +86,14 @@ public MessageListeningRetryPolicy(Int32 retryCount, Int32 baseDelayDurationInSe RetryCount = retryCount.RejectIf().IsLessThan(0, nameof(retryCount)); } + /// + /// Converts the value of the current to its equivalent string representation. + /// + /// + /// A string representation of the current . + /// + public override String ToString() => $"{{ \"{nameof(RetryCount)}\": {RetryCount}, \"{nameof(BaseDelayDurationInSeconds)}\": {BaseDelayDurationInSeconds}, \"{nameof(DurationScale)}\": \"{DurationScale}\" }}"; + /// /// Gets or sets the number of seconds to wait between retries, or the duration, in seconds, from which to scale /// non-linearly. diff --git a/src/RapidField.SolidInstruments.Messaging/MessageProcessingAttemptResult.cs b/src/RapidField.SolidInstruments.Messaging/MessageProcessingAttemptResult.cs index 09f94f72..8175b3f4 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessageProcessingAttemptResult.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessageProcessingAttemptResult.cs @@ -110,6 +110,14 @@ public MessageProcessingAttemptResult(DateTime attemptEndTimeStamp, DateTime? at ExceptionStackTrace = exceptionStackTrace.IsNullOrEmpty() ? null : exceptionStackTrace; } + /// + /// Converts the value of the current to its equivalent string representation. + /// + /// + /// A string representation of the current . + /// + public override String ToString() => $"{{ \"{nameof(WasSuccessful)}\": {WasSuccessful.ToSerializedString()}, \"{nameof(AttemptEndTimeStamp)}\": \"{AttemptEndTimeStamp.ToSerializedString()}\" }}"; + /// /// Gets or sets the date and time when the associated attempt ended, successfully or otherwise. /// diff --git a/src/RapidField.SolidInstruments.Messaging/MessageProcessingInformation.cs b/src/RapidField.SolidInstruments.Messaging/MessageProcessingInformation.cs index d1edf37b..0ee1b49b 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessageProcessingInformation.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessageProcessingInformation.cs @@ -3,8 +3,9 @@ // ================================================================================================================================= using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Core.Extensions; using System; -using System.Collections.ObjectModel; +using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; @@ -13,8 +14,11 @@ namespace RapidField.SolidInstruments.Messaging /// /// Represents instructions and contextual information relating to processing for an . /// + /// + /// is the default implementation of . + /// [DataContract] - public sealed class MessageProcessingInformation + public sealed class MessageProcessingInformation : IMessageProcessingInformation { /// /// Initializes a new instance of the class. @@ -37,10 +41,18 @@ public MessageProcessingInformation() /// public MessageProcessingInformation(MessageListeningFailurePolicy failurePolicy) { - AttemptResults = new Collection(); + AttemptResults = new List(); FailurePolicy = failurePolicy.RejectIf().IsNull(nameof(failurePolicy)); } + /// + /// Converts the value of the current to its equivalent string representation. + /// + /// + /// A string representation of the current . + /// + public override String ToString() => $"{{ \"{nameof(IsSuccessfullyProcessed)}\": {IsSuccessfullyProcessed.ToSerializedString()}, \"{nameof(AttemptCount)}\": {AttemptCount} }}"; + /// /// Gets the number of times that processing has been attempted for the associated message, or zero if processing has not /// yet been attempted. @@ -49,14 +61,14 @@ public MessageProcessingInformation(MessageListeningFailurePolicy failurePolicy) public Int32 AttemptCount => AttemptResults.Count(); /// - /// Gets an ordered collection of processing attempt results for the associated message, or an empty collection if + /// Gets or sets an ordered collection of processing attempt results for the associated message, or an empty collection if /// processing has not yet been attempted. /// [DataMember] - public Collection AttemptResults + public ICollection AttemptResults { get; - private set; + set; } /// diff --git a/src/RapidField.SolidInstruments.Messaging/MessageRequestingFacade.cs b/src/RapidField.SolidInstruments.Messaging/MessageRequestingFacade.cs index 16f0f4f9..b4d1f3bc 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessageRequestingFacade.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessageRequestingFacade.cs @@ -78,7 +78,7 @@ protected MessageRequestingFacade(TListeningFacade listeningFacade) /// /// A token that represents and manages contextual thread safety. /// - protected sealed override void RegisterResponseHandler(Action responseHandler, ConcurrencyControlToken controlToken) => ListeningFacade.RegisterTopicMessageHandler(responseHandler); + protected sealed override void RegisterResponseHandler(Action responseHandler, IConcurrencyControlToken controlToken) => ListeningFacade.RegisterTopicMessageHandler(responseHandler); /// /// Asynchronously transmits the specified request message to a bus. @@ -240,7 +240,7 @@ public Task RequestAsync(TR /// /// A token that represents and manages contextual thread safety. /// - protected abstract void RegisterResponseHandler(Action responseHandler, ConcurrencyControlToken controlToken) + protected abstract void RegisterResponseHandler(Action responseHandler, IConcurrencyControlToken controlToken) where TResponseMessage : class, IResponseMessage; /// diff --git a/src/RapidField.SolidInstruments.Messaging/MessageTransmitter.cs b/src/RapidField.SolidInstruments.Messaging/MessageTransmitter.cs index 5c31c674..d1292480 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessageTransmitter.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessageTransmitter.cs @@ -68,7 +68,7 @@ protected MessageTransmitter(ICommandMediator mediator, IMessageTransmittingFaca /// /// A token that represents and manages contextual thread safety. /// - protected override void Process(TMessage command, ICommandMediator mediator, ConcurrencyControlToken controlToken) + protected override void Process(TMessage command, ICommandMediator mediator, IConcurrencyControlToken controlToken) { switch (EntityType) { @@ -159,7 +159,7 @@ protected MessageTransmitter(ICommandMediator mediator, IMessageRequestingFacade /// /// The result that is emitted when processing the command. /// - protected override TResponseMessage Process(TRequestMessage command, ICommandMediator mediator, ConcurrencyControlToken controlToken) + protected override TResponseMessage Process(TRequestMessage command, ICommandMediator mediator, IConcurrencyControlToken controlToken) { using (var requestTask = Facade.RequestAsync(command)) { diff --git a/src/RapidField.SolidInstruments.Messaging/MessageTransmittingFacade.cs b/src/RapidField.SolidInstruments.Messaging/MessageTransmittingFacade.cs index 1e94e96e..e2578b66 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessageTransmittingFacade.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessageTransmittingFacade.cs @@ -260,6 +260,6 @@ internal Task TransmitAsync(TMessage message, IEnumerable path /// /// A task representing the asynchronous operation. /// - protected abstract Task TransmitAsync(TAdaptedMessage message, TSender sendClient, ConcurrencyControlToken controlToken); + protected abstract Task TransmitAsync(TAdaptedMessage message, TSender sendClient, IConcurrencyControlToken controlToken); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/QueueTransmitter.cs b/src/RapidField.SolidInstruments.Messaging/QueueTransmitter.cs index 7d352ac6..71427e74 100644 --- a/src/RapidField.SolidInstruments.Messaging/QueueTransmitter.cs +++ b/src/RapidField.SolidInstruments.Messaging/QueueTransmitter.cs @@ -56,6 +56,6 @@ public QueueTransmitter(ICommandMediator mediator, IMessageTransmittingFacade fa /// /// A token that represents and manages contextual thread safety. /// - protected override void Process(TMessage command, ICommandMediator mediator, ConcurrencyControlToken controlToken) => base.Process(command, mediator, controlToken); + protected override void Process(TMessage command, ICommandMediator mediator, IConcurrencyControlToken controlToken) => base.Process(command, mediator, controlToken); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/RequestTransmitter.cs b/src/RapidField.SolidInstruments.Messaging/RequestTransmitter.cs index 7ac61831..ca9d3f78 100644 --- a/src/RapidField.SolidInstruments.Messaging/RequestTransmitter.cs +++ b/src/RapidField.SolidInstruments.Messaging/RequestTransmitter.cs @@ -63,6 +63,6 @@ public RequestTransmitter(ICommandMediator mediator, IMessageRequestingFacade fa /// /// The result that is emitted when processing the command. /// - protected override TResponseMessage Process(TRequestMessage command, ICommandMediator mediator, ConcurrencyControlToken controlToken) => base.Process(command, mediator, controlToken); + protected override TResponseMessage Process(TRequestMessage command, ICommandMediator mediator, IConcurrencyControlToken controlToken) => base.Process(command, mediator, controlToken); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/Service/HeartbeatScheduleItem.cs b/src/RapidField.SolidInstruments.Messaging/Service/HeartbeatScheduleItem.cs index 4d878882..921211cf 100644 --- a/src/RapidField.SolidInstruments.Messaging/Service/HeartbeatScheduleItem.cs +++ b/src/RapidField.SolidInstruments.Messaging/Service/HeartbeatScheduleItem.cs @@ -274,7 +274,7 @@ public override Boolean Equals(Object obj) /// /// A string representation of the current . /// - public override String ToString() => $"{{ {nameof(EntityType)}: {EntityType}, {nameof(MessageType)}: {MessageType.FullName}, {nameof(IntervalInSeconds)}: {IntervalInSeconds}, {nameof(Label)}: \"{Label}\" }}"; + public override String ToString() => $"{{ \"{nameof(EntityType)}\": \"{EntityType}\", {nameof(MessageType)}\": \"{MessageType.FullName}\", {nameof(IntervalInSeconds)}\": {IntervalInSeconds}, \"{nameof(Label)}\": \"{Label}\" }}"; /// /// Asynchronously transmits a single heartbeat message with characteristics defined by the current diff --git a/src/RapidField.SolidInstruments.Messaging/TopicTransmitter.cs b/src/RapidField.SolidInstruments.Messaging/TopicTransmitter.cs index 443800e4..7e35f5b0 100644 --- a/src/RapidField.SolidInstruments.Messaging/TopicTransmitter.cs +++ b/src/RapidField.SolidInstruments.Messaging/TopicTransmitter.cs @@ -56,6 +56,6 @@ public TopicTransmitter(ICommandMediator mediator, IMessageTransmittingFacade fa /// /// A token that represents and manages contextual thread safety. /// - protected override void Process(TMessage command, ICommandMediator mediator, ConcurrencyControlToken controlToken) => base.Process(command, mediator, controlToken); + protected override void Process(TMessage command, ICommandMediator mediator, IConcurrencyControlToken controlToken) => base.Process(command, mediator, controlToken); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/PrimitiveMessage.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/PrimitiveMessage.cs index b6baab28..8de1e819 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/PrimitiveMessage.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/PrimitiveMessage.cs @@ -190,7 +190,7 @@ public String BodyTypeName /// Gets instructions and contextual information relating to processing for the current . /// [IgnoreDataMember] - public MessageProcessingInformation ProcessingInformation => Body.ProcessingInformation; + public IMessageProcessingInformation ProcessingInformation => Body.ProcessingInformation; /// /// Gets or sets an exclusive lock token for the current message, which can be used to complete or abandon processing, or diff --git a/src/RapidField.SolidInstruments.ObjectComposition/CompositeObjectFactory.cs b/src/RapidField.SolidInstruments.ObjectComposition/CompositeObjectFactory.cs index cbcd8d2a..b934a958 100644 --- a/src/RapidField.SolidInstruments.ObjectComposition/CompositeObjectFactory.cs +++ b/src/RapidField.SolidInstruments.ObjectComposition/CompositeObjectFactory.cs @@ -227,9 +227,9 @@ public CompositeObjectFactory(IConfiguration applicationConfiguration, IList [DebuggerHidden] - internal sealed override ConcurrentDictionary DefineProductionFunctions() + internal sealed override ConcurrentDictionary DefineProductionFunctions() { - var dictionary = new ConcurrentDictionary(); + var dictionary = new ConcurrentDictionary(); foreach (var factory in Factories) { diff --git a/src/RapidField.SolidInstruments.ObjectComposition/FactoryProducedInstanceGroup.cs b/src/RapidField.SolidInstruments.ObjectComposition/FactoryProducedInstanceGroup.cs index 90c4caf1..992839bd 100644 --- a/src/RapidField.SolidInstruments.ObjectComposition/FactoryProducedInstanceGroup.cs +++ b/src/RapidField.SolidInstruments.ObjectComposition/FactoryProducedInstanceGroup.cs @@ -170,7 +170,7 @@ protected override void Dispose(Boolean disposing) /// An exception was raised during object production. /// [DebuggerHidden] - private T GetNew(ConcurrencyControlToken controlToken) + private T GetNew(IConcurrencyControlToken controlToken) where T : class { var instanceType = typeof(T); diff --git a/src/RapidField.SolidInstruments.ObjectComposition/IObjectContainerBuilder.cs b/src/RapidField.SolidInstruments.ObjectComposition/IObjectContainerBuilder.cs new file mode 100644 index 00000000..11ec70db --- /dev/null +++ b/src/RapidField.SolidInstruments.ObjectComposition/IObjectContainerBuilder.cs @@ -0,0 +1,97 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using System; + +namespace RapidField.SolidInstruments.ObjectComposition +{ + /// + /// Represents an object that configures and initializes new instances. + /// + public interface IObjectContainerBuilder : IObjectBuilder + { + /// + /// Configures the to support production of . + /// + /// + /// The type that is produced by the container. + /// + /// + /// The current . + /// + /// + /// An exception was raised while configuring the . See inner exception for details. + /// + public IObjectContainerBuilder Configure() + where TProduct : class, new(); + + /// + /// Configures the to support production of . + /// + /// + /// The type that is produced by the container. + /// + /// + /// A function that produces the specified type. + /// + /// + /// The current . + /// + /// + /// is . + /// + /// + /// An exception was raised while configuring the . See inner exception for details. + /// + public IObjectContainerBuilder Configure(Func productionFunction) + where TProduct : class; + + /// + /// Configures the to support production of in response to + /// a request for . + /// + /// + /// The request type that identifies the registration. + /// + /// + /// The type that is produced by the container as a result of a request for . + /// + /// + /// The current . + /// + /// + /// An exception was raised while configuring the . See inner exception for details. + /// + public IObjectContainerBuilder Configure() + where TRequest : class + where TProduct : class, TRequest, new(); + + /// + /// Configures the to support production of in response to + /// a request for . + /// + /// + /// The request type that identifies the registration. + /// + /// + /// The type that is produced by the container as a result of a request for . + /// + /// + /// A function that produces the specified type. + /// + /// + /// The current . + /// + /// + /// is . + /// + /// + /// An exception was raised while configuring the . See inner exception for details. + /// + public IObjectContainerBuilder Configure(Func productionFunction) + where TRequest : class + where TProduct : class, TRequest; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.ObjectComposition/IObjectContainerConfigurationDefinitions.cs b/src/RapidField.SolidInstruments.ObjectComposition/IObjectContainerConfigurationDefinitions.cs new file mode 100644 index 00000000..83a5fd33 --- /dev/null +++ b/src/RapidField.SolidInstruments.ObjectComposition/IObjectContainerConfigurationDefinitions.cs @@ -0,0 +1,43 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.ObjectComposition +{ + /// + /// Represents a collection of definitions that control the behavior of an when resolving + /// requested objects. + /// + public interface IObjectContainerConfigurationDefinitions + { + /// + /// Registers the specified product type with the associated . + /// + /// + /// The request type that identifies the registration. + /// + /// + /// The type that is produced by the container as a result of a request for . + /// + /// + /// A definition already exists for . + /// + public IObjectContainerConfigurationDefinitions Add() + where TRequest : class + where TProduct : class, TRequest; + + /// + /// Registers the specified product type with the associated . + /// + /// + /// The type that is registered for production by the container. + /// + /// + /// A definition already exists for . + /// + public IObjectContainerConfigurationDefinitions Add() + where TProduct : class; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.ObjectComposition/IObjectContainerDefinition.cs b/src/RapidField.SolidInstruments.ObjectComposition/IObjectContainerDefinition.cs new file mode 100644 index 00000000..699ee940 --- /dev/null +++ b/src/RapidField.SolidInstruments.ObjectComposition/IObjectContainerDefinition.cs @@ -0,0 +1,38 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.ObjectComposition +{ + /// + /// Represents information about how an resolves requests for objects. + /// + internal interface IObjectContainerDefinition : IComparable, IEquatable + { + /// + /// Converts the current to an array of bytes. + /// + /// + /// An array of bytes representing the current . + /// + public Byte[] ToByteArray(); + + /// + /// Gets the type that is produced as a result of a request for . + /// + public Type ProductType + { + get; + } + + /// + /// Gets the request type that identifies the current . + /// + public Type RequestType + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.ObjectComposition/IObjectFactoryConfigurationProductionFunctions.cs b/src/RapidField.SolidInstruments.ObjectComposition/IObjectFactoryConfigurationProductionFunctions.cs new file mode 100644 index 00000000..b5584780 --- /dev/null +++ b/src/RapidField.SolidInstruments.ObjectComposition/IObjectFactoryConfigurationProductionFunctions.cs @@ -0,0 +1,43 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.ObjectComposition +{ + /// + /// Represents a collection of configured types and functions that produce them using an . + /// + public interface IObjectFactoryConfigurationProductionFunctions : IObjectFactoryConfigurationProductionFunctions + { + } + + /// + /// Represents a collection of configured types and functions that produce them using an + /// . + /// + /// + /// The base type from which objects produced by the associated factory derive. + /// + public interface IObjectFactoryConfigurationProductionFunctions + { + /// + /// Defines the function that produces the specified type. + /// + /// + /// The type of the object produced by the function. + /// + /// + /// A function that produces an output of type . + /// + /// + /// A function is already defined for the specified type, . + /// + /// + /// is . + /// + public IObjectFactoryConfigurationProductionFunctions Add(Func function) + where TProduct : class, TProductBase; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.ObjectComposition/IObjectFactoryProductionFunction.cs b/src/RapidField.SolidInstruments.ObjectComposition/IObjectFactoryProductionFunction.cs new file mode 100644 index 00000000..b9211cbe --- /dev/null +++ b/src/RapidField.SolidInstruments.ObjectComposition/IObjectFactoryProductionFunction.cs @@ -0,0 +1,33 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.ObjectComposition +{ + /// + /// Represents a function that produces an object of a specified type. + /// + internal interface IObjectFactoryProductionFunction + { + /// + /// Invokes the function and returns an output object. + /// + /// + /// The function output. + /// + /// + /// An exception was raised during object production. + /// + public Object Invoke(); + + /// + /// Gets the type of the object produced by the function. + /// + public Type ProductType + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.ObjectComposition/ObjectContainer.cs b/src/RapidField.SolidInstruments.ObjectComposition/ObjectContainer.cs index c7af6f9d..17222977 100644 --- a/src/RapidField.SolidInstruments.ObjectComposition/ObjectContainer.cs +++ b/src/RapidField.SolidInstruments.ObjectComposition/ObjectContainer.cs @@ -36,7 +36,7 @@ public sealed class ObjectContainer : ConfigurableInstrument is -or- is /// . /// - public ObjectContainer(Action factoryConfigurator, Action definitionConfigurator) + public ObjectContainer(Action factoryConfigurator, Action definitionConfigurator) : this(InitializeFactory(DefaultConfiguration, factoryConfigurator.RejectIf().IsNull()), definitionConfigurator) { return; @@ -55,7 +55,7 @@ public ObjectContainer(Action fac /// is -or- is /// . /// - public ObjectContainer(IObjectFactory factory, Action definitionConfigurator) + public ObjectContainer(IObjectFactory factory, Action definitionConfigurator) : this(DefaultConfiguration, factory, definitionConfigurator, false) { return; @@ -77,7 +77,7 @@ public ObjectContainer(IObjectFactory factory, Action is -or- /// is -or- is . /// - public ObjectContainer(IConfiguration applicationConfiguration, IObjectFactory factory, Action definitionConfigurator) + public ObjectContainer(IConfiguration applicationConfiguration, IObjectFactory factory, Action definitionConfigurator) : this(applicationConfiguration, factory, definitionConfigurator, false) { return; @@ -103,7 +103,7 @@ public ObjectContainer(IConfiguration applicationConfiguration, IObjectFactory f /// is -or- is . /// [DebuggerHidden] - private ObjectContainer(IConfiguration applicationConfiguration, IObjectFactory factory, Action definitionConfigurator, Boolean managesFactory) + private ObjectContainer(IConfiguration applicationConfiguration, IObjectFactory factory, Action definitionConfigurator, Boolean managesFactory) : base(applicationConfiguration) { DefinitionConfigurator = definitionConfigurator.RejectIf().IsNull(nameof(definitionConfigurator)); @@ -142,7 +142,7 @@ public T Get() try { - if (Configuration.Definitions.Registrations.ContainsKey(requestType) == false) + if (Registrations.ContainsKey(requestType) == false) { throw new ArgumentException($"{requestType.FullName} is not a registered request type.", nameof(T)); } @@ -152,7 +152,7 @@ public T Get() throw new ObjectProductionException(requestType, exception); } - var productType = Configuration.Definitions.Registrations[requestType].ProductType; + var productType = Registrations[requestType].ProductType; if (Factory.SupportedProductTypes.Contains(productType) == false) { @@ -211,12 +211,12 @@ public T GetNew() try { - if (Configuration.Definitions.Registrations.ContainsKey(requestType) == false) + if (Registrations.ContainsKey(requestType) == false) { throw new ArgumentException($"{requestType.FullName} is not a registered request type.", nameof(T)); } - var productType = Configuration.Definitions.Registrations[requestType].ProductType; + var productType = Registrations[requestType].ProductType; var getNewMethod = InstanceGroup.GetType().GetMethod(nameof(InstanceGroup.GetNew), Array.Empty()).MakeGenericMethod(productType); if (!(getNewMethod.Invoke(InstanceGroup, Array.Empty()) is T newProduct)) @@ -290,7 +290,7 @@ protected override void Dispose(Boolean disposing) /// is . /// [DebuggerHidden] - private static IObjectFactory InitializeFactory(IConfiguration applicationConfiguration, Action factoryConfigurator) => new ContainerObjectFactory(applicationConfiguration, factoryConfigurator); + private static IObjectFactory InitializeFactory(IConfiguration applicationConfiguration, Action factoryConfigurator) => new ContainerObjectFactory(applicationConfiguration, factoryConfigurator); /// /// Gets a collection of managed object instances. @@ -308,7 +308,7 @@ private IDictionary InitializeInstanceDictionary() try { - foreach (var definition in Configuration.Definitions.Registrations.Values) + foreach (var definition in Registrations.Values) { var getLazyMethodInfo = InstanceGroup.GetType().GetMethod(nameof(InstanceGroup.GetLazy)).MakeGenericMethod(definition.ProductType); var lazyInstance = getLazyMethodInfo.Invoke(InstanceGroup, Array.Empty()); @@ -342,7 +342,7 @@ public IEnumerable InstanceTypes { get { - foreach (var instanceType in Configuration.Definitions.Registrations.Keys) + foreach (var instanceType in Registrations.Keys) { RejectIfDisposed(); yield return instanceType; @@ -350,6 +350,12 @@ public IEnumerable InstanceTypes } } + /// + /// Gets a collection of request-product type pairs that constitute the configured definitions for the container. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private IDictionary Registrations => ((ObjectContainerConfigurationDefinitions)Configuration.Definitions).Registrations; + /// /// Gets a collection of managed object instances. /// @@ -369,7 +375,7 @@ public IEnumerable InstanceTypes /// Represents an action that configures the request-product type pair definitions for the container. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly Action DefinitionConfigurator; + private readonly Action DefinitionConfigurator; /// /// Represents an factory that produces objects for the current . @@ -416,10 +422,10 @@ private sealed class ContainerObjectFactory : ObjectFactory /// is . /// [DebuggerHidden] - internal ContainerObjectFactory(IConfiguration applicationConfiguration, Action factoryConfigurator) + internal ContainerObjectFactory(IConfiguration applicationConfiguration, Action factoryConfigurator) : base(applicationConfiguration) { - FactoryConfigurator = new Action>((functions) => + FactoryConfigurator = new Action>((functions) => { factoryConfigurator(new ObjectFactoryConfigurationProductionFunctions(functions)); }); @@ -445,7 +451,7 @@ internal ContainerObjectFactory(IConfiguration applicationConfiguration, Action< /// Represents an action that configures the object factory. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly Action> FactoryConfigurator; + private readonly Action> FactoryConfigurator; } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.ObjectComposition/ObjectContainerBuilder.cs b/src/RapidField.SolidInstruments.ObjectComposition/ObjectContainerBuilder.cs index f427a983..d3778078 100644 --- a/src/RapidField.SolidInstruments.ObjectComposition/ObjectContainerBuilder.cs +++ b/src/RapidField.SolidInstruments.ObjectComposition/ObjectContainerBuilder.cs @@ -15,7 +15,10 @@ namespace RapidField.SolidInstruments.ObjectComposition /// /// Represents an object that configures and initializes new instances. /// - public sealed class ObjectContainerBuilder : ObjectBuilder + /// + /// is the default implementation of . + /// + public sealed class ObjectContainerBuilder : ObjectBuilder, IObjectContainerBuilder { /// /// Initializes a new instance of the class. @@ -38,7 +41,7 @@ public ObjectContainerBuilder() /// /// An exception was raised while configuring the . See inner exception for details. /// - public ObjectContainerBuilder Configure() + public IObjectContainerBuilder Configure() where TProduct : class, new() => Configure(() => new TProduct()); /// @@ -59,7 +62,7 @@ public ObjectContainerBuilder Configure() /// /// An exception was raised while configuring the . See inner exception for details. /// - public ObjectContainerBuilder Configure(Func productionFunction) + public IObjectContainerBuilder Configure(Func productionFunction) where TProduct : class => Configure(productionFunction); /// @@ -78,7 +81,7 @@ public ObjectContainerBuilder Configure(Func productionFunct /// /// An exception was raised while configuring the . See inner exception for details. /// - public ObjectContainerBuilder Configure() + public IObjectContainerBuilder Configure() where TRequest : class where TProduct : class, TRequest, new() => Configure(() => new TProduct()); @@ -104,7 +107,7 @@ public ObjectContainerBuilder Configure() /// /// An exception was raised while configuring the . See inner exception for details. /// - public ObjectContainerBuilder Configure(Func productionFunction) + public IObjectContainerBuilder Configure(Func productionFunction) where TRequest : class where TProduct : class, TRequest { @@ -125,7 +128,7 @@ public ObjectContainerBuilder Configure(Func produ throw new ArgumentException($"The builder is already configured for the request-product type pair: {requestType.FullName}, {productType.FullName}.", nameof(TProduct)); } - DefinitionConfigurationActions.Add(definitionKey, new Action((definitions) => + DefinitionConfigurationActions.Add(definitionKey, new Action((definitions) => { definitions.Add(); })); @@ -135,7 +138,7 @@ public ObjectContainerBuilder Configure(Func produ return this; } - FunctionConfigurationActions.Add(definitionKey, new Action((functions) => + FunctionConfigurationActions.Add(definitionKey, new Action((functions) => { functions.Add(productionFunction); })); @@ -148,15 +151,23 @@ public ObjectContainerBuilder Configure(Func produ return this; } + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + /// /// Produces the configured instance. /// /// /// An exception was raised during finalization of the builder. /// - protected sealed override IObjectContainer ToResult(ConcurrencyControlToken controlToken) + protected sealed override IObjectContainer ToResult(IConcurrencyControlToken controlToken) { - var factoryConfigurator = new Action((functions) => + var factoryConfigurator = new Action((functions) => { foreach (var action in FunctionConfigurationActions.Values) { @@ -164,7 +175,7 @@ protected sealed override IObjectContainer ToResult(ConcurrencyControlToken cont } }); - var definitionConfigurator = new Action((definitions) => + var definitionConfigurator = new Action((definitions) => { foreach (var action in DefinitionConfigurationActions.Values) { @@ -180,26 +191,19 @@ protected sealed override IObjectContainer ToResult(ConcurrencyControlToken cont /// . /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly IDictionary> DefinitionConfigurationActions = new Dictionary>(); + private readonly IDictionary> DefinitionConfigurationActions = new Dictionary>(); /// /// Represents a collection of definitions that control the behavior of the resulting . /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly ObjectContainerConfigurationDefinitions Definitions = new ObjectContainerConfigurationDefinitions(); + private readonly IObjectContainerConfigurationDefinitions Definitions = new ObjectContainerConfigurationDefinitions(); /// /// Represents a collection of actions that configure the functions that produce new object instances for the resulting /// . /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly IDictionary> FunctionConfigurationActions = new Dictionary>(); - - /// - /// Represents a collection of configured types and functions that produce them using the resulting - /// . - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly ObjectFactoryConfigurationProductionFunctions Functions = new ObjectFactoryConfigurationProductionFunctions(); + private readonly IDictionary> FunctionConfigurationActions = new Dictionary>(); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.ObjectComposition/ObjectContainerConfiguration.cs b/src/RapidField.SolidInstruments.ObjectComposition/ObjectContainerConfiguration.cs index 4d55a6d3..5f2fcc1f 100644 --- a/src/RapidField.SolidInstruments.ObjectComposition/ObjectContainerConfiguration.cs +++ b/src/RapidField.SolidInstruments.ObjectComposition/ObjectContainerConfiguration.cs @@ -23,7 +23,7 @@ public ObjectContainerConfiguration() /// /// Gets a collection of supported request-product type pairs for the associated . /// - public ObjectContainerConfigurationDefinitions Definitions + public IObjectContainerConfigurationDefinitions Definitions { get; } diff --git a/src/RapidField.SolidInstruments.ObjectComposition/ObjectContainerConfigurationDefinitions.cs b/src/RapidField.SolidInstruments.ObjectComposition/ObjectContainerConfigurationDefinitions.cs index 313fe353..ada73fca 100644 --- a/src/RapidField.SolidInstruments.ObjectComposition/ObjectContainerConfigurationDefinitions.cs +++ b/src/RapidField.SolidInstruments.ObjectComposition/ObjectContainerConfigurationDefinitions.cs @@ -12,7 +12,11 @@ namespace RapidField.SolidInstruments.ObjectComposition /// Represents a collection of definitions that control the behavior of an when resolving /// requested objects. /// - public sealed class ObjectContainerConfigurationDefinitions + /// + /// is the default implementation of + /// . + /// + public sealed class ObjectContainerConfigurationDefinitions : IObjectContainerConfigurationDefinitions { /// /// Initializes a new instance of the class. @@ -20,7 +24,7 @@ public sealed class ObjectContainerConfigurationDefinitions [DebuggerHidden] internal ObjectContainerConfigurationDefinitions() { - Registrations = new ConcurrentDictionary(); + Registrations = new ConcurrentDictionary(); } /// @@ -35,7 +39,7 @@ internal ObjectContainerConfigurationDefinitions() /// /// A definition already exists for . /// - public ObjectContainerConfigurationDefinitions Add() + public IObjectContainerConfigurationDefinitions Add() where TRequest : class where TProduct : class, TRequest { @@ -59,13 +63,13 @@ public ObjectContainerConfigurationDefinitions Add() /// /// A definition already exists for . /// - public ObjectContainerConfigurationDefinitions Add() + public IObjectContainerConfigurationDefinitions Add() where TProduct : class => Add(); /// /// Represents a collection of request-product type pairs that constitute the definitions. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - internal readonly ConcurrentDictionary Registrations; + internal readonly ConcurrentDictionary Registrations; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.ObjectComposition/ObjectContainerDefinition.cs b/src/RapidField.SolidInstruments.ObjectComposition/ObjectContainerDefinition.cs index 1cac1b16..625d227a 100644 --- a/src/RapidField.SolidInstruments.ObjectComposition/ObjectContainerDefinition.cs +++ b/src/RapidField.SolidInstruments.ObjectComposition/ObjectContainerDefinition.cs @@ -6,13 +6,17 @@ using RapidField.SolidInstruments.Core.ArgumentValidation; using RapidField.SolidInstruments.Core.Extensions; using System; +using System.Diagnostics; namespace RapidField.SolidInstruments.ObjectComposition { /// /// Represents information about how an resolves requests for objects. /// - internal class ObjectContainerDefinition : IComparable, IEquatable + /// + /// is the default implementation of . + /// + internal class ObjectContainerDefinition : IObjectContainerDefinition { /// /// Initializes a new instance of the class. @@ -30,70 +34,71 @@ internal class ObjectContainerDefinition : IComparable /// is not a subclass or implementation of . /// - public ObjectContainerDefinition(Type requestType, Type productType) + [DebuggerHidden] + internal ObjectContainerDefinition(Type requestType, Type productType) { RequestType = requestType.RejectIf().IsNull(nameof(requestType)).OrIf().IsNotSupportedType(requestType); ProductType = productType.RejectIf().IsNull(nameof(productType)); } /// - /// Determines whether or not two specified instances are not equal. + /// Determines whether or not two specified instances are not equal. /// /// - /// The first instance to compare. + /// The first instance to compare. /// /// - /// The second instance to compare. + /// The second instance to compare. /// /// /// A value indicating whether or not the specified instances are not equal. /// - public static Boolean operator !=(ObjectContainerDefinition a, ObjectContainerDefinition b) => (a == b) == false; + public static Boolean operator !=(ObjectContainerDefinition a, IObjectContainerDefinition b) => (a == b) == false; /// /// Determines whether or not a specified instance is less than another specified /// instance. /// /// - /// The first instance to compare. + /// The first instance to compare. /// /// - /// The second instance to compare. + /// The second instance to compare. /// /// /// if the second object is earlier than the first object, otherwise . /// - public static Boolean operator <(ObjectContainerDefinition a, ObjectContainerDefinition b) => a.CompareTo(b) == -1; + public static Boolean operator <(ObjectContainerDefinition a, IObjectContainerDefinition b) => a.CompareTo(b) == -1; /// - /// Determines whether or not a specified instance is less than or equal to another - /// supplied instance. + /// Determines whether or not a specified instance is less than or equal to + /// another supplied instance. /// /// - /// The first instance to compare. + /// The first instance to compare. /// /// - /// The second instance to compare. + /// The second instance to compare. /// /// /// if the second object is earlier than or equal to the first object, otherwise /// . /// - public static Boolean operator <=(ObjectContainerDefinition a, ObjectContainerDefinition b) => a.CompareTo(b) < 1; + public static Boolean operator <=(ObjectContainerDefinition a, IObjectContainerDefinition b) => a.CompareTo(b) < 1; /// - /// Determines whether or not two specified instances are equal. + /// Determines whether or not two specified instances are equal. /// /// - /// The first instance to compare. + /// The first instance to compare. /// /// - /// The second instance to compare. + /// The second instance to compare. /// /// /// A value indicating whether or not the specified instances are equal. /// - public static Boolean operator ==(ObjectContainerDefinition a, ObjectContainerDefinition b) + public static Boolean operator ==(ObjectContainerDefinition a, IObjectContainerDefinition b) { if ((Object)a is null && (Object)b is null) { @@ -108,48 +113,48 @@ public ObjectContainerDefinition(Type requestType, Type productType) } /// - /// Determines whether or not a specified instance is greater than another + /// Determines whether or not a specified instance is greater than another /// specified instance. /// /// - /// The first instance to compare. + /// The first instance to compare. /// /// - /// The second instance to compare. + /// The second instance to compare. /// /// /// if the second object is later than the first object, otherwise . /// - public static Boolean operator >(ObjectContainerDefinition a, ObjectContainerDefinition b) => a.CompareTo(b) == 1; + public static Boolean operator >(ObjectContainerDefinition a, IObjectContainerDefinition b) => a.CompareTo(b) == 1; /// - /// Determines whether or not a specified instance is greater than or equal to + /// Determines whether or not a specified instance is greater than or equal to /// another supplied instance. /// /// - /// The first instance to compare. + /// The first instance to compare. /// /// - /// The second instance to compare. + /// The second instance to compare. /// /// /// if the second object is later than or equal to the first object, otherwise /// . /// - public static Boolean operator >=(ObjectContainerDefinition a, ObjectContainerDefinition b) => a.CompareTo(b) > -1; + public static Boolean operator >=(ObjectContainerDefinition a, IObjectContainerDefinition b) => a.CompareTo(b) > -1; /// /// Compares the current to the specified object and returns an indication of their /// relative values. /// /// - /// The to compare to this instance. + /// The to compare to this instance. /// /// /// Negative one if this instance is earlier than the specified instance; one if this instance is later than the supplied /// instance; zero if they are equal. /// - public Int32 CompareTo(ObjectContainerDefinition other) + public Int32 CompareTo(IObjectContainerDefinition other) { var thisInstanceHashCode = GetHashCode(); var otherInstancHashCode = other.GetHashCode(); @@ -184,24 +189,24 @@ public override Boolean Equals(Object obj) { return false; } - else if (obj is ObjectContainerDefinition) + else if (obj is IObjectContainerDefinition) { - return Equals((ObjectContainerDefinition)obj); + return Equals((IObjectContainerDefinition)obj); } return false; } /// - /// Determines whether or not two specified instances are equal. + /// Determines whether or not two specified instances are equal. /// /// - /// The to compare to this instance. + /// The to compare to this instance. /// /// /// A value indicating whether or not the specified instances are equal. /// - public Boolean Equals(ObjectContainerDefinition other) + public Boolean Equals(IObjectContainerDefinition other) { if ((Object)other is null) { @@ -251,7 +256,7 @@ public Byte[] ToByteArray() /// /// A string representation of the current . /// - public override String ToString() => $"{{ {nameof(RequestType)}: {RequestType.FullName}, {nameof(ProductType)}: {ProductType.FullName} }}"; + public override String ToString() => $"{{ \"{nameof(RequestType)}\": \"{RequestType.FullName}\", {nameof(ProductType)}\": \"{ProductType.FullName}\" }}"; /// /// Gets the type that is produced as a result of a request for . diff --git a/src/RapidField.SolidInstruments.ObjectComposition/ObjectFactory.cs b/src/RapidField.SolidInstruments.ObjectComposition/ObjectFactory.cs index 0b4f792f..1592fc91 100644 --- a/src/RapidField.SolidInstruments.ObjectComposition/ObjectFactory.cs +++ b/src/RapidField.SolidInstruments.ObjectComposition/ObjectFactory.cs @@ -102,7 +102,7 @@ protected ObjectFactory() protected ObjectFactory(IConfiguration applicationConfiguration) : base(applicationConfiguration) { - LazyProductionFunctions = new Lazy>(DefineProductionFunctions, LazyThreadSafetyMode.ExecutionAndPublication); + LazyProductionFunctions = new Lazy>(DefineProductionFunctions, LazyThreadSafetyMode.ExecutionAndPublication); ProductBaseType = typeof(TProductBase); } @@ -171,7 +171,7 @@ public Object Produce(Type type) /// /// A string representation of the current . /// - public override String ToString() => $"{{ {nameof(ProductBaseType)}: {ProductBaseType.FullName} }}"; + public override String ToString() => $"{{ \"{nameof(ProductBaseType)}\": \"{ProductBaseType.FullName}\" }}"; /// /// Returns a collection of supported product types paired with functions to create them. @@ -180,7 +180,7 @@ public Object Produce(Type type) /// An exception was raised during configuration of the factory. /// [DebuggerHidden] - internal virtual ConcurrentDictionary DefineProductionFunctions() => Configuration.ProductionFunctions.Dictionary; + internal virtual ConcurrentDictionary DefineProductionFunctions() => ((ObjectFactoryConfigurationProductionFunctions)(Configuration.ProductionFunctions)).Dictionary; /// /// Releases all resources consumed by the current . @@ -229,7 +229,7 @@ public IEnumerable SupportedProductTypes /// An exception was raised during configuration of the factory. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - internal ConcurrentDictionary ProductionFunctions => LazyProductionFunctions.Value; + internal ConcurrentDictionary ProductionFunctions => LazyProductionFunctions.Value; /// /// Represents a lazily-initialized collection of type names and functions that produce the associated types. @@ -238,6 +238,6 @@ public IEnumerable SupportedProductTypes /// An exception was raised during configuration of the factory. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly Lazy> LazyProductionFunctions; + private readonly Lazy> LazyProductionFunctions; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.ObjectComposition/ObjectFactoryConfiguration.cs b/src/RapidField.SolidInstruments.ObjectComposition/ObjectFactoryConfiguration.cs index 5ab028e3..b0b59e33 100644 --- a/src/RapidField.SolidInstruments.ObjectComposition/ObjectFactoryConfiguration.cs +++ b/src/RapidField.SolidInstruments.ObjectComposition/ObjectFactoryConfiguration.cs @@ -64,17 +64,17 @@ public ObjectFactoryConfiguration() /// is . /// [DebuggerHidden] - internal ObjectFactoryConfiguration(ObjectFactoryConfigurationProductionFunctions productionFunctions) + internal ObjectFactoryConfiguration(IObjectFactoryConfigurationProductionFunctions productionFunctions) : base() { - ProductionFunctions = productionFunctions.RejectIf().IsNull(nameof(productionFunctions)); + ProductionFunctions = productionFunctions.RejectIf().IsNull(nameof(productionFunctions)).TargetArgument; } /// /// Gets a collection of functions that produce supported types for the associated /// . /// - public ObjectFactoryConfigurationProductionFunctions ProductionFunctions + public IObjectFactoryConfigurationProductionFunctions ProductionFunctions { get; } diff --git a/src/RapidField.SolidInstruments.ObjectComposition/ObjectFactoryConfigurationProductionFunctions.cs b/src/RapidField.SolidInstruments.ObjectComposition/ObjectFactoryConfigurationProductionFunctions.cs index 398906b0..62922508 100644 --- a/src/RapidField.SolidInstruments.ObjectComposition/ObjectFactoryConfigurationProductionFunctions.cs +++ b/src/RapidField.SolidInstruments.ObjectComposition/ObjectFactoryConfigurationProductionFunctions.cs @@ -12,7 +12,11 @@ namespace RapidField.SolidInstruments.ObjectComposition /// /// Represents a collection of configured types and functions that produce them using an . /// - public sealed class ObjectFactoryConfigurationProductionFunctions : ObjectFactoryConfigurationProductionFunctions + /// + /// is the default implementation of + /// . + /// + public sealed class ObjectFactoryConfigurationProductionFunctions : ObjectFactoryConfigurationProductionFunctions, IObjectFactoryConfigurationProductionFunctions { /// /// Initializes a new instance of the class. @@ -31,8 +35,8 @@ internal ObjectFactoryConfigurationProductionFunctions() /// Configured types that are wrapped by the new functions. /// [DebuggerHidden] - internal ObjectFactoryConfigurationProductionFunctions(ObjectFactoryConfigurationProductionFunctions functions) - : base(functions.Dictionary) + internal ObjectFactoryConfigurationProductionFunctions(IObjectFactoryConfigurationProductionFunctions functions) + : base(((ObjectFactoryConfigurationProductionFunctions)functions).Dictionary) { return; } @@ -42,17 +46,21 @@ internal ObjectFactoryConfigurationProductionFunctions(ObjectFactoryConfiguratio /// Represents a collection of configured types and functions that produce them using an /// . /// + /// + /// is the default implementation of + /// . + /// /// /// The base type from which objects produced by the associated factory derive. /// - public class ObjectFactoryConfigurationProductionFunctions + public class ObjectFactoryConfigurationProductionFunctions : IObjectFactoryConfigurationProductionFunctions { /// /// Initializes a new instance of the class. /// [DebuggerHidden] internal ObjectFactoryConfigurationProductionFunctions() - : this(new ConcurrentDictionary()) + : this(new ConcurrentDictionary()) { return; } @@ -67,7 +75,7 @@ internal ObjectFactoryConfigurationProductionFunctions() /// is . /// [DebuggerHidden] - internal ObjectFactoryConfigurationProductionFunctions(ConcurrentDictionary dictionary) + internal ObjectFactoryConfigurationProductionFunctions(ConcurrentDictionary dictionary) { Dictionary = dictionary.RejectIf().IsNull(nameof(dictionary)); } @@ -87,7 +95,7 @@ internal ObjectFactoryConfigurationProductionFunctions(ConcurrentDictionary /// is . /// - public ObjectFactoryConfigurationProductionFunctions Add(Func function) + public IObjectFactoryConfigurationProductionFunctions Add(Func function) where TProduct : class, TProductBase { var productType = typeof(TProduct); @@ -104,6 +112,6 @@ public ObjectFactoryConfigurationProductionFunctions Add /// Represents a collection of functions that produce an output. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - internal readonly ConcurrentDictionary Dictionary; + internal readonly ConcurrentDictionary Dictionary; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.ObjectComposition/ObjectFactoryProductionFunction.cs b/src/RapidField.SolidInstruments.ObjectComposition/ObjectFactoryProductionFunction.cs index cb2fab97..7be9d366 100644 --- a/src/RapidField.SolidInstruments.ObjectComposition/ObjectFactoryProductionFunction.cs +++ b/src/RapidField.SolidInstruments.ObjectComposition/ObjectFactoryProductionFunction.cs @@ -74,7 +74,11 @@ public sealed override Type ProductType /// /// Represents a function that produces an object of a specified type. /// - internal abstract class ObjectFactoryProductionFunction + /// + /// is the default implementation of + /// . + /// + internal abstract class ObjectFactoryProductionFunction : IObjectFactoryProductionFunction { /// /// Initializes a new instance of the class. @@ -103,7 +107,7 @@ protected ObjectFactoryProductionFunction() /// /// A string representation of the current . /// - public override String ToString() => $"{{ {nameof(ProductType)}: {ProductType.FullName} }}"; + public override String ToString() => $"{{ \"{nameof(ProductType)}\": \"{ProductType.FullName}\" }}"; /// /// Gets the type of the object produced by the function. diff --git a/src/RapidField.SolidInstruments.Serialization/DynamicSerializer.cs b/src/RapidField.SolidInstruments.Serialization/DynamicSerializer.cs index 4a121cbe..a0ccb988 100644 --- a/src/RapidField.SolidInstruments.Serialization/DynamicSerializer.cs +++ b/src/RapidField.SolidInstruments.Serialization/DynamicSerializer.cs @@ -90,7 +90,7 @@ public DynamicSerializer(SerializationFormat format) /// /// A string representation of the current . /// - public override String ToString() => $"{{ {nameof(Format)}: {Format}, {nameof(ContractType)}: {ContractType.FullName} }}"; + public override String ToString() => $"{{ \"{nameof(Format)}\": \"{Format}\", {nameof(ContractType)}\": \"{ContractType.FullName}\" }}"; /// /// Converts the specified buffer to its typed equivalent. @@ -384,7 +384,7 @@ protected DynamicSerializer(SerializationFormat format) /// /// A string representation of the current . /// - public override String ToString() => $"{{ {nameof(Format)}: {Format} }}"; + public override String ToString() => $"{{ \"{nameof(Format)}\": \"{Format}\" }}"; /// /// Converts the specified buffer to its typed equivalent. diff --git a/src/RapidField.SolidInstruments.Service/ServiceExecutor.cs b/src/RapidField.SolidInstruments.Service/ServiceExecutor.cs index b904cdc5..f4b9f84e 100644 --- a/src/RapidField.SolidInstruments.Service/ServiceExecutor.cs +++ b/src/RapidField.SolidInstruments.Service/ServiceExecutor.cs @@ -94,7 +94,7 @@ public void Execute() /// A string representation of the current /// . /// - public override String ToString() => $"{{ {nameof(ServiceName)}: \"{ServiceName}\" }}"; + public override String ToString() => $"{{ \"{nameof(ServiceName)}\": \"{ServiceName}\" }}"; /// /// Builds the application configuration for the service. diff --git a/src/RapidField.SolidInstruments.SignalProcessing/Channel.cs b/src/RapidField.SolidInstruments.SignalProcessing/Channel.cs index ff56dae1..aa7e0fff 100644 --- a/src/RapidField.SolidInstruments.SignalProcessing/Channel.cs +++ b/src/RapidField.SolidInstruments.SignalProcessing/Channel.cs @@ -5,7 +5,6 @@ using RapidField.SolidInstruments.Core; using RapidField.SolidInstruments.Core.ArgumentValidation; using RapidField.SolidInstruments.Core.Extensions; -using RapidField.SolidInstruments.TextEncoding.Extensions; using System; using System.Collections.Generic; using System.Diagnostics; @@ -438,7 +437,7 @@ public void Toggle() /// /// A string representation of the current . /// - public override String ToString() => $"{{ {nameof(Identifier)}: {Identifier.ToSerializedString()}, {nameof(Name)}: {Name} }}"; + public override String ToString() => $"{{ \"{nameof(Identifier)}\": \"{Identifier.ToSerializedString()}\", {nameof(Name)}\": \"{Name}\" }}"; /// /// Releases all resources consumed by the current . diff --git a/src/RapidField.SolidInstruments.SignalProcessing/ChannelCollection.cs b/src/RapidField.SolidInstruments.SignalProcessing/ChannelCollection.cs index 186fb6ff..f9a84f4a 100644 --- a/src/RapidField.SolidInstruments.SignalProcessing/ChannelCollection.cs +++ b/src/RapidField.SolidInstruments.SignalProcessing/ChannelCollection.cs @@ -2533,7 +2533,7 @@ public IEnumerator GetEnumerator() /// /// A string representation of the current . /// - public override String ToString() => $"{{ {nameof(Identifier)}: {Identifier.ToSerializedString()}, {nameof(Count)}: {Count} }}"; + public override String ToString() => $"{{ \"{nameof(Identifier)}\": \"{Identifier.ToSerializedString()}\", {nameof(Count)}\": {Count} }}"; /// /// Gets the number of channels in the collection. diff --git a/src/RapidField.SolidInstruments.SignalProcessing/DiscreteUnitOfOutput.cs b/src/RapidField.SolidInstruments.SignalProcessing/DiscreteUnitOfOutput.cs index 58ef4fb9..6115db3c 100644 --- a/src/RapidField.SolidInstruments.SignalProcessing/DiscreteUnitOfOutput.cs +++ b/src/RapidField.SolidInstruments.SignalProcessing/DiscreteUnitOfOutput.cs @@ -42,7 +42,7 @@ public DiscreteUnitOfOutput(T value, Int32 channelReadIndex) /// /// A string representation of the current . /// - public override String ToString() => $"{{ {nameof(ChannelReadIndex)}: {ChannelReadIndex}, {nameof(Value)}: {Value} }}"; + public override String ToString() => $"{{ \"{nameof(ChannelReadIndex)}\": {ChannelReadIndex}, \"{nameof(Value)}\": \"{Value}\" }}"; /// /// Gets the zero-based index for the current within the associated channel's output diff --git a/src/RapidField.SolidInstruments.SignalProcessing/SignalSample.cs b/src/RapidField.SolidInstruments.SignalProcessing/SignalSample.cs index a4da0c19..209954fd 100644 --- a/src/RapidField.SolidInstruments.SignalProcessing/SignalSample.cs +++ b/src/RapidField.SolidInstruments.SignalProcessing/SignalSample.cs @@ -84,7 +84,7 @@ public SignalSample(IDiscreteUnitOfOutput unitOfOutput, IOutputRange lookB /// /// A string representation of the current . /// - public override String ToString() => $"{{ {nameof(UnitOfOutput)}: {UnitOfOutput} }}"; + public override String ToString() => $"{{ \"{nameof(UnitOfOutput)}\": {UnitOfOutput} }}"; /// /// Gets a range of discrete units of output following in the channel's output stream, or an diff --git a/test/RapidField.SolidInstruments.Command.UnitTests/SimulatedCommandHandler.cs b/test/RapidField.SolidInstruments.Command.UnitTests/SimulatedCommandHandler.cs index ab401f21..3ab785e0 100644 --- a/test/RapidField.SolidInstruments.Command.UnitTests/SimulatedCommandHandler.cs +++ b/test/RapidField.SolidInstruments.Command.UnitTests/SimulatedCommandHandler.cs @@ -45,6 +45,6 @@ public SimulatedCommandHandler(ICommandMediator mediator) /// /// A token that represents and manages contextual thread safety. /// - protected sealed override void Process(SimulatedCommand command, ICommandMediator mediator, ConcurrencyControlToken controlToken) => command.IsProcessed = true; + protected sealed override void Process(SimulatedCommand command, ICommandMediator mediator, IConcurrencyControlToken controlToken) => command.IsProcessed = true; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Command.UnitTests/SimulatedCommandWithResultHandler.cs b/test/RapidField.SolidInstruments.Command.UnitTests/SimulatedCommandWithResultHandler.cs index bed5c102..9699625b 100644 --- a/test/RapidField.SolidInstruments.Command.UnitTests/SimulatedCommandWithResultHandler.cs +++ b/test/RapidField.SolidInstruments.Command.UnitTests/SimulatedCommandWithResultHandler.cs @@ -48,7 +48,7 @@ public SimulatedCommandWithResultHandler(ICommandMediator mediator) /// /// The result that is emitted by processing the command. /// - protected sealed override Guid Process(SimulatedCommandWithResult command, ICommandMediator mediator, ConcurrencyControlToken controlToken) + protected sealed override Guid Process(SimulatedCommandWithResult command, ICommandMediator mediator, IConcurrencyControlToken controlToken) { command.IsProcessed = true; return command.Identifier; diff --git a/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/ConcurrencyControlTests.cs b/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/ConcurrencyControlTests.cs index 1aef9824..11269452 100644 --- a/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/ConcurrencyControlTests.cs +++ b/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/ConcurrencyControlTests.cs @@ -152,7 +152,7 @@ internal static void FunctionalLifeSpanTest_ShouldProduceDesiredResults(Concurre { controlToken.IsActive.Should().BeTrue(); operation(); - controlToken.Release(); + ((ConcurrencyControlToken)controlToken).Release(); controlToken.IsActive.Should().BeFalse(); } }); diff --git a/test/RapidField.SolidInstruments.DataAccess.UnitTests/SimulatedRepository.cs b/test/RapidField.SolidInstruments.DataAccess.UnitTests/SimulatedRepository.cs index db9befc6..360bad57 100644 --- a/test/RapidField.SolidInstruments.DataAccess.UnitTests/SimulatedRepository.cs +++ b/test/RapidField.SolidInstruments.DataAccess.UnitTests/SimulatedRepository.cs @@ -44,7 +44,7 @@ public SimulatedRepository(SimulatedDataStore dataStore) /// /// A token that represents and manages contextual thread safety. /// - protected override void Add(TEntity entity, ConcurrencyControlToken controlToken) => DataStore.Add(entity.Identifier, entity.Value); + protected override void Add(TEntity entity, IConcurrencyControlToken controlToken) => DataStore.Add(entity.Identifier, entity.Value); /// /// Adds the specified entities to the current . @@ -55,7 +55,7 @@ public SimulatedRepository(SimulatedDataStore dataStore) /// /// A token that represents and manages contextual thread safety. /// - protected override void AddRange(IEnumerable entities, ConcurrencyControlToken controlToken) + protected override void AddRange(IEnumerable entities, IConcurrencyControlToken controlToken) { foreach (var entity in entities) { @@ -72,7 +72,7 @@ protected override void AddRange(IEnumerable entities, ConcurrencyContr /// /// All entities within the current . /// - protected override IQueryable All(ConcurrencyControlToken controlToken) => DataStore.GetAllEntities(); + protected override IQueryable All(IConcurrencyControlToken controlToken) => DataStore.GetAllEntities(); /// /// Determines whether or not any entities matching the specified predicate exist in the current @@ -88,7 +88,7 @@ protected override void AddRange(IEnumerable entities, ConcurrencyContr /// if any entities matching the specified predicate exist in the current /// , otherwise . /// - protected override Boolean AnyWhere(Expression> predicate, ConcurrencyControlToken controlToken) => CountWhere(predicate, controlToken) > 0; + protected override Boolean AnyWhere(Expression> predicate, IConcurrencyControlToken controlToken) => CountWhere(predicate, controlToken) > 0; /// /// Determines whether or not the specified entity exists in the current @@ -104,7 +104,7 @@ protected override void AddRange(IEnumerable entities, ConcurrencyContr /// if the specified entity exists in the current /// , otherwise . /// - protected override Boolean Contains(TEntity entity, ConcurrencyControlToken controlToken) => DataStore.ContainsKey(entity.Identifier); + protected override Boolean Contains(TEntity entity, IConcurrencyControlToken controlToken) => DataStore.ContainsKey(entity.Identifier); /// /// Returns the number of entities in the current . @@ -115,7 +115,7 @@ protected override void AddRange(IEnumerable entities, ConcurrencyContr /// /// The number of entities in the current . /// - protected override Int64 Count(ConcurrencyControlToken controlToken) => DataStore.Count; + protected override Int64 Count(IConcurrencyControlToken controlToken) => DataStore.Count; /// /// Returns the number of entities matching the specified predicate in the current @@ -131,7 +131,7 @@ protected override void AddRange(IEnumerable entities, ConcurrencyContr /// The number of entities matching the specified predicate in the current /// . /// - protected override Int64 CountWhere(Expression> predicate, ConcurrencyControlToken controlToken) => FindWhere(predicate, controlToken).Count(); + protected override Int64 CountWhere(Expression> predicate, IConcurrencyControlToken controlToken) => FindWhere(predicate, controlToken).Count(); /// /// Releases all resources consumed by the current . @@ -154,7 +154,7 @@ protected override void AddRange(IEnumerable entities, ConcurrencyContr /// /// All entities matching the specified predicate within the current . /// - protected override IQueryable FindWhere(Expression> predicate, ConcurrencyControlToken controlToken) => DataStore.GetEntitiesWhere(predicate); + protected override IQueryable FindWhere(Expression> predicate, IConcurrencyControlToken controlToken) => DataStore.GetEntitiesWhere(predicate); /// /// Removes the specified entity from the current . @@ -165,7 +165,7 @@ protected override void AddRange(IEnumerable entities, ConcurrencyContr /// /// A token that represents and manages contextual thread safety. /// - protected override void Remove(TEntity entity, ConcurrencyControlToken controlToken) => DataStore.Remove(entity.Identifier); + protected override void Remove(TEntity entity, IConcurrencyControlToken controlToken) => DataStore.Remove(entity.Identifier); /// /// Removes the specified entities from the current . @@ -176,7 +176,7 @@ protected override void AddRange(IEnumerable entities, ConcurrencyContr /// /// A token that represents and manages contextual thread safety. /// - protected override void RemoveRange(IEnumerable entities, ConcurrencyControlToken controlToken) + protected override void RemoveRange(IEnumerable entities, IConcurrencyControlToken controlToken) { foreach (var entity in entities) { @@ -193,7 +193,7 @@ protected override void RemoveRange(IEnumerable entities, ConcurrencyCo /// /// A token that represents and manages contextual thread safety. /// - protected override void Update(TEntity entity, ConcurrencyControlToken controlToken) + protected override void Update(TEntity entity, IConcurrencyControlToken controlToken) { Remove(entity, controlToken); Add(entity, controlToken); @@ -208,7 +208,7 @@ protected override void Update(TEntity entity, ConcurrencyControlToken controlTo /// /// A token that represents and manages contextual thread safety. /// - protected override void UpdateRange(IEnumerable entities, ConcurrencyControlToken controlToken) + protected override void UpdateRange(IEnumerable entities, IConcurrencyControlToken controlToken) { RemoveRange(entities, controlToken); AddRange(entities, controlToken); diff --git a/test/RapidField.SolidInstruments.DataAccess.UnitTests/SimulatedTransaction.cs b/test/RapidField.SolidInstruments.DataAccess.UnitTests/SimulatedTransaction.cs index 89578c19..9352e28b 100644 --- a/test/RapidField.SolidInstruments.DataAccess.UnitTests/SimulatedTransaction.cs +++ b/test/RapidField.SolidInstruments.DataAccess.UnitTests/SimulatedTransaction.cs @@ -28,7 +28,7 @@ protected SimulatedTransaction() /// /// A token that represents and manages contextual thread safety. /// - protected override void Begin(ConcurrencyControlToken controlToken) + protected override void Begin(IConcurrencyControlToken controlToken) { return; } @@ -42,7 +42,7 @@ protected override void Begin(ConcurrencyControlToken controlToken) /// /// A task representing the asynchronous operation. /// - protected override Task BeginAsync(ConcurrencyControlToken controlToken) => Task.CompletedTask; + protected override Task BeginAsync(IConcurrencyControlToken controlToken) => Task.CompletedTask; /// /// Commits all changes made within the scope of the current . @@ -50,7 +50,7 @@ protected override void Begin(ConcurrencyControlToken controlToken) /// /// A token that represents and manages contextual thread safety. /// - protected override void Commit(ConcurrencyControlToken controlToken) + protected override void Commit(IConcurrencyControlToken controlToken) { return; } @@ -64,7 +64,7 @@ protected override void Commit(ConcurrencyControlToken controlToken) /// /// A task representing the asynchronous operation. /// - protected override Task CommitAsync(ConcurrencyControlToken controlToken) => Task.CompletedTask; + protected override Task CommitAsync(IConcurrencyControlToken controlToken) => Task.CompletedTask; /// /// Releases all resources consumed by the current . @@ -80,7 +80,7 @@ protected override void Commit(ConcurrencyControlToken controlToken) /// /// A token that represents and manages contextual thread safety. /// - protected override void Reject(ConcurrencyControlToken controlToken) + protected override void Reject(IConcurrencyControlToken controlToken) { return; } @@ -94,6 +94,6 @@ protected override void Reject(ConcurrencyControlToken controlToken) /// /// A token that represents and manages contextual thread safety. /// - protected override Task RejectAsync(ConcurrencyControlToken controlToken) => Task.CompletedTask; + protected override Task RejectAsync(IConcurrencyControlToken controlToken) => Task.CompletedTask; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.UnitTests/SimulatedObject.cs b/test/RapidField.SolidInstruments.Messaging.UnitTests/SimulatedObject.cs index e3ea7f17..1a4e50e8 100644 --- a/test/RapidField.SolidInstruments.Messaging.UnitTests/SimulatedObject.cs +++ b/test/RapidField.SolidInstruments.Messaging.UnitTests/SimulatedObject.cs @@ -7,7 +7,6 @@ using RapidField.SolidInstruments.TextEncoding; using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Diagnostics; using System.Linq; using System.Runtime.Serialization; @@ -26,8 +25,8 @@ internal sealed class SimulatedObject : IEquatable /// public SimulatedObject() { - DecimalValues = new Collection(); - NestedObjects = new Collection(); + DecimalValues = new List(); + NestedObjects = new List(); } /// @@ -291,10 +290,10 @@ public DateTime DateTimeValue /// Gets or sets a collection of values. /// [DataMember] - public Collection DecimalValues + public ICollection DecimalValues { get; - private set; + set; } /// @@ -311,7 +310,7 @@ public EnhancedReadabilityGuid EnhancedReadabilityGuidValue /// Gets or sets a collection of nested . /// [DataMember] - public Collection NestedObjects + public ICollection NestedObjects { get; set; diff --git a/test/RapidField.SolidInstruments.ObjectComposition.UnitTests/ObjectContainerTests.cs b/test/RapidField.SolidInstruments.ObjectComposition.UnitTests/ObjectContainerTests.cs index b465075a..9279a532 100644 --- a/test/RapidField.SolidInstruments.ObjectComposition.UnitTests/ObjectContainerTests.cs +++ b/test/RapidField.SolidInstruments.ObjectComposition.UnitTests/ObjectContainerTests.cs @@ -3,7 +3,6 @@ // ================================================================================================================================= using FluentAssertions; -using Microsoft.Extensions.Configuration; using Microsoft.VisualStudio.TestTools.UnitTesting; using RapidField.SolidInstruments.Core; using RapidField.SolidInstruments.Core.Concurrency; @@ -44,7 +43,7 @@ public void Get_ShouldRaiseArgumentException_ForUnsupportedType_UsingFactoryConf { // Arrange. var objectFactory = new SimulatedInstrumentFactory(); - var definitionConfigurator = new Action((definitions) => + var definitionConfigurator = new Action((definitions) => { definitions .Add() @@ -68,11 +67,11 @@ public void Get_ShouldRaiseArgumentException_ForUnsupportedType_UsingFactoryConf public void Get_ShouldRaiseArgumentException_ForUnsupportedType_UsingManualConfiguration() { // Arrange. - var factoryConfigurator = new Action((functions) => + var factoryConfigurator = new Action((functions) => { functions.Add(() => new SimulatedInstrument(ConcurrencyControlMode.SingleThreadLock)); }); - var definitionConfigurator = new Action((definitions) => + var definitionConfigurator = new Action((definitions) => { definitions .Add() @@ -120,7 +119,7 @@ public void Get_ShouldReturnNewObjectOfSpecifiedType_ForSupportedType_UsingFacto { // Arrange. var objectFactory = new SimulatedInstrumentFactory(); - var definitionConfigurator = new Action((definitions) => + var definitionConfigurator = new Action((definitions) => { definitions .Add() @@ -142,11 +141,11 @@ public void Get_ShouldReturnNewObjectOfSpecifiedType_ForSupportedType_UsingFacto public void Get_ShouldReturnNewObjectOfSpecifiedType_ForSupportedType_UsingManualConfiguration() { // Arrange. - var factoryConfigurator = new Action((functions) => + var factoryConfigurator = new Action((functions) => { functions.Add(() => new SimulatedInstrument(ConcurrencyControlMode.SingleThreadLock)); }); - var definitionConfigurator = new Action((definitions) => + var definitionConfigurator = new Action((definitions) => { definitions .Add() @@ -194,7 +193,7 @@ public void Get_ShouldReturnSameObjectInstance_ForRepeatedCallsForEquivalentRequ { // Arrange. var objectFactory = new SimulatedInstrumentFactory(); - var definitionConfigurator = new Action((definitions) => + var definitionConfigurator = new Action((definitions) => { definitions .Add() @@ -218,11 +217,11 @@ public void Get_ShouldReturnSameObjectInstance_ForRepeatedCallsForEquivalentRequ public void Get_ShouldReturnSameObjectInstance_ForRepeatedCallsForEquivalentRequestType_UsingManualConfiguration() { // Arrange. - var factoryConfigurator = new Action((functions) => + var factoryConfigurator = new Action((functions) => { functions.Add(() => new SimulatedInstrument(ConcurrencyControlMode.SingleThreadLock)); }); - var definitionConfigurator = new Action((definitions) => + var definitionConfigurator = new Action((definitions) => { definitions .Add() @@ -272,7 +271,7 @@ public void Get_ShouldReturnSameObjectInstance_ForRepeatedCallsForSameRequestTyp { // Arrange. var objectFactory = new SimulatedInstrumentFactory(); - var definitionConfigurator = new Action((definitions) => + var definitionConfigurator = new Action((definitions) => { definitions .Add() @@ -296,11 +295,11 @@ public void Get_ShouldReturnSameObjectInstance_ForRepeatedCallsForSameRequestTyp public void Get_ShouldReturnSameObjectInstance_ForRepeatedCallsForSameRequestType_UsingManualConfiguration() { // Arrange. - var factoryConfigurator = new Action((functions) => + var factoryConfigurator = new Action((functions) => { functions.Add(() => new SimulatedInstrument(ConcurrencyControlMode.SingleThreadLock)); }); - var definitionConfigurator = new Action((definitions) => + var definitionConfigurator = new Action((definitions) => { definitions .Add() @@ -350,7 +349,7 @@ public void GetNew_ShouldRaiseArgumentException_ForUnsupportedType_UsingFactoryC { // Arrange. var objectFactory = new SimulatedInstrumentFactory(); - var definitionConfigurator = new Action((definitions) => + var definitionConfigurator = new Action((definitions) => { definitions .Add() @@ -374,11 +373,11 @@ public void GetNew_ShouldRaiseArgumentException_ForUnsupportedType_UsingFactoryC public void GetNew_ShouldRaiseArgumentException_ForUnsupportedType_UsingManualConfiguration() { // Arrange. - var factoryConfigurator = new Action((functions) => + var factoryConfigurator = new Action((functions) => { functions.Add(() => new SimulatedInstrument(ConcurrencyControlMode.SingleThreadLock)); }); - var definitionConfigurator = new Action((definitions) => + var definitionConfigurator = new Action((definitions) => { definitions .Add() @@ -428,7 +427,7 @@ public void GetNew_ShouldReturnDifferentObjectInstances_ForRepeatedCallsForSameR { // Arrange. var objectFactory = new SimulatedInstrumentFactory(); - var definitionConfigurator = new Action((definitions) => + var definitionConfigurator = new Action((definitions) => { definitions .Add() @@ -452,11 +451,11 @@ public void GetNew_ShouldReturnDifferentObjectInstances_ForRepeatedCallsForSameR public void GetNew_ShouldReturnDifferentObjectInstances_ForRepeatedCallsForSameRequestType_UsingManualConfiguration() { // Arrange. - var factoryConfigurator = new Action((functions) => + var factoryConfigurator = new Action((functions) => { functions.Add(() => new SimulatedInstrument(ConcurrencyControlMode.SingleThreadLock)); }); - var definitionConfigurator = new Action((definitions) => + var definitionConfigurator = new Action((definitions) => { definitions .Add() @@ -504,7 +503,7 @@ public void GetNew_ShouldReturnNewObjectOfSpecifiedType_ForSupportedType_UsingFa { // Arrange. var objectFactory = new SimulatedInstrumentFactory(); - var definitionConfigurator = new Action((definitions) => + var definitionConfigurator = new Action((definitions) => { definitions .Add() @@ -526,11 +525,11 @@ public void GetNew_ShouldReturnNewObjectOfSpecifiedType_ForSupportedType_UsingFa public void GetNew_ShouldReturnNewObjectOfSpecifiedType_ForSupportedType_UsingManualConfiguration() { // Arrange. - var factoryConfigurator = new Action((functions) => + var factoryConfigurator = new Action((functions) => { functions.Add(() => new SimulatedInstrument(ConcurrencyControlMode.SingleThreadLock)); }); - var definitionConfigurator = new Action((definitions) => + var definitionConfigurator = new Action((definitions) => { definitions .Add() @@ -576,7 +575,7 @@ public void InstanceTypes_ShouldReturnConfiguredTypes_UsingFactoryConfiguration( { // Arrange. var objectFactory = new SimulatedInstrumentFactory(); - var definitionConfigurator = new Action((definitions) => + var definitionConfigurator = new Action((definitions) => { definitions .Add() @@ -598,11 +597,11 @@ public void InstanceTypes_ShouldReturnConfiguredTypes_UsingFactoryConfiguration( public void InstanceTypes_ShouldReturnConfiguredTypes_UsingManualConfiguration() { // Arrange. - var factoryConfigurator = new Action((functions) => + var factoryConfigurator = new Action((functions) => { functions.Add(() => new SimulatedInstrument(ConcurrencyControlMode.SingleThreadLock)); }); - var definitionConfigurator = new Action((definitions) => + var definitionConfigurator = new Action((definitions) => { definitions .Add() diff --git a/test/RapidField.SolidInstruments.Serialization.UnitTests/SimulatedObject.cs b/test/RapidField.SolidInstruments.Serialization.UnitTests/SimulatedObject.cs index b6090219..449bdde0 100644 --- a/test/RapidField.SolidInstruments.Serialization.UnitTests/SimulatedObject.cs +++ b/test/RapidField.SolidInstruments.Serialization.UnitTests/SimulatedObject.cs @@ -7,7 +7,6 @@ using RapidField.SolidInstruments.TextEncoding; using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Diagnostics; using System.Linq; using System.Runtime.Serialization; @@ -26,8 +25,8 @@ internal sealed class SimulatedObject : IEquatable /// public SimulatedObject() { - DecimalValues = new Collection(); - NestedObjects = new Collection(); + DecimalValues = new List(); + NestedObjects = new List(); } /// @@ -291,10 +290,10 @@ public DateTime DateTimeValue /// Gets or sets a collection of values. /// [DataMember] - public Collection DecimalValues + public ICollection DecimalValues { get; - private set; + set; } /// @@ -311,7 +310,7 @@ public EnhancedReadabilityGuid EnhancedReadabilityGuidValue /// Gets or sets a collection of nested . /// [DataMember] - public Collection NestedObjects + public ICollection NestedObjects { get; set; From 117c04af36de437e417a4f913693b64508adfdaf Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Thu, 28 May 2020 13:32:46 -0500 Subject: [PATCH 31/55] Numerous minor enhancements. --- .../ConfigurableInstrument.cs | 2 +- .../Extensions/BitArrayExtensions.cs | 4 +- .../Extensions/ByteCollectionExtensions.cs | 5 +- .../Extensions/DecimalExtensions.cs | 4 +- .../Extensions/ObjectExtensions.cs | 26 +- .../IInstrumentConfiguration.cs | 49 +++ .../InstrumentConfiguration.cs | 9 +- .../TimeStamp.cs | 10 +- .../CryptographicTransform.cs | 4 +- .../RandomNumberGeneratorExtensions.cs | 22 +- .../HardenedRandomNumberGenerator.cs | 10 +- .../Hashing/HashTree.cs | 6 +- .../Hashing/HashingProcessor.cs | 64 ++-- .../Hashing/IHashingProcessor.cs | 20 +- .../ISecureMemory.cs | 19 ++ .../Secrets/CascadingSymmetricKeySecret.cs | 4 +- .../Secrets/IReadOnlySecret.cs | 51 +++ .../Secrets/ISecret.cs | 22 ++ .../Secrets/Secret.cs | 116 +++++-- .../Secrets/SymmetricKeySecret.cs | 4 +- .../SecureMemory.cs | 133 +++++++- .../Symmetric/CascadingSymmetricKey.cs | 130 ++++---- .../Symmetric/ICascadingSymmetricKey.cs | 6 +- .../Symmetric/ISymmetricKey.cs | 14 +- .../Symmetric/ISymmetricProcessor.cs | 7 + .../Symmetric/SymmetricBinaryProcessor.cs | 31 -- .../Symmetric/SymmetricKey.cs | 292 +++++++++--------- .../Symmetric/SymmetricKeyCipher.cs | 10 +- .../Symmetric/SymmetricProcessor.cs | 109 +++++-- .../Symmetric/SymmetricStringProcessor.cs | 5 + .../IServiceCollectionExtensions.cs | 8 +- .../TransportPrimitives/MessageTransport.cs | 14 +- .../ObjectContainer.cs | 8 +- .../Base64Serializer.cs | 14 +- .../DynamicSerializer.cs | 82 ++--- .../ISerializer.cs | 12 +- .../PassThroughSerializer.cs | 14 +- .../TextEncodingSerializer.cs | 26 +- .../Base32Encoding.cs | 8 +- .../Hashing/HashingProcessorTests.cs | 8 +- .../Secrets/SecretTests.cs | 6 +- .../SecureMemoryTests.cs | 65 ++++ .../Symmetric/CascadingSymmetricKeyTests.cs | 27 +- .../SymmetricBinaryProcessorTests.cs | 86 ------ .../Symmetric/SymmetricKeyTests.cs | 92 +++--- .../Symmetric/SymmetricProcessorTests.cs | 77 ++++- .../Base64SerializerTests.cs | 6 +- .../PassThroughSerializerTests.cs | 16 +- .../UnicodeSerializerTests.cs | 6 +- 49 files changed, 1112 insertions(+), 651 deletions(-) create mode 100644 src/RapidField.SolidInstruments.Core/IInstrumentConfiguration.cs delete mode 100644 src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricBinaryProcessor.cs delete mode 100644 test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/SymmetricBinaryProcessorTests.cs diff --git a/src/RapidField.SolidInstruments.Core/ConfigurableInstrument.cs b/src/RapidField.SolidInstruments.Core/ConfigurableInstrument.cs index 07b6bd2b..8efcbab1 100644 --- a/src/RapidField.SolidInstruments.Core/ConfigurableInstrument.cs +++ b/src/RapidField.SolidInstruments.Core/ConfigurableInstrument.cs @@ -19,7 +19,7 @@ namespace RapidField.SolidInstruments.Core /// The type of the configuration information for the instrument. /// public abstract class ConfigurableInstrument : Instrument - where TConfiguration : InstrumentConfiguration, new() + where TConfiguration : IInstrumentConfiguration, new() { /// /// Initializes a new instance of the class. diff --git a/src/RapidField.SolidInstruments.Core/Extensions/BitArrayExtensions.cs b/src/RapidField.SolidInstruments.Core/Extensions/BitArrayExtensions.cs index 266a86a4..f53f7b9c 100644 --- a/src/RapidField.SolidInstruments.Core/Extensions/BitArrayExtensions.cs +++ b/src/RapidField.SolidInstruments.Core/Extensions/BitArrayExtensions.cs @@ -105,13 +105,13 @@ public static BitArray ReverseOrder(this BitArray target) } /// - /// Converts the binary data underlying the current to its equivalent string representation. + /// Converts the bits comprising the current to its equivalent string representation. /// /// /// The current . /// /// - /// A string representation of the binary data underlying the current . + /// A string representation of the bits comprising the current . /// public static String ToBinaryString(this BitArray target) { diff --git a/src/RapidField.SolidInstruments.Core/Extensions/ByteCollectionExtensions.cs b/src/RapidField.SolidInstruments.Core/Extensions/ByteCollectionExtensions.cs index 906b14e2..06872b30 100644 --- a/src/RapidField.SolidInstruments.Core/Extensions/ByteCollectionExtensions.cs +++ b/src/RapidField.SolidInstruments.Core/Extensions/ByteCollectionExtensions.cs @@ -114,6 +114,9 @@ public static Byte[] Decompress(this Byte[] target) /// /// The number of bits to shift by. /// + /// + /// The resulting shifted bit field. + /// /// /// The value of is equal to and/or the value of /// is less than zero. @@ -298,7 +301,7 @@ private static Byte[] ComputeTwoHundredFiftySixBitHashBuffer(this IEnumerable - /// Represents the binary 128-bit hash for an empty collection. + /// Represents the 128-bit hash for an empty collection. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] private static readonly Byte[] EmptyCollectionTwoHundredFiftySixBitHash = diff --git a/src/RapidField.SolidInstruments.Core/Extensions/DecimalExtensions.cs b/src/RapidField.SolidInstruments.Core/Extensions/DecimalExtensions.cs index e3ca9e53..8b23196a 100644 --- a/src/RapidField.SolidInstruments.Core/Extensions/DecimalExtensions.cs +++ b/src/RapidField.SolidInstruments.Core/Extensions/DecimalExtensions.cs @@ -75,9 +75,9 @@ public static Byte[] ToByteArray(this Decimal target) var bytes = new List(); { - var binaryComponents = Decimal.GetBits(target); + var components = Decimal.GetBits(target); - foreach (var integer in binaryComponents) + foreach (var integer in components) { bytes.AddRange(BitConverter.GetBytes(integer)); } diff --git a/src/RapidField.SolidInstruments.Core/Extensions/ObjectExtensions.cs b/src/RapidField.SolidInstruments.Core/Extensions/ObjectExtensions.cs index 769bae2b..a38f812d 100644 --- a/src/RapidField.SolidInstruments.Core/Extensions/ObjectExtensions.cs +++ b/src/RapidField.SolidInstruments.Core/Extensions/ObjectExtensions.cs @@ -37,8 +37,8 @@ internal static Int32 GetImpliedHashCode(this Object target) try { var serializer = new DataContractJsonSerializer(objectType, GetImpliedHashCodeSerializerSettings); - var buffer = Serialize(serializer, target); - hashCode ^= buffer.ComputeThirtyTwoBitHash(); + var serializedObject = Serialize(serializer, target); + hashCode ^= serializedObject.ComputeThirtyTwoBitHash(); } catch (Exception exception) { @@ -69,8 +69,8 @@ internal static Object GetSerializedClone(this Object target) try { var serializer = new DataContractJsonSerializer(objectType, GetSerializedCloneSerializerSettings); - var buffer = Serialize(serializer, target); - return Deserialize(serializer, buffer); + var serializedObject = Serialize(serializer, target); + return Deserialize(serializer, serializedObject); } catch (Exception exception) { @@ -79,28 +79,28 @@ internal static Object GetSerializedClone(this Object target) } /// - /// Converts the specified buffer to its typed equivalent. + /// Converts the specified serialized object to its typed equivalent. /// /// - /// A serializer that deserializes . + /// A serializer that deserializes . /// - /// + /// /// A serialized object. /// /// /// The deserialized object. /// /// - /// is invalid or an error occurred during deserialization. + /// is invalid or an error occurred during deserialization. /// [DebuggerHidden] - private static Object Deserialize(DataContractJsonSerializer serializer, Byte[] buffer) + private static Object Deserialize(DataContractJsonSerializer serializer, Byte[] serializedObject) { - using (var stream = new MemoryStream(buffer)) + using (var stream = new MemoryStream(serializedObject)) { try { - return serializer.ReadObject(stream) ?? throw new SerializationException("The specified buffer is invalid."); + return serializer.ReadObject(stream) ?? throw new SerializationException("The specified serialized object is invalid."); } catch (SerializationException) { @@ -114,7 +114,7 @@ private static Object Deserialize(DataContractJsonSerializer serializer, Byte[] } /// - /// Converts the specified object to a buffer. + /// Converts the specified object to a serialized byte array. /// /// /// A serializer that serializes . @@ -123,7 +123,7 @@ private static Object Deserialize(DataContractJsonSerializer serializer, Byte[] /// An object to be serialized. /// /// - /// The serialized buffer. + /// The serialized byte array. /// /// /// is invalid or an error occurred during serialization. diff --git a/src/RapidField.SolidInstruments.Core/IInstrumentConfiguration.cs b/src/RapidField.SolidInstruments.Core/IInstrumentConfiguration.cs new file mode 100644 index 00000000..de052820 --- /dev/null +++ b/src/RapidField.SolidInstruments.Core/IInstrumentConfiguration.cs @@ -0,0 +1,49 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Microsoft.Extensions.Configuration; +using RapidField.SolidInstruments.Core.Concurrency; +using System; +using System.Diagnostics; +using System.Threading; + +namespace RapidField.SolidInstruments.Core +{ + /// + /// Represents configuration information for a instance. + /// + public interface IInstrumentConfiguration + { + /// + /// Gets or sets configuration information for the application. + /// + public IConfiguration Application + { + get; + set; + } + + /// + /// Gets or sets the concurrency control mode that is used to manage state for the associated + /// . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + public ConcurrencyControlMode StateControlMode + { + get; + set; + } + + /// + /// Gets or sets the maximum length of time that the instrument's state control may block a thread before raising an + /// exception, or if indefinite thread blocking is permitted. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + public TimeSpan StateControlTimeoutThreshold + { + get; + set; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Core/InstrumentConfiguration.cs b/src/RapidField.SolidInstruments.Core/InstrumentConfiguration.cs index eee531aa..ccb19452 100644 --- a/src/RapidField.SolidInstruments.Core/InstrumentConfiguration.cs +++ b/src/RapidField.SolidInstruments.Core/InstrumentConfiguration.cs @@ -13,7 +13,10 @@ namespace RapidField.SolidInstruments.Core /// /// Represents configuration information for a instance. /// - public abstract class InstrumentConfiguration + /// + /// is the default implementation of . + /// + public abstract class InstrumentConfiguration : IInstrumentConfiguration { /// /// Initializes a new instance of the class. @@ -26,12 +29,12 @@ protected InstrumentConfiguration() } /// - /// Gets configuration information for the application. + /// Gets or sets configuration information for the application. /// public IConfiguration Application { get; - internal set; + set; } /// diff --git a/src/RapidField.SolidInstruments.Core/TimeStamp.cs b/src/RapidField.SolidInstruments.Core/TimeStamp.cs index a50139c6..a0d172e3 100644 --- a/src/RapidField.SolidInstruments.Core/TimeStamp.cs +++ b/src/RapidField.SolidInstruments.Core/TimeStamp.cs @@ -63,6 +63,12 @@ internal void UseKind(DateTimeKind kind) /// protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + /// + /// Finalizes static members of the class. + /// + [DebuggerHidden] + private static void FinalizeStaticMembers() => Instance.Dispose(); + /// /// Produces the current date and time. /// @@ -83,7 +89,7 @@ internal void UseKind(DateTimeKind kind) public static DateTime Current => Instance.Produce(); /// - /// Represents a singleton instance of . + /// Represents a singleton instance of the class. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal static readonly TimeStamp Instance = new TimeStamp(); @@ -104,7 +110,7 @@ internal void UseKind(DateTimeKind kind) /// Represents a finalizer for static members of the class. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private static readonly StaticMemberFinalizer Finalizer = new StaticMemberFinalizer(Instance.Dispose); + private static readonly StaticMemberFinalizer StaticMemberFinalizer = new StaticMemberFinalizer(FinalizeStaticMembers); /// /// Represents the that is used by the current when producing the diff --git a/src/RapidField.SolidInstruments.Cryptography/CryptographicTransform.cs b/src/RapidField.SolidInstruments.Cryptography/CryptographicTransform.cs index 4d5479d2..c10edc7c 100644 --- a/src/RapidField.SolidInstruments.Cryptography/CryptographicTransform.cs +++ b/src/RapidField.SolidInstruments.Cryptography/CryptographicTransform.cs @@ -289,10 +289,10 @@ private static Byte[] ConditionallyRemovePadding(Byte[] block, Int32 blockSizeIn } /// - /// Transform the supplied binary range. + /// Transform the supplied byte range. /// /// - /// The binary data to be transformed. + /// The data to be transformed. /// /// /// The cryptographic transform that performs the operation. diff --git a/src/RapidField.SolidInstruments.Cryptography/Extensions/RandomNumberGeneratorExtensions.cs b/src/RapidField.SolidInstruments.Cryptography/Extensions/RandomNumberGeneratorExtensions.cs index 81ff5d60..60b7edd4 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Extensions/RandomNumberGeneratorExtensions.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Extensions/RandomNumberGeneratorExtensions.cs @@ -1667,13 +1667,13 @@ private static void GenerateChar(RandomNumberGenerator target, Boolean permitNon { var characterEncoding = permitNonLatin ? Encoding.Unicode : Encoding.ASCII; var characterByteLength = permitNonLatin ? UnicodeCharByteLength : AsciiCharByteLength; - var buffer = new Byte[characterByteLength * CharacterBufferLengthMultiplier]; - var singlePassIterationCount = (buffer.Length - characterByteLength); - target.GetBytes(buffer); + var characterBytes = new Byte[characterByteLength * CharacterBytesLengthMultiplier]; + var singlePassIterationCount = (characterBytes.Length - characterByteLength); + target.GetBytes(characterBytes); - while (PermuteCharacterGeneration(buffer, singlePassIterationCount, characterByteLength, characterEncoding, permitNonLatin, permitLowercaseAlphabetic, permitUppercaseAlphabetic, permitNumeric, permitSymbolic, permitWhiteSpace, permitControl, out randomValue) == false) + while (PermuteCharacterGeneration(characterBytes, singlePassIterationCount, characterByteLength, characterEncoding, permitNonLatin, permitLowercaseAlphabetic, permitUppercaseAlphabetic, permitNumeric, permitSymbolic, permitWhiteSpace, permitControl, out randomValue) == false) { - target.GetBytes(buffer); + target.GetBytes(characterBytes); } } @@ -2215,8 +2215,8 @@ private static void GenerateUInt64(RandomNumberGenerator target, UInt64 floor, U /// /// Executes a single pass of the character generation operation. /// - /// - /// A buffer containing the randomly-generated character bytes. + /// + /// A byte array containing the randomly-generated character bytes. /// /// /// The number of iterations to execute. @@ -2255,19 +2255,19 @@ private static void GenerateUInt64(RandomNumberGenerator target, UInt64 floor, U /// if the random character was generated successfully, otherwise . /// [DebuggerHidden] - private static Boolean PermuteCharacterGeneration(Byte[] buffer, Int32 iterationCount, Int32 characterByteLength, Encoding characterEncoding, Boolean permitNonLatin, Boolean permitLowercaseAlphabetic, Boolean permitUppercaseAlphabetic, Boolean permitNumeric, Boolean permitSymbolic, Boolean permitWhiteSpace, Boolean permitControl, out Char randomValue) + private static Boolean PermuteCharacterGeneration(Byte[] characterBytes, Int32 iterationCount, Int32 characterByteLength, Encoding characterEncoding, Boolean permitNonLatin, Boolean permitLowercaseAlphabetic, Boolean permitUppercaseAlphabetic, Boolean permitNumeric, Boolean permitSymbolic, Boolean permitWhiteSpace, Boolean permitControl, out Char randomValue) { var operationIsSuccessful = false; for (var i = 0; i < iterationCount; i++) { - if (permitNonLatin == false && buffer[i] > 0x7f) + if (permitNonLatin == false && characterBytes[i] > 0x7f) { // 0x7f is the last valid ASCII character. continue; } - randomValue = characterEncoding.GetChars(buffer, i, characterByteLength).First(); + randomValue = characterEncoding.GetChars(characterBytes, i, characterByteLength).First(); operationIsSuccessful = operationIsSuccessful || (permitLowercaseAlphabetic && randomValue.IsLowercaseAlphabetic()); operationIsSuccessful = operationIsSuccessful || (permitUppercaseAlphabetic && randomValue.IsUppercaseAlphabetic()); operationIsSuccessful = operationIsSuccessful || (permitNumeric && randomValue.IsNumeric()); @@ -2338,7 +2338,7 @@ private static void PushBytes(ConcurrentStack stack, IEnumerable ran /// . /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const Int32 CharacterBufferLengthMultiplier = 2; + private const Int32 CharacterBytesLengthMultiplier = 2; /// /// Represents the byte length of a . diff --git a/src/RapidField.SolidInstruments.Cryptography/HardenedRandomNumberGenerator.cs b/src/RapidField.SolidInstruments.Cryptography/HardenedRandomNumberGenerator.cs index c5b22038..956419ea 100644 --- a/src/RapidField.SolidInstruments.Cryptography/HardenedRandomNumberGenerator.cs +++ b/src/RapidField.SolidInstruments.Cryptography/HardenedRandomNumberGenerator.cs @@ -103,6 +103,12 @@ protected override void Dispose(Boolean disposing) } } + /// + /// Finalizes static members of the class. + /// + [DebuggerHidden] + private static void FinalizeStaticMembers() => Instance.Dispose(); + /// /// Initializes a random number generator that is used to provide source random material for the current /// . @@ -172,7 +178,7 @@ private void PermuteBuffer() private RandomNumberGenerator SourceRandomnessProvider => LazySourceRandomnessProvider.Value; /// - /// Represents a singleton instance of . + /// Represents a singleton instance of the class. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal static readonly HardenedRandomNumberGenerator Instance = new HardenedRandomNumberGenerator(); @@ -211,7 +217,7 @@ private void PermuteBuffer() /// Represents a finalizer for static members of the class. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private static readonly StaticMemberFinalizer Finalizer = new StaticMemberFinalizer(Instance.Dispose); + private static readonly StaticMemberFinalizer StaticMemberFinalizer = new StaticMemberFinalizer(FinalizeStaticMembers); /// /// Represents a synchronized stack of processed (encrypted) bytes. diff --git a/src/RapidField.SolidInstruments.Cryptography/Hashing/HashTree.cs b/src/RapidField.SolidInstruments.Cryptography/Hashing/HashTree.cs index cedcb22f..a30174d9 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Hashing/HashTree.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Hashing/HashTree.cs @@ -174,8 +174,8 @@ public void AddBlockRange(IEnumerable blocks) /// /// Calculates a hash value for the specified plaintext data block object. /// - /// - /// The plaintext binary array hash. + /// + /// The plaintext byte array hash. /// /// /// The resulting hash value. @@ -184,7 +184,7 @@ public void AddBlockRange(IEnumerable blocks) /// An exception was raised during hashing or serialization. /// [DebuggerHidden] - private Byte[] CalculateHash(Byte[] binaryArray) => HashingProcessor.CalculateHash(binaryArray, Algorithm); + private Byte[] CalculateHash(Byte[] plaintext) => HashingProcessor.CalculateHash(plaintext, Algorithm); /// /// Calculates a hash value for the specified hash value pair. diff --git a/src/RapidField.SolidInstruments.Cryptography/Hashing/HashingProcessor.cs b/src/RapidField.SolidInstruments.Cryptography/Hashing/HashingProcessor.cs index 49e0eaee..8d80de53 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Hashing/HashingProcessor.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Hashing/HashingProcessor.cs @@ -14,7 +14,7 @@ namespace RapidField.SolidInstruments.Cryptography.Hashing { /// - /// Provides facilities for hashing typed objects and binary arrays. + /// Provides facilities for hashing typed objects and byte arrays. /// /// /// is the default implementation of . @@ -31,15 +31,15 @@ public class HashingProcessor : IHashingProcessor /// /// A random number generator that is used to generate salt values. /// - /// - /// A binary serializer that is used to transform plaintext. + /// + /// A serializer that is used to transform plaintext. /// /// - /// is -or- is + /// is -or- is /// . /// - public HashingProcessor(RandomNumberGenerator randomnessProvider, ISerializer binarySerializer) - : this(randomnessProvider, binarySerializer, DefaultSaltLengthInBytes) + public HashingProcessor(RandomNumberGenerator randomnessProvider, ISerializer serializer) + : this(randomnessProvider, serializer, DefaultSaltLengthInBytes) { return; } @@ -50,31 +50,31 @@ public HashingProcessor(RandomNumberGenerator randomnessProvider, ISerializer /// /// A random number generator that is used to generate salt values. /// - /// - /// A binary serializer that is used to transform plaintext. + /// + /// A serializer that is used to transform plaintext. /// /// /// The salt length, in bytes, to use when calculating and evaluating hash values. The default value is 8. /// /// - /// is -or- is + /// is -or- is /// . /// /// /// is less than one. /// - public HashingProcessor(RandomNumberGenerator randomnessProvider, ISerializer binarySerializer, Int32 saltLengthInBytes) + public HashingProcessor(RandomNumberGenerator randomnessProvider, ISerializer serializer, Int32 saltLengthInBytes) { - BinarySerializer = binarySerializer.RejectIf().IsNull(nameof(binarySerializer)).TargetArgument; + Serializer = serializer.RejectIf().IsNull(nameof(serializer)).TargetArgument; RandomnessProvider = randomnessProvider.RejectIf().IsNull(nameof(randomnessProvider)); SaltLengthInBytes = saltLengthInBytes.RejectIf().IsLessThan(1, nameof(saltLengthInBytes)); } /// - /// Calculates a hash value for the specified plaintext binary array. + /// Calculates a hash value for the specified plaintext byte array. /// - /// - /// The plaintext binary array to hash. + /// + /// The plaintext byte array to hash. /// /// /// The algorithm specification used to transform the plaintext. @@ -85,13 +85,13 @@ public HashingProcessor(RandomNumberGenerator randomnessProvider, ISerializer /// /// An exception was raised during hashing or serialization. /// - public Byte[] CalculateHash(Byte[] plaintextBinaryArray, HashingAlgorithmSpecification algorithm) => CalculateHash(plaintextBinaryArray, algorithm, null); + public Byte[] CalculateHash(Byte[] plaintext, HashingAlgorithmSpecification algorithm) => CalculateHash(plaintext, algorithm, null); /// - /// Calculates a hash value for the specified plaintext binary array. + /// Calculates a hash value for the specified plaintext byte array. /// - /// - /// The plaintext binary array to hash. + /// + /// The plaintext byte array to hash. /// /// /// The algorithm specification used to transform the plaintext. @@ -106,33 +106,33 @@ public HashingProcessor(RandomNumberGenerator randomnessProvider, ISerializer /// /// An exception was raised during hashing or serialization. /// - public Byte[] CalculateHash(Byte[] plaintextBinaryArray, HashingAlgorithmSpecification algorithm, Byte[] salt) + public Byte[] CalculateHash(Byte[] plaintext, HashingAlgorithmSpecification algorithm, Byte[] salt) { try { var applySalt = (salt is null == false); var saltLengthInBytes = (applySalt ? salt.Length : 0); - var plaintextLengthInBytes = plaintextBinaryArray.Length; - var plaintextBufferLengthInBytes = (plaintextLengthInBytes + saltLengthInBytes); + var plaintextLengthInBytes = plaintext.Length; + var saltedPlaintextLengthInBytes = (plaintextLengthInBytes + saltLengthInBytes); var digestLengthInBytes = (algorithm.ToDigestBitLength() / 8); var hashLengthInBytes = (digestLengthInBytes + saltLengthInBytes); var hashValue = new Byte[hashLengthInBytes]; - Byte[] plaintextBuffer; + Byte[] processedPlaintextBytes; if (applySalt) { - plaintextBuffer = new Byte[plaintextBufferLengthInBytes]; - Array.Copy(plaintextBinaryArray, plaintextBuffer, plaintextLengthInBytes); - Array.Copy(salt, 0, plaintextBuffer, plaintextLengthInBytes, saltLengthInBytes); + processedPlaintextBytes = new Byte[saltedPlaintextLengthInBytes]; + Array.Copy(plaintext, processedPlaintextBytes, plaintextLengthInBytes); + Array.Copy(salt, 0, processedPlaintextBytes, plaintextLengthInBytes, saltLengthInBytes); } else { - plaintextBuffer = plaintextBinaryArray; + processedPlaintextBytes = plaintext; } using (var hashAlgorithm = algorithm.ToHashAlgorithm()) { - Array.Copy(hashAlgorithm.ComputeHash(plaintextBuffer), hashValue, digestLengthInBytes); + Array.Copy(hashAlgorithm.ComputeHash(processedPlaintextBytes), hashValue, digestLengthInBytes); } if (applySalt) @@ -279,7 +279,7 @@ public Boolean EvaluateHash(Byte[] hash, T plaintextObject, HashingAlgorithmSpec /// The resulting hash value. /// [DebuggerHidden] - private Byte[] CalculateHash(T plaintextObject, HashingAlgorithmSpecification algorithm, Byte[] salt) => CalculateHash(BinarySerializer.Serialize(plaintextObject), algorithm, salt); + private Byte[] CalculateHash(T plaintextObject, HashingAlgorithmSpecification algorithm, Byte[] salt) => CalculateHash(Serializer.Serialize(plaintextObject), algorithm, salt); /// /// Gets the salt length, in bytes, to use when calculating and evaluating hash values. @@ -296,15 +296,15 @@ public Int32 SaltLengthInBytes private const Int32 DefaultSaltLengthInBytes = 8; /// - /// Represents a binary serializer that is used to transform plaintext. + /// Represents a random number generator that is used to generate salt values. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly ISerializer BinarySerializer; + private readonly RandomNumberGenerator RandomnessProvider; /// - /// Represents a random number generator that is used to generate salt values. + /// Represents a serializer that is used to transform plaintext. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly RandomNumberGenerator RandomnessProvider; + private readonly ISerializer Serializer; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Hashing/IHashingProcessor.cs b/src/RapidField.SolidInstruments.Cryptography/Hashing/IHashingProcessor.cs index c65b3b75..ad19c2ad 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Hashing/IHashingProcessor.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Hashing/IHashingProcessor.cs @@ -8,7 +8,7 @@ namespace RapidField.SolidInstruments.Cryptography.Hashing { /// - /// Provides facilities for hashing typed objects and binary arrays. + /// Provides facilities for hashing typed objects and byte arrays. /// /// /// The type of the object that can be hashed. @@ -61,15 +61,15 @@ public interface IHashingProcessor : IHashingProcessor } /// - /// Provides facilities for hashing typed objects and binary arrays. + /// Provides facilities for hashing typed objects and byte arrays. /// public interface IHashingProcessor { /// - /// Calculates a hash value for the specified plaintext binary array. + /// Calculates a hash value for the specified plaintext byte array. /// - /// - /// The plaintext binary array to hash. + /// + /// The plaintext byte array to hash. /// /// /// The algorithm specification used to transform the plaintext. @@ -80,13 +80,13 @@ public interface IHashingProcessor /// /// An exception was raised during hashing or serialization. /// - public Byte[] CalculateHash(Byte[] plaintextBinaryArray, HashingAlgorithmSpecification algorithm); + public Byte[] CalculateHash(Byte[] plaintext, HashingAlgorithmSpecification algorithm); /// - /// Calculates a hash value for the specified plaintext binary array. + /// Calculates a hash value for the specified plaintext byte array. /// - /// - /// The plaintext binary array to hash. + /// + /// The plaintext byte array to hash. /// /// /// The algorithm specification used to transform the plaintext. @@ -101,6 +101,6 @@ public interface IHashingProcessor /// /// An exception was raised during hashing or serialization. /// - public Byte[] CalculateHash(Byte[] plaintextBinaryArray, HashingAlgorithmSpecification algorithm, Byte[] salt); + public Byte[] CalculateHash(Byte[] plaintext, HashingAlgorithmSpecification algorithm, Byte[] salt); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/ISecureMemory.cs b/src/RapidField.SolidInstruments.Cryptography/ISecureMemory.cs index 0db248ef..2c9f1376 100644 --- a/src/RapidField.SolidInstruments.Cryptography/ISecureMemory.cs +++ b/src/RapidField.SolidInstruments.Cryptography/ISecureMemory.cs @@ -4,6 +4,7 @@ using RapidField.SolidInstruments.Collections; using System; +using System.Threading.Tasks; namespace RapidField.SolidInstruments.Cryptography { @@ -27,6 +28,24 @@ public interface ISecureMemory : IAsyncDisposable, IDisposable /// public void Access(Action action); + /// + /// Asynchronously decrypts the bit field, performs the specified operation against the pinned plaintext and encrypts the + /// bit field as a thread-safe, atomic operation. + /// + /// + /// The operation to perform. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public Task AccessAsync(Action action); + /// /// Gets the length of the bit field, in bytes. /// diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/CascadingSymmetricKeySecret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/CascadingSymmetricKeySecret.cs index 01e7c77a..ddeb6375 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/CascadingSymmetricKeySecret.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/CascadingSymmetricKeySecret.cs @@ -84,7 +84,7 @@ protected sealed override CascadingSymmetricKey ConvertBytesToValue(IReadOnlyPin bytes.ReadOnlySpan.CopyTo(memory); }); - result = CascadingSymmetricKey.FromBuffer(secureMemory); + result = CascadingSymmetricKey.FromSecureMemory(secureMemory); } return result; @@ -107,7 +107,7 @@ protected sealed override IReadOnlyPinnedMemory ConvertValueToBytes(Cascad { var result = (ReadOnlyPinnedMemory)null; - using (var secureMemory = value.ToBuffer()) + using (var secureMemory = value.ToSecureMemory()) { secureMemory.Access(memory => { diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/IReadOnlySecret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/IReadOnlySecret.cs index 0cc9bdab..2cd09df4 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/IReadOnlySecret.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/IReadOnlySecret.cs @@ -4,6 +4,7 @@ using RapidField.SolidInstruments.Collections; using System; +using System.Threading.Tasks; namespace RapidField.SolidInstruments.Cryptography.Secrets { @@ -36,6 +37,31 @@ public interface IReadOnlySecret : IReadOnlySecret /// raised an exception. /// public void Read(Action readAction); + + /// + /// Asynchronously decrypts the secret value, pins a copy of it in memory and performs the specified read operation against + /// it as a thread-safe, atomic operation. + /// + /// + /// The read operation to perform. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// The secret does not have a value. This exception can be avoided by evaluating + /// before invoking the method. + /// + /// + /// The object is disposed. + /// + /// + /// raised an exception. + /// + public Task ReadAsync(Action readAction); } /// @@ -65,6 +91,31 @@ public interface IReadOnlySecret : IAsyncDisposable, IDisposable /// public void Read(Action> readAction); + /// + /// Asynchronously decrypts the secret value, pins it in memory and performs the specified read operation against the + /// resulting bytes as a thread-safe, atomic operation. + /// + /// + /// The read operation to perform. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// The secret does not have a value. This exception can be avoided by evaluating before invoking + /// the method. + /// + /// + /// The object is disposed. + /// + /// + /// raised an exception. + /// + public Task ReadAsync(Action> readAction); + /// /// Gets a value indicating whether or not the current has a value. /// diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecret.cs index f6eda5ce..cdd6e2ba 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecret.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecret.cs @@ -3,6 +3,7 @@ // ================================================================================================================================= using System; +using System.Threading.Tasks; namespace RapidField.SolidInstruments.Cryptography.Secrets { @@ -30,6 +31,27 @@ public interface ISecret : IReadOnlySecret, ISecret /// raised an exception or returned an invalid . /// public void Write(Func writeFunction); + + /// + /// Asynchronously performs the specified write operation and encrypts the resulting value as a thread-safe, atomic + /// operation. + /// + /// + /// The write operation to perform. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// raised an exception or returned an invalid . + /// + public Task WriteAsync(Func writeFunction); } /// diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/Secret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/Secret.cs index 4b7ab108..ca2ae958 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/Secret.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/Secret.cs @@ -8,6 +8,7 @@ using RapidField.SolidInstruments.Core.Concurrency; using System; using System.Diagnostics; +using System.Threading.Tasks; namespace RapidField.SolidInstruments.Cryptography.Secrets { @@ -126,7 +127,7 @@ protected Secret(String name) { HasValue = false; Name = name.RejectIf().IsNullOrEmpty(nameof(name)); - SecureValueBuffer = null; + SecureValueMemory = null; } /// @@ -139,9 +140,9 @@ public override Int32 GetHashCode() { var hashCode = (Name?.GetHashCode() ?? 0) ^ (ValueType?.FullName.GetHashCode() ?? 0); - if (HasValue && SecureValueBuffer is null == false) + if (HasValue && SecureValueMemory is null == false) { - hashCode ^= SecureValueBuffer.GetHashCode(); + hashCode ^= SecureValueMemory.GetHashCode(); } return hashCode; @@ -205,6 +206,56 @@ public void Read(Action readAction) } } + /// + /// Asynchronously decrypts the secret value, pins it in memory and performs the specified read operation against the + /// resulting bytes as a thread-safe, atomic operation. + /// + /// + /// The read operation to perform. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// The secret does not have a value. This exception can be avoided by evaluating before invoking + /// the method. + /// + /// + /// The object is disposed. + /// + /// + /// raised an exception. + /// + public Task ReadAsync(Action> readAction) => Task.Factory.StartNew(() => Read(readAction)); + + /// + /// Asynchronously decrypts the secret value, pins a copy of it in memory and performs the specified read operation against + /// it as a thread-safe, atomic operation. + /// + /// + /// The read operation to perform. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// The secret does not have a value. This exception can be avoided by evaluating + /// before invoking the method. + /// + /// + /// The object is disposed. + /// + /// + /// raised an exception. + /// + public Task ReadAsync(Action readAction) => Task.Factory.StartNew(() => Read(readAction)); + /// /// Converts the value of the current to its equivalent string representation. /// @@ -237,6 +288,27 @@ public void Write(Func writeFunction) } } + /// + /// Asynchronously performs the specified write operation and encrypts the resulting value as a thread-safe, atomic + /// operation. + /// + /// + /// The write operation to perform. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// raised an exception or returned an invalid . + /// + public Task WriteAsync(Func writeFunction) => Task.Factory.StartNew(() => Write(writeFunction)); + /// /// Creates a using the provided bytes. /// @@ -278,7 +350,7 @@ protected override void Dispose(Boolean disposing) { if (disposing) { - SecureValueBuffer?.Dispose(); + SecureValueMemory?.Dispose(); } } finally @@ -313,19 +385,19 @@ private void Read(Action> readAction, IConcurrencyCo try { - if (SecureValueBuffer is null) + if (SecureValueMemory is null) { using (var memory = new ReadOnlyPinnedMemory(0)) { - // Because secure buffers cannot have length zero, this is here to handle cases in which write operations - // produce empty buffers. + // Because secure memory cannot have length zero, this is here to handle cases in which write operations + // produce empty memory. readAction(memory); } return; } - SecureValueBuffer.Access((buffer) => + SecureValueMemory.Access(buffer => { readAction(buffer); }); @@ -365,7 +437,7 @@ private void Read(Action readAction, IConcurrencyControlToken controlTok { try { - Read((memory) => + Read(memory => { readAction(ConvertBytesToValue(memory, controlToken)); }, controlToken); @@ -411,30 +483,30 @@ private void Write(Func writeFunction, IConcurrencyControlToken controlT throw new SecretAccessException("The specified write function produced a null secret value."); } - using (var valueBuffer = ConvertValueToBytes(value, controlToken)) + using (var valueMemory = ConvertValueToBytes(value, controlToken)) { - if (valueBuffer.IsEmpty) + if (valueMemory.IsEmpty) { - // Secure buffers cannot be empty. - SecureValueBuffer?.Dispose(); - SecureValueBuffer = null; + // Secure memory cannot be empty. + SecureValueMemory?.Dispose(); + SecureValueMemory = null; HasValue = true; return; } - if (SecureValueBuffer is null) + if (SecureValueMemory is null) { - SecureValueBuffer = new SecureMemory(valueBuffer.LengthInBytes); + SecureValueMemory = new SecureMemory(valueMemory.LengthInBytes); } - else if (SecureValueBuffer.LengthInBytes != valueBuffer.LengthInBytes) + else if (SecureValueMemory.LengthInBytes != valueMemory.LengthInBytes) { - SecureValueBuffer.Dispose(); - SecureValueBuffer = new SecureMemory(valueBuffer.LengthInBytes); + SecureValueMemory.Dispose(); + SecureValueMemory = new SecureMemory(valueMemory.LengthInBytes); } - SecureValueBuffer.Access(secureBuffer => + SecureValueMemory.Access(memory => { - valueBuffer.ReadOnlySpan.CopyTo(secureBuffer.Span); + valueMemory.ReadOnlySpan.CopyTo(memory.Span); }); HasValue = true; @@ -480,6 +552,6 @@ public String Name /// Represents the encrypted field in which the secure value is stored. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private ISecureMemory SecureValueBuffer; + private ISecureMemory SecureValueMemory; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/SymmetricKeySecret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/SymmetricKeySecret.cs index 14ad1490..96f08d46 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/SymmetricKeySecret.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/SymmetricKeySecret.cs @@ -84,7 +84,7 @@ protected sealed override SymmetricKey ConvertBytesToValue(IReadOnlyPinnedMemory bytes.ReadOnlySpan.CopyTo(memory); }); - result = SymmetricKey.FromBuffer(secureMemory); + result = SymmetricKey.FromSecureMemory(secureMemory); } return result; @@ -107,7 +107,7 @@ protected sealed override IReadOnlyPinnedMemory ConvertValueToBytes(Symmet { var result = (ReadOnlyPinnedMemory)null; - using (var secureMemory = value.ToBuffer()) + using (var secureMemory = value.ToSecureMemory()) { secureMemory.Access(memory => { diff --git a/src/RapidField.SolidInstruments.Cryptography/SecureMemory.cs b/src/RapidField.SolidInstruments.Cryptography/SecureMemory.cs index 8e5b0e43..d601edd9 100644 --- a/src/RapidField.SolidInstruments.Cryptography/SecureMemory.cs +++ b/src/RapidField.SolidInstruments.Cryptography/SecureMemory.cs @@ -7,11 +7,14 @@ using RapidField.SolidInstruments.Core.ArgumentValidation; using RapidField.SolidInstruments.Core.Concurrency; using RapidField.SolidInstruments.Core.Extensions; +using RapidField.SolidInstruments.Cryptography.Extensions; using RapidField.SolidInstruments.Cryptography.Symmetric; using RapidField.SolidInstruments.Cryptography.Symmetric.Aes; using System; using System.Diagnostics; +using System.Linq; using System.Security.Cryptography; +using System.Threading.Tasks; namespace RapidField.SolidInstruments.Cryptography { @@ -34,8 +37,10 @@ public SecureMemory(Int32 lengthInBytes) { Cipher = new Aes128CbcCipher(RandomnessProvider); LengthInBytes = lengthInBytes.RejectIf().IsLessThanOrEqualTo(0, nameof(lengthInBytes)); - PrivateKey = new PinnedMemory(Cipher.KeySizeInBytes, true); - RandomnessProvider.GetBytes(PrivateKey); + PrivateKeySource = new PinnedMemory(PrivateKeySourceLengthInBytes, true); + PrivateKeySourceBitShiftDirection = RandomnessProvider.GetBoolean() ? BitShiftDirection.Left : BitShiftDirection.Right; + PrivateKeySourceBitShiftCount = BitConverter.GetBytes(RandomnessProvider.GetUInt16()).Max(); + RandomnessProvider.GetBytes(PrivateKeySource); using (var initializationVector = new PinnedMemory(Cipher.BlockSizeInBytes, true)) { @@ -43,7 +48,10 @@ public SecureMemory(Int32 lengthInBytes) using (var plaintext = new PinnedMemory(lengthInBytes)) { - Ciphertext = Cipher.Encrypt(plaintext, PrivateKey, initializationVector); + using (var privateKey = DerivePrivateKey(PrivateKeySource, PrivateKeySourceBitShiftDirection, PrivateKeySourceBitShiftCount, Cipher.KeySizeInBytes)) + { + Ciphertext = Cipher.Encrypt(plaintext, privateKey, initializationVector); + } } } } @@ -108,6 +116,24 @@ public void Access(Action action) } } + /// + /// Asynchronously decrypts the bit field, performs the specified operation against the pinned plaintext and encrypts the + /// bit field as a thread-safe, atomic operation. + /// + /// + /// The operation to perform. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public Task AccessAsync(Action action) => Task.Factory.StartNew(() => Access(action)); + /// /// Returns the hash code for this instance. /// @@ -116,6 +142,35 @@ public void Access(Action action) /// public override Int32 GetHashCode() => Ciphertext.ComputeThirtyTwoBitHash() ^ 0x3a566a5c; + /// + /// Regenerates and replaces the source bytes for the private key that is used to secure the current + /// . + /// + /// + /// The object is disposed. + /// + public void RegeneratePrivateKey() + { + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + + using (var plaintext = new PinnedMemory(LengthInBytes, true)) + { + DecryptField(plaintext); + + try + { + RandomnessProvider.GetBytes(PrivateKeySource); + } + finally + { + EncryptField(plaintext); + } + } + } + } + /// /// Converts the value of the current to its equivalent string representation. /// @@ -124,6 +179,15 @@ public void Access(Action action) /// public override String ToString() => $"{{ \"{nameof(LengthInBytes)}\": {LengthInBytes} }}"; + /// + /// Derives a private key from . + /// + /// + /// The resulting private key. + /// + [DebuggerHidden] + internal PinnedMemory DerivePrivateKey() => DerivePrivateKey(PrivateKeySource, PrivateKeySourceBitShiftDirection, PrivateKeySourceBitShiftCount, Cipher.BlockSizeInBytes); + /// /// Releases all resources consumed by the current . /// @@ -138,7 +202,7 @@ protected override void Dispose(Boolean disposing) { Cipher.Dispose(); Ciphertext.Dispose(); - PrivateKey.Dispose(); + PrivateKeySource.Dispose(); } } finally @@ -147,6 +211,31 @@ protected override void Dispose(Boolean disposing) } } + /// + /// Derives a private key from the specified source bytes. + /// + /// + /// This doesn't ensure security of the private key. It exists to complicate the engineering challenge for would-be + /// attackers and to increase the computational expense of an attack to improve the chance that it will be evident to users. + /// + /// + /// A field of random bits from which the private key is derived. + /// + /// + /// The circular bit shift direction to use when deriving the key. + /// + /// + /// The circular bit shift count to use when deriving the key. + /// + /// + /// The length of the derived key, in bytes. + /// + /// + /// The resulting private key. + /// + [DebuggerHidden] + private static PinnedMemory DerivePrivateKey(Byte[] privateKeySource, BitShiftDirection bitShiftDirection, Int32 bitShiftCount, Int32 keyLengthInBytes) => new PinnedMemory(new Span(privateKeySource.PerformCircularBitShift(bitShiftDirection, bitShiftCount)).Slice(0, keyLengthInBytes).ToArray(), true); + /// /// Decrypts the current bit field and writes the result to the specified bit field. /// @@ -156,9 +245,12 @@ protected override void Dispose(Boolean disposing) [DebuggerHidden] private void DecryptField(PinnedMemory plaintext) { - using (var memory = Cipher.Decrypt(Ciphertext, PrivateKey)) + using (var privateKey = DerivePrivateKey()) { - memory.Span.CopyTo(plaintext); + using (var buffer = Cipher.Decrypt(Ciphertext, privateKey)) + { + buffer.Span.CopyTo(plaintext); + } } } @@ -175,9 +267,12 @@ private void EncryptField(PinnedMemory plaintext) { RandomnessProvider.GetBytes(initializationVector); - using (var ciphertext = Cipher.Encrypt(plaintext, PrivateKey, initializationVector)) + using (var privateKey = DerivePrivateKey()) { - ciphertext.Span.CopyTo(Ciphertext); + using (var buffer = Cipher.Encrypt(plaintext, privateKey, initializationVector)) + { + buffer.Span.CopyTo(Ciphertext); + } } } } @@ -196,6 +291,12 @@ public Int32 LengthInBytes get; } + /// + /// Gets the length, in bytes, of . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private Int32 PrivateKeySourceLengthInBytes => Cipher.KeySizeInBytes + 16; + /// /// Represents a cipher that is used to encrypt and decrypt the bit field. /// @@ -209,9 +310,21 @@ public Int32 LengthInBytes private readonly PinnedMemory Ciphertext; /// - /// Represents a private key that is used to encrypt and decrypt the bit field. + /// Represents a field of random bits from which a private key is derived to encrypt and decrypt the secure bit field. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly PinnedMemory PrivateKeySource; + + /// + /// Represents the circular bit shift count to use when deriving a private key from . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly Byte PrivateKeySourceBitShiftCount; + + /// + /// Represents the circular bit shift direction to use when deriving a private key from . /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly PinnedMemory PrivateKey; + private readonly BitShiftDirection PrivateKeySourceBitShiftDirection; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/CascadingSymmetricKey.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/CascadingSymmetricKey.cs index 48fa8f7f..a0959863 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Symmetric/CascadingSymmetricKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Symmetric/CascadingSymmetricKey.cs @@ -63,59 +63,6 @@ private CascadingSymmetricKey(ISymmetricKey[] keys) Keys = keys; } - /// - /// Creates a new instance of a using the specified buffer. - /// - /// - /// A binary representation of a . - /// - /// - /// A new instance of a . - /// - /// - /// is invalid. - /// - /// - /// is . - /// - public static CascadingSymmetricKey FromBuffer(ISecureMemory buffer) - { - buffer.RejectIf().IsNull(nameof(buffer)).OrIf(argument => argument.LengthInBytes != SerializedLength, nameof(buffer), "The specified buffer is invalid."); - - try - { - var keys = (ISymmetricKey[])null; - - buffer.Access(memory => - { - // Interrogate the final 16 bits to determine the depth. - var keyLength = SymmetricKey.SerializedLength; - var depth = BitConverter.ToUInt16(memory, (SerializedLength - sizeof(UInt16))); - keys = new ISymmetricKey[depth]; - - for (var i = 0; i < depth; i++) - { - using (var secureBuffer = new SecureMemory(keyLength)) - { - secureBuffer.Access(key => - { - // Copy out the key buffers. - Array.Copy(memory, (keyLength * i), key, 0, keyLength); - }); - - keys[i] = SymmetricKey.FromBuffer(secureBuffer); - } - } - }); - - return new CascadingSymmetricKey(keys); - } - catch - { - throw new ArgumentException("The specified buffer is invalid.", nameof(buffer)); - } - } - /// /// Derives a new from the specified password. /// @@ -246,6 +193,59 @@ public static CascadingSymmetricKey FromBuffer(ISecureMemory buffer) /// public static CascadingSymmetricKey FromPassword(String password, SymmetricKeyDerivationMode derivationMode, SymmetricAlgorithmSpecification firstLayerAlgorithm, SymmetricAlgorithmSpecification secondLayerAlgorithm, SymmetricAlgorithmSpecification thirdLayerAlgorithm, SymmetricAlgorithmSpecification fourthLayerAlgorithm) => FromPassword(password, derivationMode, new SymmetricAlgorithmSpecification[] { firstLayerAlgorithm, secondLayerAlgorithm, thirdLayerAlgorithm, fourthLayerAlgorithm }); + /// + /// Creates a new instance of a using the specified secure bit field. + /// + /// + /// A secure bit field containing a . + /// + /// + /// A new instance of a . + /// + /// + /// is invalid. + /// + /// + /// is . + /// + public static CascadingSymmetricKey FromSecureMemory(ISecureMemory secureMemory) + { + secureMemory.RejectIf().IsNull(nameof(secureMemory)).OrIf(argument => argument.LengthInBytes != SerializedLength, nameof(secureMemory), "The specified memory field is invalid."); + + try + { + var keys = (ISymmetricKey[])null; + + secureMemory.Access(memory => + { + // Interrogate the final 16 bits to determine the depth. + var keyLength = SymmetricKey.SerializedLength; + var depth = BitConverter.ToUInt16(memory, (SerializedLength - sizeof(UInt16))); + keys = new ISymmetricKey[depth]; + + for (var i = 0; i < depth; i++) + { + using (var secureMemory = new SecureMemory(keyLength)) + { + secureMemory.Access(key => + { + // Copy out the key buffers. + Array.Copy(memory, (keyLength * i), key, 0, keyLength); + }); + + keys[i] = SymmetricKey.FromSecureMemory(secureMemory); + } + } + }); + + return new CascadingSymmetricKey(keys); + } + catch + { + throw new ArgumentException("The specified memory field is invalid.", nameof(secureMemory)); + } + } + /// /// Generates a new . /// @@ -334,20 +334,20 @@ public static CascadingSymmetricKey FromBuffer(ISecureMemory buffer) public static CascadingSymmetricKey New(SymmetricKeyDerivationMode derivationMode, SymmetricAlgorithmSpecification firstLayerAlgorithm, SymmetricAlgorithmSpecification secondLayerAlgorithm, SymmetricAlgorithmSpecification thirdLayerAlgorithm, SymmetricAlgorithmSpecification fourthLayerAlgorithm) => new CascadingSymmetricKey(derivationMode, firstLayerAlgorithm, secondLayerAlgorithm, thirdLayerAlgorithm, fourthLayerAlgorithm); /// - /// Converts the value of the current to its equivalent binary representation. + /// Converts the value of the current to a secure bit field. /// /// - /// A binary representation of the current . + /// A secure bit field containing a representation of the current . /// - public ISecureMemory ToBuffer() + public ISecureMemory ToSecureMemory() { - var result = new SecureMemory(SerializedLength); + var secureMemory = new SecureMemory(SerializedLength); try { using (var controlToken = StateControl.Enter()) { - result.Access(pinnedResultBuffer => + secureMemory.Access(pinnedMemory => { var keyLength = SymmetricKey.SerializedLength; @@ -355,12 +355,12 @@ public ISecureMemory ToBuffer() { if (i < Depth) { - using (var keyBuffer = Keys.ElementAt(i).ToBuffer()) + using (var keyMemory = Keys.ElementAt(i).ToSecureMemory()) { - keyBuffer.Access(key => + keyMemory.Access(key => { // Copy the key buffers out to the result buffer. - Array.Copy(key, 0, pinnedResultBuffer, (keyLength * i), keyLength); + Array.Copy(key, 0, pinnedMemory, (keyLength * i), keyLength); }); } @@ -370,19 +370,19 @@ public ISecureMemory ToBuffer() // Fill the unused segments with random bytes. var randomBytes = new Byte[keyLength]; HardenedRandomNumberGenerator.Instance.GetBytes(randomBytes); - Array.Copy(randomBytes, 0, pinnedResultBuffer, (keyLength * i), keyLength); + Array.Copy(randomBytes, 0, pinnedMemory, (keyLength * i), keyLength); } // Append the depth as a 16-bit integer. - Buffer.BlockCopy(BitConverter.GetBytes(Convert.ToUInt16(Depth)), 0, pinnedResultBuffer, (SerializedLength - sizeof(UInt16)), sizeof(UInt16)); + Buffer.BlockCopy(BitConverter.GetBytes(Convert.ToUInt16(Depth)), 0, pinnedMemory, (SerializedLength - sizeof(UInt16)), sizeof(UInt16)); }); - return result; + return secureMemory; } } catch { - result.Dispose(); + secureMemory.Dispose(); throw new SecurityException("Key serialization failed."); } } diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/ICascadingSymmetricKey.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/ICascadingSymmetricKey.cs index 7d0ff14e..494ea294 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Symmetric/ICascadingSymmetricKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Symmetric/ICascadingSymmetricKey.cs @@ -14,12 +14,12 @@ namespace RapidField.SolidInstruments.Cryptography.Symmetric public interface ICascadingSymmetricKey : IAsyncDisposable, IDisposable { /// - /// Converts the value of the current to its equivalent binary representation. + /// Converts the value of the current to a secure bit field. /// /// - /// A binary representation of the current . + /// A secure bit field containing a representation of the current . /// - public ISecureMemory ToBuffer(); + public ISecureMemory ToSecureMemory(); /// /// Gets the number of layers of encryption that a resulting transform will apply. diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/ISymmetricKey.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/ISymmetricKey.cs index 48ac3164..61795e30 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Symmetric/ISymmetricKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Symmetric/ISymmetricKey.cs @@ -13,21 +13,21 @@ namespace RapidField.SolidInstruments.Cryptography.Symmetric public interface ISymmetricKey : IAsyncDisposable, IDisposable { /// - /// Converts the value of the current to its equivalent binary representation. + /// Converts the current to symmetric-key plaintext with correct bit-length for the encryption + /// mode specified by . /// /// - /// A binary representation of the current . + /// The derived key. /// - public ISecureMemory ToBuffer(); + public ISecureMemory ToDerivedKeyBytes(); /// - /// Converts the current to symmetric-key plaintext with correct bit-length for the encryption - /// mode specified by . + /// Converts the value of the current to a secure bit field. /// /// - /// The derived key. + /// A secure bit field containing a representation of the current . /// - public ISecureMemory ToDerivedKeyBytes(); + public ISecureMemory ToSecureMemory(); /// /// Gets the symmetric-key algorithm for which a key is derived. diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/ISymmetricProcessor.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/ISymmetricProcessor.cs index 029827cc..0d335a3d 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Symmetric/ISymmetricProcessor.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Symmetric/ISymmetricProcessor.cs @@ -7,6 +7,13 @@ namespace RapidField.SolidInstruments.Cryptography.Symmetric { + /// + /// Provides facilities for encrypting and decrypting byte arrays. + /// + public interface ISymmetricProcessor : ISymmetricProcessor + { + } + /// /// Provides facilities for encrypting and decrypting typed objects. /// diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricBinaryProcessor.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricBinaryProcessor.cs deleted file mode 100644 index b578ec70..00000000 --- a/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricBinaryProcessor.cs +++ /dev/null @@ -1,31 +0,0 @@ -// ================================================================================================================================= -// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. -// ================================================================================================================================= - -using RapidField.SolidInstruments.Serialization; -using System; -using System.Security.Cryptography; - -namespace RapidField.SolidInstruments.Cryptography.Symmetric -{ - /// - /// Provides facilities for encrypting and decrypting byte arrays. - /// - public sealed class SymmetricBinaryProcessor : SymmetricProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// - /// A random number generator that is used to generate initialization vectors. - /// - /// - /// is . - /// - public SymmetricBinaryProcessor(RandomNumberGenerator randomnessProvider) - : base(randomnessProvider, new PassThroughSerializer()) - { - return; - } - } -} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKey.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKey.cs index 364d6cb4..16816c21 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKey.cs @@ -38,7 +38,7 @@ public sealed class SymmetricKey : Instrument, ISymmetricKey /// The mode used to derive the output key. /// /// - /// A buffer that is used to derive key bits. + /// A bit field that is used to derive key bits. /// /// /// is equal to or @@ -60,60 +60,7 @@ private SymmetricKey(SymmetricAlgorithmSpecification algorithm, SymmetricKeyDeri BlockCount = (KeySourceWordCount / BlockWordCount); // Copy in the key source bits. - KeySource.Access(buffer => Array.Copy(keySource, buffer, buffer.Length)); - } - - /// - /// Creates a new instance of a using the specified buffer. - /// - /// - /// A binary representation of a . - /// - /// - /// A new instance of a . - /// - /// - /// is invalid. - /// - /// - /// is . - /// - public static SymmetricKey FromBuffer(ISecureMemory buffer) - { - buffer.RejectIf().IsNull(nameof(buffer)).OrIf(argument => argument.LengthInBytes != SerializedLength, nameof(buffer), "The specified buffer is invalid."); - - try - { - var result = (SymmetricKey)null; - - buffer.Access(pinnedCiphertext => - { - using (var plaintextBuffer = new PinnedMemory(SerializedPlaintextLength, true)) - { - using (var cipher = BufferEncryptionAlgorithm.ToCipher(RandomnessProvider)) - { - using (var plaintext = cipher.Decrypt(pinnedCiphertext, BufferEncryptionKey)) - { - Array.Copy(plaintext, 0, plaintextBuffer, 0, SerializedPlaintextLength); - } - } - - using (var keySource = new PinnedMemory(KeySourceLengthInBytes, true)) - { - Array.Copy(plaintextBuffer, KeySourceBufferIndex, keySource, 0, KeySourceLengthInBytes); - var algorithm = (SymmetricAlgorithmSpecification)plaintextBuffer[AlgorithmBufferIndex]; - var derivationMode = (SymmetricKeyDerivationMode)plaintextBuffer[DerivationModeBufferIndex]; - result = new SymmetricKey(algorithm, derivationMode, keySource); - } - } - }); - - return result; - } - catch (Exception exception) - { - throw new ArgumentException("The specified buffer is invalid.", nameof(buffer), exception); - } + KeySource.Access(memory => Array.Copy(keySource, memory, memory.Length)); } /// @@ -200,6 +147,59 @@ public static SymmetricKey FromPassword(String password, SymmetricAlgorithmSpeci } } + /// + /// Creates a new instance of a using the specified secure bit field. + /// + /// + /// A secure bit field containing a . + /// + /// + /// A new instance of a . + /// + /// + /// is invalid. + /// + /// + /// is . + /// + public static SymmetricKey FromSecureMemory(ISecureMemory secureMemory) + { + secureMemory.RejectIf().IsNull(nameof(secureMemory)).OrIf(argument => argument.LengthInBytes != SerializedLength, nameof(secureMemory), "The specified memory field is invalid."); + + try + { + var key = (SymmetricKey)null; + + secureMemory.Access(pinnedCiphertext => + { + using (var plaintextBuffer = new PinnedMemory(SerializedPlaintextLength, true)) + { + using (var cipher = SecureMemoryEncryptionAlgorithm.ToCipher(RandomnessProvider)) + { + using (var plaintext = cipher.Decrypt(pinnedCiphertext, SecureMemoryEncryptionKey)) + { + Array.Copy(plaintext, 0, plaintextBuffer, 0, SerializedPlaintextLength); + } + } + + using (var keySource = new PinnedMemory(KeySourceLengthInBytes, true)) + { + Array.Copy(plaintextBuffer, KeySourceSecureMemoryIndex, keySource, 0, KeySourceLengthInBytes); + var algorithm = (SymmetricAlgorithmSpecification)plaintextBuffer[AlgorithmSecureMemoryIndex]; + var derivationMode = (SymmetricKeyDerivationMode)plaintextBuffer[DerivationModeSecureMemoryIndex]; + key = new SymmetricKey(algorithm, derivationMode, keySource); + } + } + }); + + return key; + } + catch (Exception exception) + { + throw new ArgumentException("The specified memory field is invalid.", nameof(secureMemory), exception); + } + } + /// /// Generates a new . /// @@ -253,58 +253,6 @@ public static SymmetricKey New(SymmetricAlgorithmSpecification algorithm, Symmet } } - /// - /// Converts the value of the current to its equivalent binary representation. - /// - /// - /// A binary representation of the current . - /// - [DebuggerHidden] - public ISecureMemory ToBuffer() - { - var resultBuffer = new SecureMemory(SerializedLength); - - try - { - using (var controlToken = StateControl.Enter()) - { - using (var plaintextBuffer = new PinnedMemory(SerializedPlaintextLength, true)) - { - KeySource.Access(pinnedKeySourceBuffer => - { - Array.Copy(pinnedKeySourceBuffer, 0, plaintextBuffer, KeySourceBufferIndex, KeySourceLengthInBytes); - }); - - plaintextBuffer[AlgorithmBufferIndex] = (Byte)Algorithm; - plaintextBuffer[DerivationModeBufferIndex] = (Byte)DerivationMode; - - using (var cipher = BufferEncryptionAlgorithm.ToCipher(RandomnessProvider)) - { - using (var initializationVector = new PinnedMemory(cipher.BlockSizeInBytes, true)) - { - RandomnessProvider.GetBytes(initializationVector); - - resultBuffer.Access(pinnedResultBuffer => - { - using (var ciphertext = cipher.Encrypt(plaintextBuffer, BufferEncryptionKey, initializationVector)) - { - Array.Copy(ciphertext, 0, pinnedResultBuffer, 0, SerializedLength); - } - }); - } - } - } - } - - return resultBuffer; - } - catch - { - resultBuffer.Dispose(); - throw new SecurityException("Key serialization failed."); - } - } - /// /// Converts the current to symmetric-key plaintext with correct bit-length for the encryption /// mode specified by . @@ -345,7 +293,7 @@ public ISecureMemory ToDerivedKeyBytes() { KeySource.Access(memory => { - // Convert the source buffer to an array of 32-bit words. + // Convert the source bit field to an array of 32-bit words. Buffer.BlockCopy(memory, 0, sourceWords, 0, KeySourceLengthInBytes); }); @@ -416,6 +364,58 @@ public ISecureMemory ToDerivedKeyBytes() } } + /// + /// Converts the value of the current to a secure bit field. + /// + /// + /// A secure bit field containing a representation of the current . + /// + [DebuggerHidden] + public ISecureMemory ToSecureMemory() + { + var secureMemory = new SecureMemory(SerializedLength); + + try + { + using (var controlToken = StateControl.Enter()) + { + using (var plaintextMemory = new PinnedMemory(SerializedPlaintextLength, true)) + { + KeySource.Access(pinnedKeySourceMemory => + { + Array.Copy(pinnedKeySourceMemory, 0, plaintextMemory, KeySourceSecureMemoryIndex, KeySourceLengthInBytes); + }); + + plaintextMemory[AlgorithmSecureMemoryIndex] = (Byte)Algorithm; + plaintextMemory[DerivationModeSecureMemoryIndex] = (Byte)DerivationMode; + + using (var cipher = SecureMemoryEncryptionAlgorithm.ToCipher(RandomnessProvider)) + { + using (var initializationVector = new PinnedMemory(cipher.BlockSizeInBytes, true)) + { + RandomnessProvider.GetBytes(initializationVector); + + secureMemory.Access(pinnedMemory => + { + using (var ciphertext = cipher.Encrypt(plaintextMemory, SecureMemoryEncryptionKey, initializationVector)) + { + Array.Copy(ciphertext, 0, pinnedMemory, 0, SerializedLength); + } + }); + } + } + } + } + + return secureMemory; + } + catch + { + secureMemory.Dispose(); + throw new SecurityException("Key serialization failed."); + } + } + /// /// Generates private key source bytes using the specified password. /// @@ -466,7 +466,7 @@ internal static PinnedMemory DeriveKeySourceBytesFromPassword(String password, I /// The mode used to derive the generated key. The default value is . /// /// - /// A buffer comprising 384 bytes (3,072 bits) from which the private key is derived. + /// A bit field comprising 384 bytes (3,072 bits) from which the private key is derived. /// /// /// A new . @@ -493,7 +493,7 @@ protected override void Dispose(Boolean disposing) { if (disposing) { - BufferEncryptionKey.Dispose(); + SecureMemoryEncryptionKey.Dispose(); KeySource.Dispose(); LazyPbkdf2Provider.Dispose(); } @@ -504,6 +504,12 @@ protected override void Dispose(Boolean disposing) } } + /// + /// Finalizes static members of the class. + /// + [DebuggerHidden] + private static void FinalizeStaticMembers() => SecureMemoryEncryptionKey.Dispose(); + /// /// Substitutes the specified input word for a word derived from . /// @@ -530,24 +536,24 @@ private static UInt32 SubstituteWord(UInt32 inputWord) [DebuggerHidden] private Rfc2898DeriveBytes InitializePbkdf2Algorithm() { - var result = (Rfc2898DeriveBytes)null; + var algorithm = (Rfc2898DeriveBytes)null; - KeySource.Access(buffer => + KeySource.Access(memory => { - var iterationSumBuffer = buffer.Take(Pbkdf2IterationSumLengthInBytes); - var saltBuffer = buffer.Skip(Pbkdf2IterationSumLengthInBytes).Take(Pbkdf2SaltLengthInBytes); - var passwordBuffer = buffer.Skip(Pbkdf2IterationSumLengthInBytes + Pbkdf2SaltLengthInBytes).Take(Pbkdf2PasswordLengthInBytes); + var iterationSumBytes = memory.Take(Pbkdf2IterationSumLengthInBytes); + var saltBytes = memory.Skip(Pbkdf2IterationSumLengthInBytes).Take(Pbkdf2SaltLengthInBytes); + var passwordBytes = memory.Skip(Pbkdf2IterationSumLengthInBytes + Pbkdf2SaltLengthInBytes).Take(Pbkdf2PasswordLengthInBytes); var iterationCount = Pbkdf2MinimumIterationCount; - foreach (var iterationSumValue in iterationSumBuffer) + foreach (var iterationSumValue in iterationSumBytes) { iterationCount += iterationSumValue; } - result = new Rfc2898DeriveBytes(passwordBuffer.ToArray(), saltBuffer.ToArray(), iterationCount); + algorithm = new Rfc2898DeriveBytes(passwordBytes.ToArray(), saltBytes.ToArray(), iterationCount); }); - return result; + return algorithm; } /// @@ -580,7 +586,7 @@ public SymmetricAlgorithmSpecification Algorithm /// Represents the number of bytes comprising a post-encrypted, serialized representation of a . /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - internal const Int32 SerializedLength = (SerializedPlaintextLength + (BufferEncryptionAlgorithmBlockSizeInBytes * 2) - AlgorithmLength - DerivationModeLength); + internal const Int32 SerializedLength = (SerializedPlaintextLength + (SecureMemoryEncryptionAlgorithmBlockSizeInBytes * 2) - AlgorithmLength - DerivationModeLength); /// /// Represents the encoding that is used when evaluating passwords. @@ -594,12 +600,6 @@ public SymmetricAlgorithmSpecification Algorithm [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal static readonly RandomNumberGenerator RandomnessProvider = HardenedRandomNumberGenerator.Instance; - /// - /// Represents the byte index of the algorithm byte within an unencrypted, serialized buffer. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const Int32 AlgorithmBufferIndex = (KeySourceBufferIndex + KeySourceLengthInBytes); - /// /// Represents the byte length of . /// @@ -607,16 +607,10 @@ public SymmetricAlgorithmSpecification Algorithm private const Int32 AlgorithmLength = sizeof(SymmetricAlgorithmSpecification); /// - /// Represents the symmetric-key algorithm that is used to obscure serialized buffers. + /// Represents the byte index of the algorithm byte within an unencrypted, serialized bit field. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const SymmetricAlgorithmSpecification BufferEncryptionAlgorithm = SymmetricAlgorithmSpecification.Aes128Cbc; - - /// - /// Represents the block size, in bytes, for the symmetric-key algorithm that is used to obscure serialized buffers. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const Int32 BufferEncryptionAlgorithmBlockSizeInBytes = 16; + private const Int32 AlgorithmSecureMemoryIndex = (KeySourceSecureMemoryIndex + KeySourceLengthInBytes); /// /// Represents the default symmetric-key algorithm specification for new keys. @@ -631,22 +625,22 @@ public SymmetricAlgorithmSpecification Algorithm private const SymmetricKeyDerivationMode DefaultDerivationMode = SymmetricKeyDerivationMode.Pbkdf2; /// - /// Represents the byte index of the derivation mode byte within an unencrypted, serialized buffer. + /// Represents the byte length of . /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const Int32 DerivationModeBufferIndex = (AlgorithmBufferIndex + AlgorithmLength); + private const Int32 DerivationModeLength = sizeof(SymmetricKeyDerivationMode); /// - /// Represents the byte length of . + /// Represents the byte index of the derivation mode byte within an unencrypted, serialized bit field. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const Int32 DerivationModeLength = sizeof(SymmetricKeyDerivationMode); + private const Int32 DerivationModeSecureMemoryIndex = (AlgorithmSecureMemoryIndex + AlgorithmLength); /// - /// Represents the byte index of the key source within an unencrypted, serialized buffer. + /// Represents the byte index of the key source within an unencrypted, serialized bit field. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const Int32 KeySourceBufferIndex = 0; + private const Int32 KeySourceSecureMemoryIndex = 0; /// /// Represents the exact number of 32-bit key words that are derived from . @@ -684,6 +678,18 @@ public SymmetricAlgorithmSpecification Algorithm [DebuggerBrowsable(DebuggerBrowsableState.Never)] private const Int32 Pbkdf2SaltLengthInBytes = (KeySourceLengthInBytes / 3); + /// + /// Represents the symmetric-key algorithm that is used to obscure serialized bit fields. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const SymmetricAlgorithmSpecification SecureMemoryEncryptionAlgorithm = SymmetricAlgorithmSpecification.Aes128Cbc; + + /// + /// Represents the block size, in bytes, for the symmetric-key algorithm that is used to obscure serialized bit fields. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Int32 SecureMemoryEncryptionAlgorithmBlockSizeInBytes = 16; + /// /// Represents the number of bytes comprising a pre-encrypted, serialized representation of a . /// @@ -691,7 +697,7 @@ public SymmetricAlgorithmSpecification Algorithm private const Int32 SerializedPlaintextLength = (KeySourceLengthInBytes + AlgorithmLength + DerivationModeLength); /// - /// Represents the key for the symmetric-key algorithm that is used to obscure serialized buffers. + /// Represents the key for the symmetric-key algorithm that is used to obscure serialized bit fields. /// /// /// The author acknowledges that obscurity does not ensure security. Encrypting sensitive information with a known key does @@ -700,7 +706,7 @@ public SymmetricAlgorithmSpecification Algorithm /// probably few good reasons to. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private static readonly PinnedMemory BufferEncryptionKey = new PinnedMemory(new Byte[] + private static readonly PinnedMemory SecureMemoryEncryptionKey = new PinnedMemory(new Byte[] { 0xaa, 0xf0, 0xcc, 0xff, 0x00, 0x33, 0x0f, 0x55, 0xff, 0xcc, 0xf0, 0xaa, 0x55, 0x0f, 0x33, 0x00 }); @@ -709,7 +715,7 @@ public SymmetricAlgorithmSpecification Algorithm /// Represents a finalizer for static members of the class. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private static readonly StaticMemberFinalizer Finalizer = new StaticMemberFinalizer(BufferEncryptionKey.Dispose); + private static readonly StaticMemberFinalizer StaticMemberFinalizer = new StaticMemberFinalizer(FinalizeStaticMembers); /// /// Represents substitution bytes that are used to fulfill key derivation operations when is @@ -766,7 +772,7 @@ public SymmetricAlgorithmSpecification Algorithm private readonly Int32 DerivedKeyLength; /// - /// Represents a buffer that is used to derive key bits from the current . + /// Represents a bit field that is used to derive key bits from the current . /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly ISecureMemory KeySource; diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKeyCipher.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKeyCipher.cs index 8812c472..cceedb27 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKeyCipher.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKeyCipher.cs @@ -64,8 +64,8 @@ protected SymmetricKeyCipher(Int32 blockSize, Int32 keySize, CipherMode mode, Pa /// The plaintext result of the algorithm. /// /// - /// The binary length of is invalid -or- the binary length of - /// is invalid. + /// The byte length of is invalid -or- the byte length of is + /// invalid. /// /// /// is -or- is @@ -101,8 +101,8 @@ internal PinnedMemory Decrypt(PinnedMemory ciphertext, PinnedMemory privateKey) /// The ciphertext result of the algorithm. /// /// - /// The binary length of is invalid -or- the cipher mode is and - /// the binary length of is invalid. + /// The byte length of is invalid -or- the cipher mode is and + /// the byte length of is invalid. /// /// /// is -or- is @@ -243,7 +243,7 @@ private PinnedMemory DecryptInEcbMode(PinnedMemory ciphertext, PinnedMemory priv /// The ciphertext result of the algorithm. /// /// - /// The binary length of is invalid. + /// The byte length of is invalid. /// /// /// is . diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricProcessor.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricProcessor.cs index d3a40f57..9c84fa68 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricProcessor.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricProcessor.cs @@ -15,6 +15,47 @@ namespace RapidField.SolidInstruments.Cryptography.Symmetric { + /// + /// Provides facilities for encrypting and decrypting byte arrays. + /// + /// + /// is the default implementation of . + /// + public sealed class SymmetricProcessor : SymmetricProcessor, ISymmetricProcessor + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A random number generator that is used to generate initialization vectors. + /// + /// + /// is . + /// + public SymmetricProcessor(RandomNumberGenerator randomnessProvider) + : base(randomnessProvider, new PassThroughSerializer()) + { + return; + } + + /// + /// Creates a new for the specified serializable type. + /// + /// + /// The serializable object type that the processor can encrypt or decrypt. + /// + /// + /// A new for the specified serializable type. + /// + public static ISymmetricProcessor ForType() + where T : class => new SymmetricProcessor(); + + /// + /// Represents a singleton instance of the class. + /// + public static readonly ISymmetricProcessor Instance = new SymmetricProcessor(HardenedRandomNumberGenerator.Instance); + } + /// /// Provides facilities for encrypting and decrypting typed objects. /// @@ -27,22 +68,46 @@ namespace RapidField.SolidInstruments.Cryptography.Symmetric public class SymmetricProcessor : ISymmetricProcessor where T : class { + /// + /// Initializes a new instance of the class. + /// + public SymmetricProcessor() + : this(HardenedRandomNumberGenerator.Instance) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A random number generator that is used to generate initialization vectors. + /// + /// + /// is . + /// + public SymmetricProcessor(RandomNumberGenerator randomnessProvider) + : this(randomnessProvider, DefaultSerializer) + { + return; + } + /// /// Initializes a new instance of the class. /// /// /// A random number generator that is used to generate initialization vectors. /// - /// - /// A binary serializer that is used to transform plaintext. + /// + /// A serializer that is used to transform plaintext. /// /// - /// is -or- is + /// is -or- is /// . /// - public SymmetricProcessor(RandomNumberGenerator randomnessProvider, ISerializer binarySerializer) + public SymmetricProcessor(RandomNumberGenerator randomnessProvider, ISerializer serializer) { - BinarySerializer = binarySerializer.RejectIf().IsNull(nameof(binarySerializer)).TargetArgument; + Serializer = serializer.RejectIf().IsNull(nameof(serializer)).TargetArgument; RandomnessProvider = randomnessProvider.RejectIf().IsNull(nameof(randomnessProvider)); } @@ -65,9 +130,9 @@ public T Decrypt(Byte[] ciphertext, ISymmetricKey key) { try { - using (var keyBuffer = key.ToDerivedKeyBytes()) + using (var keyMemory = key.ToDerivedKeyBytes()) { - return Decrypt(ciphertext, keyBuffer, key.Algorithm); + return Decrypt(ciphertext, keyMemory, key.Algorithm); } } catch @@ -96,12 +161,12 @@ public T Decrypt(Byte[] ciphertext, ICascadingSymmetricKey key) try { var keys = key.Keys; - var binaryDecryptor = new SymmetricBinaryProcessor(RandomnessProvider); + var decryptor = new SymmetricProcessor(RandomnessProvider); var buffer = ciphertext; for (var i = (key.Depth - 1); i > 0; i--) { - buffer = binaryDecryptor.Decrypt(buffer, keys.ElementAt(i)); + buffer = decryptor.Decrypt(buffer, keys.ElementAt(i)); } return Decrypt(buffer, keys.First()); @@ -189,9 +254,9 @@ public Byte[] Encrypt(T plaintextObject, ISymmetricKey key, Byte[] initializatio { try { - using (var keyBuffer = key.ToDerivedKeyBytes()) + using (var keyMemory = key.ToDerivedKeyBytes()) { - return Encrypt(plaintextObject, keyBuffer, key.Algorithm, initializationVector); + return Encrypt(plaintextObject, keyMemory, key.Algorithm, initializationVector); } } catch @@ -220,12 +285,12 @@ public Byte[] Encrypt(T plaintextObject, ICascadingSymmetricKey key) try { var keys = key.Keys; - var binaryEncryptor = new SymmetricBinaryProcessor(RandomnessProvider); + var encryptor = new SymmetricProcessor(RandomnessProvider); var buffer = Encrypt(plaintextObject, keys.First()); for (var i = 1; i < key.Depth; i++) { - buffer = binaryEncryptor.Encrypt(buffer, keys.ElementAt(i)); + buffer = encryptor.Encrypt(buffer, keys.ElementAt(i)); } return buffer; @@ -284,9 +349,9 @@ public Byte[] Encrypt(T plaintextObject, ISecureMemory key, SymmetricAlgorithmSp { var ciphertext = (Byte[])null; - key.Access(keyBuffer => + key.Access(keyMemory => { - ciphertext = Encrypt(plaintextObject, keyBuffer, algorithm, initializationVector); + ciphertext = Encrypt(plaintextObject, keyMemory, algorithm, initializationVector); }); return ciphertext; @@ -321,7 +386,7 @@ private T Decrypt(Byte[] ciphertext, PinnedMemory key, SymmetricAlgorithmSpecifi { using (var plaintext = cipher.Decrypt(pinnedCiphertext, key)) { - return BinarySerializer.Deserialize(plaintext); + return Serializer.Deserialize(plaintext); } } } @@ -349,7 +414,7 @@ private T Decrypt(Byte[] ciphertext, PinnedMemory key, SymmetricAlgorithmSpecifi [DebuggerHidden] private Byte[] Encrypt(T plaintextObject, PinnedMemory key, SymmetricAlgorithmSpecification algorithm, Byte[] initializationVector) { - var plaintext = BinarySerializer.Serialize(plaintextObject); + var plaintext = Serializer.Serialize(plaintextObject); var plaintextLength = plaintext.Length; using (var pinnedPlaintext = new PinnedMemory(plaintextLength, true)) @@ -389,15 +454,21 @@ private Byte[] Encrypt(T plaintextObject, PinnedMemory key, SymmetricAlgorithmSp } /// - /// Represents a binary serializer that is used to transform plaintext. + /// Represents the default serializer that is used to transform plaintext. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly ISerializer BinarySerializer; + private static readonly ISerializer DefaultSerializer = new CompressedJsonSerializer(); /// /// Represents a random number generator that is used to generate initialization vectors. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly RandomNumberGenerator RandomnessProvider; + + /// + /// Represents a serializer that is used to transform plaintext. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly ISerializer Serializer; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricStringProcessor.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricStringProcessor.cs index 8f553e92..f2700814 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricStringProcessor.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricStringProcessor.cs @@ -27,5 +27,10 @@ public SymmetricStringProcessor(RandomNumberGenerator randomnessProvider) { return; } + + /// + /// Represents a singleton instance of the class. + /// + public static readonly ISymmetricProcessor Instance = new SymmetricStringProcessor(HardenedRandomNumberGenerator.Instance); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.InversionOfControl/Extensions/IServiceCollectionExtensions.cs b/src/RapidField.SolidInstruments.InversionOfControl/Extensions/IServiceCollectionExtensions.cs index 664d0a12..d3d12480 100644 --- a/src/RapidField.SolidInstruments.InversionOfControl/Extensions/IServiceCollectionExtensions.cs +++ b/src/RapidField.SolidInstruments.InversionOfControl/Extensions/IServiceCollectionExtensions.cs @@ -103,6 +103,12 @@ internal static IServiceCollection AddDependencyPackage + /// Finalizes static members of the class. + /// + [DebuggerHidden] + private static void FinalizeStaticMembers() => ReferenceManager.Dispose(); + /// /// Represents a reference manager for disposable references created by . /// @@ -113,6 +119,6 @@ internal static IServiceCollection AddDependencyPackage class. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private static readonly StaticMemberFinalizer Finalizer = new StaticMemberFinalizer(ReferenceManager.Dispose); + private static readonly StaticMemberFinalizer StaticMemberFinalizer = new StaticMemberFinalizer(FinalizeStaticMembers); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransport.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransport.cs index fa952cf9..d5717fef 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransport.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransport.cs @@ -1080,6 +1080,12 @@ protected override void Dispose(Boolean disposing) } } + /// + /// Finalizes static members of the class. + /// + [DebuggerHidden] + private static void FinalizeStaticMembers() => LazyInstance.Dispose(); + /// /// Initializes a new in-memory instance. /// @@ -1218,16 +1224,16 @@ public SerializationFormat MessageBodySerializationFormat internal static IMessageTransport Instance => LazyInstance.Value; /// - /// Represents a finalizer for static members of the class. + /// Represents a lazily-initialized in-memory instance. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private static readonly StaticMemberFinalizer Finalizer = new StaticMemberFinalizer(LazyInstance.Dispose); + private static readonly Lazy LazyInstance = new Lazy(InitializeInstance, LazyThreadSafetyMode.ExecutionAndPublication); /// - /// Represents a lazily-initialized in-memory instance. + /// Represents a finalizer for static members of the class. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private static readonly Lazy LazyInstance = new Lazy(InitializeInstance, LazyThreadSafetyMode.ExecutionAndPublication); + private static readonly StaticMemberFinalizer StaticMemberFinalizer = new StaticMemberFinalizer(FinalizeStaticMembers); /// /// Represents a collection of active connections to the current , which are keyed by diff --git a/src/RapidField.SolidInstruments.ObjectComposition/ObjectContainer.cs b/src/RapidField.SolidInstruments.ObjectComposition/ObjectContainer.cs index 17222977..e3b6b3d3 100644 --- a/src/RapidField.SolidInstruments.ObjectComposition/ObjectContainer.cs +++ b/src/RapidField.SolidInstruments.ObjectComposition/ObjectContainer.cs @@ -109,7 +109,7 @@ private ObjectContainer(IConfiguration applicationConfiguration, IObjectFactory DefinitionConfigurator = definitionConfigurator.RejectIf().IsNull(nameof(definitionConfigurator)); Factory = factory.RejectIf().IsNull(nameof(factory)).TargetArgument; LazyInstanceDictionary = new Lazy>(InitializeInstanceDictionary, LazyThreadSafetyMode.ExecutionAndPublication); - LazyInstanceGroup = new Lazy(InitializeInstanceGroup, LazyThreadSafetyMode.PublicationOnly); + LazyInstanceGroup = new Lazy(InitializeInstanceGroup, LazyThreadSafetyMode.PublicationOnly); ManagesFactory = managesFactory; } @@ -330,7 +330,7 @@ private IDictionary InitializeInstanceDictionary() /// An object that manages creation and lifetime of instances for the current . /// [DebuggerHidden] - private FactoryProducedInstanceGroup InitializeInstanceGroup() => new FactoryProducedInstanceGroup(Factory); + private IFactoryProducedInstanceGroup InitializeInstanceGroup() => new FactoryProducedInstanceGroup(Factory); /// /// Gets the types of the instances that are managed by the current . @@ -369,7 +369,7 @@ public IEnumerable InstanceTypes /// Gets an object that manages creation and lifetime of instances for the current . /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private FactoryProducedInstanceGroup InstanceGroup => LazyInstanceGroup.Value; + private IFactoryProducedInstanceGroup InstanceGroup => LazyInstanceGroup.Value; /// /// Represents an action that configures the request-product type pair definitions for the container. @@ -394,7 +394,7 @@ public IEnumerable InstanceTypes /// . /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly Lazy LazyInstanceGroup; + private readonly Lazy LazyInstanceGroup; /// /// Represents a valued that indicates whether or not the current manages diff --git a/src/RapidField.SolidInstruments.Serialization/Base64Serializer.cs b/src/RapidField.SolidInstruments.Serialization/Base64Serializer.cs index 06875e68..35101359 100644 --- a/src/RapidField.SolidInstruments.Serialization/Base64Serializer.cs +++ b/src/RapidField.SolidInstruments.Serialization/Base64Serializer.cs @@ -22,9 +22,9 @@ public Base64Serializer() } /// - /// Converts the specified buffer to its typed equivalent. + /// Converts the specified bit field to its typed equivalent. /// - /// + /// /// A serialized object. /// /// @@ -34,13 +34,13 @@ public Base64Serializer() /// The deserialized object. /// /// - /// is invalid or an error occurred during deserialization. + /// is invalid or an error occurred during deserialization. /// - protected sealed override String Deserialize(Byte[] buffer, SerializationFormat format) + protected sealed override String Deserialize(Byte[] serializedObject, SerializationFormat format) { try { - return Convert.ToBase64String(buffer); + return Convert.ToBase64String(serializedObject); } catch (Exception exception) { @@ -49,7 +49,7 @@ protected sealed override String Deserialize(Byte[] buffer, SerializationFormat } /// - /// Converts the specified object to a buffer. + /// Converts the specified object to a bit field. /// /// /// An object to be serialized. @@ -58,7 +58,7 @@ protected sealed override String Deserialize(Byte[] buffer, SerializationFormat /// The format to use for serialization. /// /// - /// The serialized buffer. + /// The serialized bit field. /// /// /// is invalid or an error occurred during serialization. diff --git a/src/RapidField.SolidInstruments.Serialization/DynamicSerializer.cs b/src/RapidField.SolidInstruments.Serialization/DynamicSerializer.cs index a0ccb988..d6e1c5a2 100644 --- a/src/RapidField.SolidInstruments.Serialization/DynamicSerializer.cs +++ b/src/RapidField.SolidInstruments.Serialization/DynamicSerializer.cs @@ -51,30 +51,30 @@ public DynamicSerializer(SerializationFormat format) } /// - /// Converts the specified buffer to its typed equivalent. + /// Converts the specified bit field to its typed equivalent. /// - /// + /// /// A serialized object. /// /// /// The deserialized object. /// /// - /// is . + /// is . /// /// - /// is invalid or an error occurred during deserialization. + /// is invalid or an error occurred during deserialization. /// - public T Deserialize(Byte[] buffer) => Deserialize(buffer.RejectIf().IsNull(nameof(buffer)), Format); + public T Deserialize(Byte[] serializedObject) => Deserialize(serializedObject.RejectIf().IsNull(nameof(serializedObject)), Format); /// - /// Converts the specified object to a serialized buffer. + /// Converts the specified object to a serialized bit field. /// /// /// An object to be serialized. /// /// - /// The serialized buffer. + /// The serialized bit field. /// /// /// is . @@ -93,9 +93,9 @@ public DynamicSerializer(SerializationFormat format) public override String ToString() => $"{{ \"{nameof(Format)}\": \"{Format}\", {nameof(ContractType)}\": \"{ContractType.FullName}\" }}"; /// - /// Converts the specified buffer to its typed equivalent. + /// Converts the specified bit field to its typed equivalent. /// - /// + /// /// A serialized object. /// /// @@ -105,20 +105,20 @@ public DynamicSerializer(SerializationFormat format) /// The deserialized object. /// /// - /// is invalid or an error occurred during deserialization. + /// is invalid or an error occurred during deserialization. /// - protected virtual T Deserialize(Byte[] buffer, SerializationFormat format) => format switch + protected virtual T Deserialize(Byte[] serializedObject, SerializationFormat format) => format switch { - SerializationFormat.Binary => Deserialize(buffer, DefaultFormat), - SerializationFormat.CompressedJson => DeserializeFromJson(buffer.Decompress()), - SerializationFormat.CompressedXml => DeserializeFromXml(buffer.Decompress()), - SerializationFormat.Json => DeserializeFromJson(buffer), - SerializationFormat.Xml => DeserializeFromXml(buffer), + SerializationFormat.Binary => Deserialize(serializedObject, DefaultFormat), + SerializationFormat.CompressedJson => DeserializeFromJson(serializedObject.Decompress()), + SerializationFormat.CompressedXml => DeserializeFromXml(serializedObject.Decompress()), + SerializationFormat.Json => DeserializeFromJson(serializedObject), + SerializationFormat.Xml => DeserializeFromXml(serializedObject), _ => throw new UnsupportedSpecificationException($"The specified serialization format, {Format}, is not supported.") }; /// - /// Converts the specified object to a buffer. + /// Converts the specified object to a bit field. /// /// /// An object to be serialized. @@ -127,7 +127,7 @@ public DynamicSerializer(SerializationFormat format) /// The format to use for serialization. /// /// - /// The serialized buffer. + /// The serialized bit field. /// /// /// is invalid or an error occurred during serialization. @@ -161,25 +161,25 @@ public DynamicSerializer(SerializationFormat format) private static DataContractSerializer InitializeXmlSerializer() => new DataContractSerializer(ContractType, XmlSerializerSettings); /// - /// Converts the specified JSON buffer to its typed equivalent. + /// Converts the specified JSON bit field to its typed equivalent. /// - /// + /// /// A serialized object. /// /// /// The deserialized object. /// /// - /// is invalid or an error occurred during deserialization. + /// is invalid or an error occurred during deserialization. /// [DebuggerHidden] - private T DeserializeFromJson(Byte[] buffer) + private T DeserializeFromJson(Byte[] serializedObject) { - using (var stream = new MemoryStream(buffer)) + using (var stream = new MemoryStream(serializedObject)) { try { - return JsonSerializer.ReadObject(stream) as T ?? throw new SerializationException("The specified buffer is invalid."); + return JsonSerializer.ReadObject(stream) as T ?? throw new SerializationException("The specified bit field is invalid."); } catch (SerializationException) { @@ -193,25 +193,25 @@ private T DeserializeFromJson(Byte[] buffer) } /// - /// Converts the specified XML buffer to its typed equivalent. + /// Converts the specified XML bit field to its typed equivalent. /// - /// + /// /// A serialized object. /// /// /// The deserialized object. /// /// - /// is invalid or an error occurred during deserialization. + /// is invalid or an error occurred during deserialization. /// [DebuggerHidden] - private T DeserializeFromXml(Byte[] buffer) + private T DeserializeFromXml(Byte[] serializedObject) { - using (var stream = new MemoryStream(buffer)) + using (var stream = new MemoryStream(serializedObject)) { try { - return XmlSerializer.ReadObject(stream) as T ?? throw new SerializationException("The specified buffer is invalid."); + return XmlSerializer.ReadObject(stream) as T ?? throw new SerializationException("The specified bit field is invalid."); } catch (SerializationException) { @@ -225,13 +225,13 @@ private T DeserializeFromXml(Byte[] buffer) } /// - /// Converts the specified object to a JSON buffer. + /// Converts the specified object to a JSON bit field. /// /// /// An object to be serialized. /// /// - /// The serialized buffer. + /// The serialized bit field. /// /// /// is invalid or an error occurred during serialization. @@ -259,13 +259,13 @@ private Byte[] SerializeToJson(T target) } /// - /// Converts the specified object to an XML buffer. + /// Converts the specified object to an XML bit field. /// /// /// An object to be serialized. /// /// - /// The serialized buffer. + /// The serialized bit field. /// /// /// is invalid or an error occurred during serialization. @@ -387,9 +387,9 @@ protected DynamicSerializer(SerializationFormat format) public override String ToString() => $"{{ \"{nameof(Format)}\": \"{Format}\" }}"; /// - /// Converts the specified buffer to its typed equivalent. + /// Converts the specified bit field to its typed equivalent. /// - /// + /// /// A serialized object. /// /// @@ -402,16 +402,16 @@ protected DynamicSerializer(SerializationFormat format) /// The deserialized object. /// /// - /// is invalid or an error occurred during deserialization. + /// is invalid or an error occurred during deserialization. /// [DebuggerHidden] - internal static Object Deserialize(Byte[] buffer, Type targetType, SerializationFormat format) + internal static Object Deserialize(Byte[] serializedObject, Type targetType, SerializationFormat format) { try { var serializer = Create(targetType.RejectIf().IsNull(nameof(targetType)), format.RejectIf().IsEqualToValue(SerializationFormat.Unspecified)); var deserializeMethod = serializer.GetType().GetMethod(nameof(ISerializer.Deserialize)); - return deserializeMethod.Invoke(serializer, new Object[] { buffer.RejectIf().IsNull(nameof(buffer)).TargetArgument }); + return deserializeMethod.Invoke(serializer, new Object[] { serializedObject.RejectIf().IsNull(nameof(serializedObject)).TargetArgument }); } catch (SerializationException) { @@ -424,7 +424,7 @@ internal static Object Deserialize(Byte[] buffer, Type targetType, Serialization } /// - /// Converts the specified object to a buffer. + /// Converts the specified object to a bit field. /// /// /// An object to be serialized. @@ -436,7 +436,7 @@ internal static Object Deserialize(Byte[] buffer, Type targetType, Serialization /// The format to use for serialization. /// /// - /// The serialized buffer. + /// The serialized bit field. /// /// /// is invalid or an error occurred during serialization. diff --git a/src/RapidField.SolidInstruments.Serialization/ISerializer.cs b/src/RapidField.SolidInstruments.Serialization/ISerializer.cs index f1dc36b8..a52e8470 100644 --- a/src/RapidField.SolidInstruments.Serialization/ISerializer.cs +++ b/src/RapidField.SolidInstruments.Serialization/ISerializer.cs @@ -16,27 +16,27 @@ public interface ISerializer : ISerializer where T : class { /// - /// Converts the specified buffer to its typed equivalent. + /// Converts the specified bit field to its typed equivalent. /// - /// + /// /// A serialized object. /// /// /// The deserialized object. /// /// - /// is . + /// is . /// - public T Deserialize(Byte[] buffer); + public T Deserialize(Byte[] serializedObject); /// - /// Converts the specified object to a serialized buffer. + /// Converts the specified object to a serialized bit field. /// /// /// An object to be serialized. /// /// - /// The serialized buffer. + /// The serialized bit field. /// /// /// is . diff --git a/src/RapidField.SolidInstruments.Serialization/PassThroughSerializer.cs b/src/RapidField.SolidInstruments.Serialization/PassThroughSerializer.cs index fc5fbac4..3c8bdd3a 100644 --- a/src/RapidField.SolidInstruments.Serialization/PassThroughSerializer.cs +++ b/src/RapidField.SolidInstruments.Serialization/PassThroughSerializer.cs @@ -8,7 +8,7 @@ namespace RapidField.SolidInstruments.Serialization { /// - /// Stands in place of a serializer when the input and output are both unformatted binary arrays. + /// Stands in place of a serializer when the input and output are both unformatted byte arrays. /// public sealed class PassThroughSerializer : DynamicSerializer { @@ -22,9 +22,9 @@ public PassThroughSerializer() } /// - /// Converts the specified buffer to its typed equivalent. + /// Converts the specified bit field to its typed equivalent. /// - /// + /// /// A serialized object. /// /// @@ -34,12 +34,12 @@ public PassThroughSerializer() /// The deserialized object. /// /// - /// is invalid or an error occurred during deserialization. + /// is invalid or an error occurred during deserialization. /// - protected sealed override Byte[] Deserialize(Byte[] buffer, SerializationFormat format) => buffer; + protected sealed override Byte[] Deserialize(Byte[] serializedObject, SerializationFormat format) => serializedObject; /// - /// Converts the specified object to a buffer. + /// Converts the specified object to a bit field. /// /// /// An object to be serialized. @@ -48,7 +48,7 @@ public PassThroughSerializer() /// The format to use for serialization. /// /// - /// The serialized buffer. + /// The serialized bit field. /// /// /// is invalid or an error occurred during serialization. diff --git a/src/RapidField.SolidInstruments.Serialization/TextEncodingSerializer.cs b/src/RapidField.SolidInstruments.Serialization/TextEncodingSerializer.cs index 97d2745d..a748a842 100644 --- a/src/RapidField.SolidInstruments.Serialization/TextEncodingSerializer.cs +++ b/src/RapidField.SolidInstruments.Serialization/TextEncodingSerializer.cs @@ -41,9 +41,9 @@ protected TextEncodingSerializer(Encoding characterEncoding) } /// - /// Converts the specified buffer to its typed equivalent. + /// Converts the specified bit field to its typed equivalent. /// - /// + /// /// A serialized object. /// /// @@ -53,13 +53,13 @@ protected TextEncodingSerializer(Encoding characterEncoding) /// The deserialized object. /// /// - /// is invalid or an error occurred during deserialization. + /// is invalid or an error occurred during deserialization. /// - protected override String Deserialize(Byte[] buffer, SerializationFormat format) + protected override String Deserialize(Byte[] serializedObject, SerializationFormat format) { try { - return Deserialize(buffer, CharacterEncoding); + return Deserialize(serializedObject, CharacterEncoding); } catch (Exception exception) { @@ -68,7 +68,7 @@ protected override String Deserialize(Byte[] buffer, SerializationFormat format) } /// - /// Converts the specified object to a buffer. + /// Converts the specified object to a bit field. /// /// /// An object to be serialized. @@ -77,7 +77,7 @@ protected override String Deserialize(Byte[] buffer, SerializationFormat format) /// The format to use for serialization. /// /// - /// The serialized buffer. + /// The serialized bit field. /// /// /// is invalid or an error occurred during serialization. @@ -95,9 +95,9 @@ protected override Byte[] Serialize(String target, SerializationFormat format) } /// - /// Converts the specified buffer to its typed equivalent. + /// Converts the specified bit field to its typed equivalent. /// - /// + /// /// A serialized object. /// /// @@ -107,19 +107,19 @@ protected override Byte[] Serialize(String target, SerializationFormat format) /// The deserialized object. /// /// - /// is invalid. + /// is invalid. /// /// - /// is invalid. + /// is invalid. /// /// /// A fallback occurred. /// [DebuggerHidden] - private static String Deserialize(Byte[] buffer, Encoding characterEncoding) => characterEncoding.GetString(buffer); + private static String Deserialize(Byte[] serializedObject, Encoding characterEncoding) => characterEncoding.GetString(serializedObject); /// - /// Converts the specified object to a buffer. + /// Converts the specified object to a bit field. /// /// /// An object to be serialized. diff --git a/src/RapidField.SolidInstruments.TextEncoding/Base32Encoding.cs b/src/RapidField.SolidInstruments.TextEncoding/Base32Encoding.cs index 72f89f97..210fc544 100644 --- a/src/RapidField.SolidInstruments.TextEncoding/Base32Encoding.cs +++ b/src/RapidField.SolidInstruments.TextEncoding/Base32Encoding.cs @@ -126,7 +126,7 @@ public override Int32 GetBytes(Char[] chars, Int32 charIndex, Int32 charCount, B bytes.RejectIf().IsNull(nameof(bytes)); byteIndex.RejectIf().IsLessThan(0, nameof(byteIndex)).OrIf().IsGreaterThanOrEqualTo(bytes.Length, nameof(byteIndex)); - Byte[] bufferArray; + Byte[] characterBytes; { var buffer = new BigInteger(); @@ -146,12 +146,12 @@ public override Int32 GetBytes(Char[] chars, Int32 charIndex, Int32 charCount, B buffer += key; } - bufferArray = buffer.ToByteArray(); + characterBytes = buffer.ToByteArray(); } // Copy the buffer to the output stream. - var encodedByteCount = Math.Min((bytes.Length - byteIndex), bufferArray.Length); - Array.Copy(bufferArray, 0, bytes, byteIndex, encodedByteCount); + var encodedByteCount = Math.Min((bytes.Length - byteIndex), characterBytes.Length); + Array.Copy(characterBytes, 0, bytes, byteIndex, encodedByteCount); return encodedByteCount; } diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Hashing/HashingProcessorTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Hashing/HashingProcessorTests.cs index 4e49d460..92e1ac5d 100644 --- a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Hashing/HashingProcessorTests.cs +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Hashing/HashingProcessorTests.cs @@ -65,8 +65,8 @@ private static void CalculateHash_ValidateDeterminism(HashingAlgorithmSpecificat using (var randomnessProvider = RandomNumberGenerator.Create()) { // Arrange. - var binarySerializer = new PassThroughSerializer(); - var target = new HashingProcessor(randomnessProvider, binarySerializer); + var serializer = new PassThroughSerializer(); + var target = new HashingProcessor(randomnessProvider, serializer); var plaintextObject = new Byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 }; // Act. @@ -103,8 +103,8 @@ private static void EvaluateHash_ShouldProduceDesiredResults(HashingAlgorithmSpe using (var randomnessProvider = RandomNumberGenerator.Create()) { // Arrange. - var binarySerializer = new PassThroughSerializer(); - var target = new HashingProcessor(randomnessProvider, binarySerializer); + var serializer = new PassThroughSerializer(); + var target = new HashingProcessor(randomnessProvider, serializer); var plaintextObject = new Byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 }; var matchingHashValue = target.CalculateHash(plaintextObject, algorithm, saltingMode); var nonMatchingHashValue = matchingHashValue.PerformCircularBitShift(Core.BitShiftDirection.Left, 16); diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SecretTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SecretTests.cs index df72261e..2adee611 100644 --- a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SecretTests.cs +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SecretTests.cs @@ -47,16 +47,16 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults() }); // Act. - target.Write(() => new PinnedMemory(valueTwo)); + target.WriteAsync(() => new PinnedMemory(valueTwo)).Wait(); // Assert. hashCode.Should().NotBe(target.GetHashCode()); hashCode = target.GetHashCode(); hashCode.Should().Be(target.GetHashCode()); - target.Read(secret => + target.ReadAsync(secret => { secret.ReadOnlySpan.ToArray().Should().BeEquivalentTo(valueTwo); - }); + }).Wait(); // Act. target.Write(() => new PinnedMemory(valueThree)); diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/SecureMemoryTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/SecureMemoryTests.cs index cbe0b197..964f3d4e 100644 --- a/test/RapidField.SolidInstruments.Cryptography.UnitTests/SecureMemoryTests.cs +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/SecureMemoryTests.cs @@ -85,6 +85,16 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults() memory[4].Should().Be(0x04); memory[8].Should().Be(0x08); }); + + // Act. + target.AccessAsync(memory => + { + // Assert. + memory.Length.Should().Be(length); + memory[0].Should().Be(0x00); + memory[4].Should().Be(0x04); + memory[8].Should().Be(0x08); + }).Wait(); } // Assert. @@ -148,6 +158,61 @@ public void GenerateHardenedRandomBytes_ShouldYieldHighEntropy() } } + [TestMethod] + public void RegeneratePrivateKey_ShouldProduceDesiredResults() + { + // Arrange. + var length = 32; + var referenceByte = (Byte)0x3f; + var referenceBytePosition = 21; + + using (var target = new SecureMemory(length)) + { + // Arrange. + using (var privateKeyOne = target.DerivePrivateKey()) + { + target.Access(memory => + { + // Arrange. + memory[referenceBytePosition] = referenceByte; + }); + + // Act. + target.RegeneratePrivateKey(); + + using (var privateKeyTwo = target.DerivePrivateKey()) + { + target.Access(memory => + { + // Assert. + memory[referenceBytePosition].Should().Be(referenceByte); + memory[referenceBytePosition - 1].Should().Be(0x00); + memory[referenceBytePosition + 1].Should().Be(0x00); + }); + + // Act. + target.RegeneratePrivateKey(); + + using (var privateKeyThree = target.DerivePrivateKey()) + { + target.Access(memory => + { + // Assert. + memory[referenceBytePosition].Should().Be(referenceByte); + memory[referenceBytePosition - 1].Should().Be(0x00); + memory[referenceBytePosition + 1].Should().Be(0x00); + }); + + // Assert. + privateKeyOne.Should().NotBeEquivalentTo(privateKeyTwo); + privateKeyOne.Should().NotBeEquivalentTo(privateKeyThree); + privateKeyTwo.Should().NotBeEquivalentTo(privateKeyThree); + } + } + } + } + } + [TestMethod] public void ShouldNotLeakMemory() { diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/CascadingSymmetricKeyTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/CascadingSymmetricKeyTests.cs index f391bae6..58a87b50 100644 --- a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/CascadingSymmetricKeyTests.cs +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/CascadingSymmetricKeyTests.cs @@ -10,7 +10,6 @@ using System; using System.Linq; using System.Security.Cryptography; -using System.Threading.Tasks; namespace RapidField.SolidInstruments.Cryptography.UnitTests.Symmetric { @@ -232,7 +231,7 @@ public void StressTest_ShouldProduceDesiredResults() for (var repititionIndex = 0; repititionIndex < repititionCount; repititionIndex++) { // Assert. - controlToken.AttachTask(() => ToBuffer_ShouldBeReversible(target)); + controlToken.AttachTask(() => ToSecureMemory_ShouldBeReversible(target)); } } } @@ -240,7 +239,7 @@ public void StressTest_ShouldProduceDesiredResults() } [TestMethod] - public void ToBuffer_ShouldBeReversible_WithFourLayers() + public void ToSecureMemory_ShouldBeReversible_WithFourLayers() { // Arrange. var derivationMode = SymmetricKeyDerivationMode.Truncation; @@ -252,12 +251,12 @@ public void ToBuffer_ShouldBeReversible_WithFourLayers() using (var target = CascadingSymmetricKey.New(derivationMode, firstLayerAlgorithm, secondLayerAlgorithm, thirdLayerAlgorithm, fourthLayerAlgorithm)) { // Assert. - ToBuffer_ShouldBeReversible(target); + ToSecureMemory_ShouldBeReversible(target); } } [TestMethod] - public void ToBuffer_ShouldBeReversible_WithThreeLayers() + public void ToSecureMemory_ShouldBeReversible_WithThreeLayers() { // Arrange. var derivationMode = SymmetricKeyDerivationMode.Truncation; @@ -268,12 +267,12 @@ public void ToBuffer_ShouldBeReversible_WithThreeLayers() using (var target = CascadingSymmetricKey.New(derivationMode, firstLayerAlgorithm, secondLayerAlgorithm, thirdLayerAlgorithm)) { // Assert. - ToBuffer_ShouldBeReversible(target); + ToSecureMemory_ShouldBeReversible(target); } } [TestMethod] - public void ToBuffer_ShouldBeReversible_WithTwoLayers() + public void ToSecureMemory_ShouldBeReversible_WithTwoLayers() { // Arrange. var derivationMode = SymmetricKeyDerivationMode.Truncation; @@ -283,12 +282,12 @@ public void ToBuffer_ShouldBeReversible_WithTwoLayers() using (var target = CascadingSymmetricKey.New(derivationMode, firstLayerAlgorithm, secondLayerAlgorithm)) { // Assert. - ToBuffer_ShouldBeReversible(target); + ToSecureMemory_ShouldBeReversible(target); } } [TestMethod] - public void ToBuffer_ShouldReturnValidResult() + public void ToSecureMemory_ShouldReturnValidResult() { // Arrange. var cascadingKeyLengthInBytes = 1666; @@ -301,9 +300,9 @@ public void ToBuffer_ShouldReturnValidResult() using (var target = CascadingSymmetricKey.New(derivationMode, firstLayerAlgorithm, secondLayerAlgorithm, thirdLayerAlgorithm, fourthLayerAlgorithm)) { // Act. - using (var buffer = target.ToBuffer()) + using (var secureMemory = target.ToSecureMemory()) { - buffer.Access((plaintext => + secureMemory.Access((plaintext => { // Assert. plaintext.Should().NotBeNullOrEmpty(); @@ -314,7 +313,7 @@ public void ToBuffer_ShouldReturnValidResult() } } - private static void ToBuffer_ShouldBeReversible(CascadingSymmetricKey target) + private static void ToSecureMemory_ShouldBeReversible(CascadingSymmetricKey target) { using (var randomnessProvider = RandomNumberGenerator.Create()) { @@ -326,9 +325,9 @@ private static void ToBuffer_ShouldBeReversible(CascadingSymmetricKey target) var ciphertext = processor.Encrypt(plaintextObject, target); // Act. - using (var buffer = target.ToBuffer()) + using (var secureMemory = target.ToSecureMemory()) { - using (var parityKey = CascadingSymmetricKey.FromBuffer(buffer)) + using (var parityKey = CascadingSymmetricKey.FromSecureMemory(secureMemory)) { // Arrange. var result = processor.Decrypt(ciphertext, parityKey); diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/SymmetricBinaryProcessorTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/SymmetricBinaryProcessorTests.cs deleted file mode 100644 index 51792b46..00000000 --- a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/SymmetricBinaryProcessorTests.cs +++ /dev/null @@ -1,86 +0,0 @@ -// ================================================================================================================================= -// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. -// ================================================================================================================================= - -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using RapidField.SolidInstruments.Cryptography.Symmetric; -using System; -using System.Linq; -using System.Security.Cryptography; - -namespace RapidField.SolidInstruments.Cryptography.UnitTests.Symmetric -{ - [TestClass] - public class SymmetricBinaryProcessorTests - { - [TestMethod] - public void Encrypt_ShouldReproduceTestVector_ForAes128Ecb() - { - // Arrange. - var algorithm = SymmetricAlgorithmSpecification.Aes128Ecb; - var blockLengthInBits = 128; -#pragma warning disable SecurityIntelliSenseCS - var cipherMode = CipherMode.ECB; -#pragma warning restore SecurityIntelliSenseCS - var key = new Byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f }; - var initializationVector = (Byte[])null; - var plaintext = new Byte[] { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff }; - var ciphertext = new Byte[] { 0x69, 0xc4, 0xe0, 0xd8, 0x6a, 0x7b, 0x04, 0x30, 0xd8, 0xcd, 0xb7, 0x80, 0x70, 0xb4, 0xc5, 0x5a }; - - // Assert. - Encrypt_ShouldReproduceTestVector(algorithm, blockLengthInBits, cipherMode, key, initializationVector, plaintext, ciphertext); - } - - [TestMethod] - public void Encrypt_ShouldReproduceTestVector_ForAes256Cbc() - { - // Arrange. - var algorithm = SymmetricAlgorithmSpecification.Aes256Cbc; - var blockLengthInBits = 128; - var cipherMode = CipherMode.CBC; - var key = new Byte[] { 0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe, 0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, 0x81, 0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7, 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4 }; - var initializationVector = new Byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f }; - var plaintext = new Byte[] { 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a, 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51, 0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef, 0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10 }; - var ciphertext = new Byte[] { 0xf5, 0x8c, 0x4c, 0x04, 0xd6, 0xe5, 0xf1, 0xba, 0x77, 0x9e, 0xab, 0xfb, 0x5f, 0x7b, 0xfb, 0xd6, 0x9c, 0xfc, 0x4e, 0x96, 0x7e, 0xdb, 0x80, 0x8d, 0x67, 0x9f, 0x77, 0x7b, 0xc6, 0x70, 0x2c, 0x7d, 0x39, 0xf2, 0x33, 0x69, 0xa9, 0xd9, 0xba, 0xcf, 0xa5, 0x30, 0xe2, 0x63, 0x04, 0x23, 0x14, 0x61, 0xb2, 0xeb, 0x05, 0xe2, 0xc3, 0x9b, 0xe9, 0xfc, 0xda, 0x6c, 0x19, 0x07, 0x8c, 0x6a, 0x9d, 0x1b }; - - // Assert. - Encrypt_ShouldReproduceTestVector(algorithm, blockLengthInBits, cipherMode, key, initializationVector, plaintext, ciphertext); - } - - private static void Encrypt_ShouldReproduceTestVector(SymmetricAlgorithmSpecification algorithm, Int32 blockLengthInBits, CipherMode cipherMode, Byte[] key, Byte[] initializationVector, Byte[] plaintext, Byte[] ciphertext) - { - using (var randomnessProvider = RandomNumberGenerator.Create()) - { - using (var secureKeyBuffer = new SecureMemory(key.Length)) - { - // Arrange. - var blockLengthInBytes = (blockLengthInBits / 8); - var target = new SymmetricBinaryProcessor(randomnessProvider); - secureKeyBuffer.Access(keyBuffer => - { - Array.Copy(key, keyBuffer, key.Length); - }); - - // Act. - var encryptResult = target.Encrypt(plaintext, secureKeyBuffer, algorithm, initializationVector); - - // Assert. - encryptResult.Should().NotBeNullOrEmpty(); - - // Arrange. - var processedEncryptResult = cipherMode == CipherMode.CBC ? encryptResult.Skip(blockLengthInBytes).Take(ciphertext.Length).ToArray() : encryptResult.Take(ciphertext.Length).ToArray(); - - // Assert. - processedEncryptResult.Length.Should().Be(ciphertext.Length); - - for (var i = 0; i < ciphertext.Length; i++) - { - // Assert. - processedEncryptResult[i].Should().Be(ciphertext[i]); - } - } - } - } - } -} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/SymmetricKeyTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/SymmetricKeyTests.cs index 14533f06..b17c2468 100644 --- a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/SymmetricKeyTests.cs +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/SymmetricKeyTests.cs @@ -251,7 +251,47 @@ public void New_ShouldReturnValidKey_ForValidArguments() } [TestMethod] - public void ToBuffer_ShouldBeReversible() + public void ToDerivedKeyBytes_ShouldReturnValidResult_ForAes128Cbc() + { + // Arrange. + var algorithm = SymmetricAlgorithmSpecification.Aes128Cbc; + + // Assert. + ToDerivedKeyBytes_ShouldReturnValidResult(algorithm); + } + + [TestMethod] + public void ToDerivedKeyBytes_ShouldReturnValidResult_ForAes128Ecb() + { + // Arrange. + var algorithm = SymmetricAlgorithmSpecification.Aes128Ecb; + + // Assert. + ToDerivedKeyBytes_ShouldReturnValidResult(algorithm); + } + + [TestMethod] + public void ToDerivedKeyBytes_ShouldReturnValidResult_ForAes256Cbc() + { + // Arrange. + var algorithm = SymmetricAlgorithmSpecification.Aes256Cbc; + + // Assert. + ToDerivedKeyBytes_ShouldReturnValidResult(algorithm); + } + + [TestMethod] + public void ToDerivedKeyBytes_ShouldReturnValidResult_ForAes256Ecb() + { + // Arrange. + var algorithm = SymmetricAlgorithmSpecification.Aes256Ecb; + + // Assert. + ToDerivedKeyBytes_ShouldReturnValidResult(algorithm); + } + + [TestMethod] + public void ToSecureMemory_ShouldBeReversible() { using (var randomnessProvider = RandomNumberGenerator.Create()) { @@ -265,9 +305,9 @@ public void ToBuffer_ShouldBeReversible() var ciphertext = processor.Encrypt(plaintextObject, target); // Act. - using (var buffer = target.ToBuffer()) + using (var secureMemory = target.ToSecureMemory()) { - using (var parityKey = SymmetricKey.FromBuffer(buffer)) + using (var parityKey = SymmetricKey.FromSecureMemory(secureMemory)) { // Arrange. var result = processor.Decrypt(ciphertext, parityKey); @@ -281,7 +321,7 @@ public void ToBuffer_ShouldBeReversible() } [TestMethod] - public void ToBuffer_ShouldReturnValidResult() + public void ToSecureMemory_ShouldReturnValidResult() { // Arrange. var keyLengthInBytes = 416; @@ -289,9 +329,9 @@ public void ToBuffer_ShouldReturnValidResult() using (var target = SymmetricKey.New()) { // Act. - using (var buffer = target.ToBuffer()) + using (var secureMemory = target.ToSecureMemory()) { - buffer.Access(plaintext => + secureMemory.Access(plaintext => { // Assert. plaintext.Should().NotBeNullOrEmpty(); @@ -302,46 +342,6 @@ public void ToBuffer_ShouldReturnValidResult() } } - [TestMethod] - public void ToDerivedKeyBytes_ShouldReturnValidResult_ForAes128Cbc() - { - // Arrange. - var algorithm = SymmetricAlgorithmSpecification.Aes128Cbc; - - // Assert. - ToDerivedKeyBytes_ShouldReturnValidResult(algorithm); - } - - [TestMethod] - public void ToDerivedKeyBytes_ShouldReturnValidResult_ForAes128Ecb() - { - // Arrange. - var algorithm = SymmetricAlgorithmSpecification.Aes128Ecb; - - // Assert. - ToDerivedKeyBytes_ShouldReturnValidResult(algorithm); - } - - [TestMethod] - public void ToDerivedKeyBytes_ShouldReturnValidResult_ForAes256Cbc() - { - // Arrange. - var algorithm = SymmetricAlgorithmSpecification.Aes256Cbc; - - // Assert. - ToDerivedKeyBytes_ShouldReturnValidResult(algorithm); - } - - [TestMethod] - public void ToDerivedKeyBytes_ShouldReturnValidResult_ForAes256Ecb() - { - // Arrange. - var algorithm = SymmetricAlgorithmSpecification.Aes256Ecb; - - // Assert. - ToDerivedKeyBytes_ShouldReturnValidResult(algorithm); - } - private static void ToDerivedKeyBytes_ShouldReturnValidResult(SymmetricAlgorithmSpecification algorithm) { using (var target = SymmetricKey.New(algorithm)) diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/SymmetricProcessorTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/SymmetricProcessorTests.cs index abd1c44a..3ef356d2 100644 --- a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/SymmetricProcessorTests.cs +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/SymmetricProcessorTests.cs @@ -432,13 +432,47 @@ public void Encrypt_ShouldBeReversible_UsingSymmetricKey_ForAes256Ecb_InXorLayer Encrypt_ShouldBeReversible_UsingSymmetricKey(algorithm, derivationMode); } + [TestMethod] + public void Encrypt_ShouldReproduceTestVector_ForAes128Ecb() + { + // Arrange. + var algorithm = SymmetricAlgorithmSpecification.Aes128Ecb; + var blockLengthInBits = 128; +#pragma warning disable SecurityIntelliSenseCS + var cipherMode = CipherMode.ECB; +#pragma warning restore SecurityIntelliSenseCS + var key = new Byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f }; + var initializationVector = (Byte[])null; + var plaintext = new Byte[] { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff }; + var ciphertext = new Byte[] { 0x69, 0xc4, 0xe0, 0xd8, 0x6a, 0x7b, 0x04, 0x30, 0xd8, 0xcd, 0xb7, 0x80, 0x70, 0xb4, 0xc5, 0x5a }; + + // Assert. + Encrypt_ShouldReproduceTestVector(algorithm, blockLengthInBits, cipherMode, key, initializationVector, plaintext, ciphertext); + } + + [TestMethod] + public void Encrypt_ShouldReproduceTestVector_ForAes256Cbc() + { + // Arrange. + var algorithm = SymmetricAlgorithmSpecification.Aes256Cbc; + var blockLengthInBits = 128; + var cipherMode = CipherMode.CBC; + var key = new Byte[] { 0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe, 0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, 0x81, 0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7, 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4 }; + var initializationVector = new Byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f }; + var plaintext = new Byte[] { 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a, 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51, 0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef, 0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10 }; + var ciphertext = new Byte[] { 0xf5, 0x8c, 0x4c, 0x04, 0xd6, 0xe5, 0xf1, 0xba, 0x77, 0x9e, 0xab, 0xfb, 0x5f, 0x7b, 0xfb, 0xd6, 0x9c, 0xfc, 0x4e, 0x96, 0x7e, 0xdb, 0x80, 0x8d, 0x67, 0x9f, 0x77, 0x7b, 0xc6, 0x70, 0x2c, 0x7d, 0x39, 0xf2, 0x33, 0x69, 0xa9, 0xd9, 0xba, 0xcf, 0xa5, 0x30, 0xe2, 0x63, 0x04, 0x23, 0x14, 0x61, 0xb2, 0xeb, 0x05, 0xe2, 0xc3, 0x9b, 0xe9, 0xfc, 0xda, 0x6c, 0x19, 0x07, 0x8c, 0x6a, 0x9d, 0x1b }; + + // Assert. + Encrypt_ShouldReproduceTestVector(algorithm, blockLengthInBits, cipherMode, key, initializationVector, plaintext, ciphertext); + } + private static void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey(CascadingSymmetricKey key) { using (var randomnessProvider = RandomNumberGenerator.Create()) { // Arrange. - var binarySerializer = new PassThroughSerializer(); - var target = new SymmetricProcessor(randomnessProvider, binarySerializer); + var serializer = new PassThroughSerializer(); + var target = new SymmetricProcessor(randomnessProvider, serializer); var plaintextObject = new Byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 }; // Act. @@ -493,8 +527,8 @@ private static void Encrypt_ShouldBeReversible_UsingSymmetricKey(SymmetricAlgori using (var randomnessProvider = RandomNumberGenerator.Create()) { // Arrange. - var binarySerializer = new PassThroughSerializer(); - var target = new SymmetricProcessor(randomnessProvider, binarySerializer); + var serializer = new PassThroughSerializer(); + var target = new SymmetricProcessor(randomnessProvider, serializer); var plaintextObject = new Byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 }; using (var key = SymmetricKey.New(algorithm, derivationMode)) @@ -522,5 +556,40 @@ private static void Encrypt_ShouldBeReversible_UsingSymmetricKey(SymmetricAlgori } } } + + private static void Encrypt_ShouldReproduceTestVector(SymmetricAlgorithmSpecification algorithm, Int32 blockLengthInBits, CipherMode cipherMode, Byte[] key, Byte[] initializationVector, Byte[] plaintext, Byte[] ciphertext) + { + using (var randomnessProvider = RandomNumberGenerator.Create()) + { + using (var secureKeyMemory = new SecureMemory(key.Length)) + { + // Arrange. + var blockLengthInBytes = (blockLengthInBits / 8); + var target = new SymmetricProcessor(randomnessProvider); + secureKeyMemory.Access(keyMemory => + { + Array.Copy(key, keyMemory, key.Length); + }); + + // Act. + var encryptResult = target.Encrypt(plaintext, secureKeyMemory, algorithm, initializationVector); + + // Assert. + encryptResult.Should().NotBeNullOrEmpty(); + + // Arrange. + var processedEncryptResult = cipherMode == CipherMode.CBC ? encryptResult.Skip(blockLengthInBytes).Take(ciphertext.Length).ToArray() : encryptResult.Take(ciphertext.Length).ToArray(); + + // Assert. + processedEncryptResult.Length.Should().Be(ciphertext.Length); + + for (var i = 0; i < ciphertext.Length; i++) + { + // Assert. + processedEncryptResult[i].Should().Be(ciphertext[i]); + } + } + } + } } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Serialization.UnitTests/Base64SerializerTests.cs b/test/RapidField.SolidInstruments.Serialization.UnitTests/Base64SerializerTests.cs index 89e643bf..8f9a7d45 100644 --- a/test/RapidField.SolidInstruments.Serialization.UnitTests/Base64SerializerTests.cs +++ b/test/RapidField.SolidInstruments.Serialization.UnitTests/Base64SerializerTests.cs @@ -12,16 +12,16 @@ namespace RapidField.SolidInstruments.Serialization.UnitTests public class Base64SerializerTests { [TestMethod] - public void Deserialize_ShouldRaiseArgumentNullException_ForNullBufferArgument() + public void Deserialize_ShouldRaiseArgumentNullException_ForNullSerializedObjectArgument() { // Arrange. var target = new Base64Serializer(); - var buffer = (Byte[])null; + var serializedObject = (Byte[])null; // Act. var action = new Action(() => { - var result = target.Deserialize(buffer); + var result = target.Deserialize(serializedObject); }); // Assert. diff --git a/test/RapidField.SolidInstruments.Serialization.UnitTests/PassThroughSerializerTests.cs b/test/RapidField.SolidInstruments.Serialization.UnitTests/PassThroughSerializerTests.cs index 1a64c8ea..37b80db7 100644 --- a/test/RapidField.SolidInstruments.Serialization.UnitTests/PassThroughSerializerTests.cs +++ b/test/RapidField.SolidInstruments.Serialization.UnitTests/PassThroughSerializerTests.cs @@ -12,16 +12,16 @@ namespace RapidField.SolidInstruments.Serialization.UnitTests public class PassThroughSerializerTests { [TestMethod] - public void Deserialize_ShouldRaiseArgumentNullException_ForNullBufferArgument() + public void Deserialize_ShouldRaiseArgumentNullException_ForNullSerializedObjectArgument() { // Arrange. var target = new PassThroughSerializer(); - var buffer = (Byte[])null; + var serializedObject = (Byte[])null; // Act. var action = new Action(() => { - var result = target.Deserialize(buffer); + var result = target.Deserialize(serializedObject); }); // Assert. @@ -29,19 +29,19 @@ public void Deserialize_ShouldRaiseArgumentNullException_ForNullBufferArgument() } [TestMethod] - public void Deserialize_ShouldReturnBufferArgument_ForNonNullBufferArgument() + public void Deserialize_ShouldReturnSerializedObjectArgument_ForNonNullSerializedObjectArgument() { // Arrange. var target = new PassThroughSerializer(); - var buffer = new Byte[] { 0x03 }; + var serializedObject = new Byte[] { 0x03 }; // Act. - var result = target.Deserialize(buffer); + var result = target.Deserialize(serializedObject); // Assert. result.Should().NotBeNull(); - result.Length.Should().Be(buffer.Length); - result[0].Should().Be(buffer[0]); + result.Length.Should().Be(serializedObject.Length); + result[0].Should().Be(serializedObject[0]); } [TestMethod] diff --git a/test/RapidField.SolidInstruments.Serialization.UnitTests/UnicodeSerializerTests.cs b/test/RapidField.SolidInstruments.Serialization.UnitTests/UnicodeSerializerTests.cs index 633a4ac3..a6d620cf 100644 --- a/test/RapidField.SolidInstruments.Serialization.UnitTests/UnicodeSerializerTests.cs +++ b/test/RapidField.SolidInstruments.Serialization.UnitTests/UnicodeSerializerTests.cs @@ -13,16 +13,16 @@ namespace RapidField.SolidInstruments.Serialization.UnitTests public class UnicodeSerializerTests { [TestMethod] - public void Deserialize_ShouldRaiseArgumentNullException_ForNullBufferArgument() + public void Deserialize_ShouldRaiseArgumentNullException_ForNullSerializedObjectArgument() { // Arrange. var target = new UnicodeSerializer(); - var buffer = (Byte[])null; + var serializedObject = (Byte[])null; // Act. var action = new Action(() => { - var result = target.Deserialize(buffer); + var result = target.Deserialize(serializedObject); }); // Assert. From dde9b942507078d479ac0966eb3bd03ffa14358a Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Mon, 1 Jun 2020 09:55:18 -0500 Subject: [PATCH 32/55] Adding import/export features to secret vaults. --- .../Extensions/IModelExtensions.cs | 65 ++ .../Secrets/EncryptedExportedSecret.cs | 175 ++++ .../Secrets/ExportedSecret.cs | 231 +++++ .../Secrets/IEncryptedExportedSecret.cs | 71 ++ .../Secrets/IExportedSecret.cs | 72 ++ .../Secrets/ISecretVault.cs | 141 +++ .../Secrets/Secret.cs | 83 +- .../Secrets/SecretVault.cs | 397 +++++++- .../Symmetric/EncryptedModel.cs | 325 +++++++ .../Symmetric/IEncryptedModel.cs | 172 ++++ .../Extensions/IModelExtensionsTests.cs | 58 ++ .../Secrets/ExportedSecretTests.cs | 160 ++++ .../Secrets/SecretVaultTests.cs | 881 +++++++++++++++++- .../SimulatedModel.cs | 181 ++++ .../Symmetric/EncryptedModelTests.cs | 210 +++++ 15 files changed, 3159 insertions(+), 63 deletions(-) create mode 100644 src/RapidField.SolidInstruments.Cryptography/Extensions/IModelExtensions.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Secrets/EncryptedExportedSecret.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Secrets/ExportedSecret.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Secrets/IEncryptedExportedSecret.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Secrets/IExportedSecret.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Symmetric/EncryptedModel.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Symmetric/IEncryptedModel.cs create mode 100644 test/RapidField.SolidInstruments.Cryptography.UnitTests/Extensions/IModelExtensionsTests.cs create mode 100644 test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/ExportedSecretTests.cs create mode 100644 test/RapidField.SolidInstruments.Cryptography.UnitTests/SimulatedModel.cs create mode 100644 test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/EncryptedModelTests.cs diff --git a/src/RapidField.SolidInstruments.Cryptography/Extensions/IModelExtensions.cs b/src/RapidField.SolidInstruments.Cryptography/Extensions/IModelExtensions.cs new file mode 100644 index 00000000..c61558a1 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Extensions/IModelExtensions.cs @@ -0,0 +1,65 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Cryptography.Symmetric; +using System; +using System.Security; + +namespace RapidField.SolidInstruments.Cryptography.Extensions +{ + /// + /// Extends the interface with cryptographic features. + /// + public static class IModelExtensions + { + /// + /// Encrypts the current using the specified key. + /// + /// + /// The type of the current . + /// + /// + /// The current . + /// + /// + /// The symmetric key to use when encrypting the model. + /// + /// + /// The symmetrically encrypted model. + /// + /// + /// is . + /// + /// + /// An exception was raised during encryption or serialization. + /// + public static IEncryptedModel Encrypt(this TModel target, ISymmetricKey key) + where TModel : class, IModel => EncryptedModel.FromPlaintextModel(target, key); + + /// + /// Encrypts the current using the specified key. + /// + /// + /// The type of the current . + /// + /// + /// The current . + /// + /// + /// The symmetric key to use when encrypting the model. + /// + /// + /// The symmetrically encrypted model. + /// + /// + /// is . + /// + /// + /// An exception was raised during encryption or serialization. + /// + public static IEncryptedModel Encrypt(this TModel target, ICascadingSymmetricKey key) + where TModel : class, IModel => EncryptedModel.FromPlaintextModel(target, key); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/EncryptedExportedSecret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/EncryptedExportedSecret.cs new file mode 100644 index 00000000..cbbedbe5 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/EncryptedExportedSecret.cs @@ -0,0 +1,175 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Cryptography.Symmetric; +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.Serialization; +using System.Security; + +namespace RapidField.SolidInstruments.Cryptography.Secrets +{ + /// + /// Represents a symmetrically encrypted . + /// + /// + /// is the default implementation of . + /// + [DataContract] + public sealed class EncryptedExportedSecret : EncryptedModel, IEncryptedExportedSecret + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The exported secret which is encrypted and wrapped by the model. + /// + /// + /// The key that is used to encrypt . + /// + /// + /// The name of the secret associated with . + /// + /// + /// is empty. + /// + /// + /// is -or- is + /// -or- is . + /// + /// + /// An exception was raised during encryption or serialization. + /// + [DebuggerHidden] + internal EncryptedExportedSecret(ExportedSecret exportedSecret, ISymmetricKey key, String keyName) + : this(SymmetricProcessor.ForType().Encrypt(exportedSecret.RejectIf().IsNull(nameof(exportedSecret)), key.RejectIf().IsNull(nameof(key)).TargetArgument), keyName) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The exported secret which is encrypted and wrapped by the model. + /// + /// + /// The key that is used to encrypt . + /// + /// + /// The name of the secret associated with . + /// + /// + /// is empty. + /// + /// + /// is -or- is + /// -or- is . + /// + /// + /// An exception was raised during encryption or serialization. + /// + [DebuggerHidden] + internal EncryptedExportedSecret(ExportedSecret exportedSecret, ICascadingSymmetricKey key, String keyName) + : this(SymmetricProcessor.ForType().Encrypt(exportedSecret.RejectIf().IsNull(nameof(exportedSecret)), key.RejectIf().IsNull(nameof(key)).TargetArgument), keyName) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The ciphertext of the exported secret. + /// + /// + /// The name of the secret associated with the key that was used to produce . + /// + /// + /// is empty. + /// + /// + /// is + /// -or- is . + /// + [DebuggerHidden] + private EncryptedExportedSecret(Byte[] ciphertext, String keyName) + : base(ciphertext) + { + KeyName = keyName.RejectIf().IsNullOrEmpty(nameof(keyName)); + } + + /// + /// Decrypts and reconstitutes the secret. + /// + /// + /// The key that is used to decrypt the exported secret. + /// + /// + /// The plaintext secret. + /// + /// + /// is . + /// + /// + /// The type assembly or one of its dependencies is invalid. + /// + /// + /// The type assembly could not be loaded. + /// + /// + /// An exception was raised during decryption or deserialization. + /// + public IReadOnlySecret ToPlaintextSecret(ISymmetricKey key) => ToPlaintextModel(key).ToSecret(); + + /// + /// Decrypts and reconstitutes the secret. + /// + /// + /// The key that is used to decrypt the exported secret. + /// + /// + /// The plaintext secret. + /// + /// + /// is . + /// + /// + /// The type assembly or one of its dependencies is invalid. + /// + /// + /// The type assembly could not be loaded. + /// + /// + /// An exception was raised during decryption or deserialization. + /// + public IReadOnlySecret ToPlaintextSecret(ICascadingSymmetricKey key) => ToPlaintextModel(key).ToSecret(); + + /// + /// Gets or sets the name of the secret associated with the key that was used to encrypt the exported secret. + /// + /// + /// The specified string is empty. + /// + /// + /// The specified string is . + /// + [DataMember] + public String KeyName + { + get => KeyNameReference; + set => KeyNameReference = value.RejectIf().IsNullOrEmpty(nameof(KeyName)); + } + + /// + /// Represents the name of the secret associated with the key that was used to encrypt the exported secret. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + [IgnoreDataMember] + private String KeyNameReference; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/ExportedSecret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/ExportedSecret.cs new file mode 100644 index 00000000..e6da2f2a --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/ExportedSecret.cs @@ -0,0 +1,231 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Core.Extensions; +using RapidField.SolidInstruments.Cryptography.Symmetric; +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.Serialization; +using System.Security.Cryptography.X509Certificates; + +namespace RapidField.SolidInstruments.Cryptography.Secrets +{ + /// + /// Represents a serializable that was exported from an . + /// + /// + /// is the default implementation of . + /// + [DataContract] + public sealed class ExportedSecret : Model, IExportedSecret + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated secret. + /// + /// + /// is . + /// + /// + /// is disposed. + /// + [DebuggerHidden] + internal ExportedSecret(IReadOnlySecret secret) + : base() + { + Name = secret.RejectIf().IsNull(nameof(secret)).TargetArgument.Name; + ValueTypeAssemblyQualifiedName = secret.ValueType.AssemblyQualifiedName; + secret.Read(secretMemory => + { + Value = new Byte[secretMemory.LengthInBytes]; + secretMemory.ReadOnlySpan.CopyTo(Value); + }); + } + + /// + /// Converts the current to its representation. + /// + /// + /// The representation of the current . + /// + public IReadOnlySecret ToSecret() + { + if (ValueType == typeof(CascadingSymmetricKey)) + { + return HydrateSecret(new CascadingSymmetricKeySecret(Name)); + } + else if (ValueType == typeof(Guid)) + { + return HydrateSecret(new GuidSecret(Name)); + } + else if (ValueType == typeof(Double)) + { + return HydrateSecret(new NumericSecret(Name)); + } + else if (ValueType == typeof(String)) + { + return HydrateSecret(new StringSecret(Name)); + } + else if (ValueType == typeof(SymmetricKey)) + { + return HydrateSecret(new SymmetricKeySecret(Name)); + } + else if (ValueType == typeof(X509Certificate2)) + { + return HydrateSecret(new X509CertificateSecret(Name)); + } + + return HydrateSecret(new Secret(Name)); + } + + /// + /// Overwrites the value bytes with zeros, if any, and sets equal to a + /// reference as an idempotent operation. + /// + [DebuggerHidden] + internal void ClearValue() + { + if (Value is null) + { + return; + } + + var valueLength = Value?.Length ?? 0; + + for (var i = 0; i < valueLength; i++) + { + Value[i] = 0x00; + } + + Value = null; + } + + /// + /// Writes to the specified secret and returns it. + /// + /// + /// The secret value type. + /// + /// + /// The secret. + /// + /// + /// The specified, hydrated secret. + /// + [DebuggerHidden] + private IReadOnlySecret HydrateSecret(Secret secret) + { + if (HasValue) + { + secret?.Write(Value); + } + + return secret; + } + + /// + /// Gets or sets a value indicating whether or not the secret has a value. + /// + [DataMember] + public Boolean HasValue + { + get => Value is null == false; + set + { + if (value && Value is null) + { + Value = Array.Empty(); + return; + } + + ClearValue(); + } + } + + /// + /// Gets or sets a textual name that uniquely identifies the secret. + /// + /// + /// The specified string is empty. + /// + /// + /// The specified string is . + /// + [DataMember] + public String Name + { + get => NameReference; + set => NameReference = value.RejectIf().IsNullOrEmpty(nameof(Name)); + } + + /// + /// Gets or sets the secret value as a Base64 string, or if is + /// . + /// + /// + /// The specified string is not a valid Base64 string. + /// + [DataMember] + public String PlaintextValue + { + get => HasValue ? Convert.ToBase64String(Value) : null; + set => Value = value is null ? null : Convert.FromBase64String(value); + } + + /// + /// Gets the type of the secret value, or if is not a + /// valid, assembly-qualified type name. + /// + /// + /// The type assembly or one of its dependencies is invalid. + /// + /// + /// The type assembly could not be loaded. + /// + [IgnoreDataMember] + public Type ValueType => ValueTypeAssemblyQualifiedName.IsNullOrEmpty() ? null : Type.GetType(ValueTypeAssemblyQualifiedName, false, true); + + /// + /// Gets or sets the assembly-qualified name of the type of the secret value. + /// + /// + /// The specified string is empty. + /// + /// + /// The specified string is . + /// + [DataMember] + public String ValueTypeAssemblyQualifiedName + { + get => ValueTypeAssemblyQualifiedNameReference; + set => ValueTypeAssemblyQualifiedNameReference = value.RejectIf().IsNullOrEmpty(nameof(ValueTypeAssemblyQualifiedName)); + } + + /// + /// Represents a textual name that uniquely identifies the secret. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + [IgnoreDataMember] + private String NameReference; + + /// + /// Represents the secret value, or an empty array if is . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + [IgnoreDataMember] + private Byte[] Value = null; + + /// + /// Represents the assembly-qualified name of the type of the secret value. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + [IgnoreDataMember] + private String ValueTypeAssemblyQualifiedNameReference; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/IEncryptedExportedSecret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/IEncryptedExportedSecret.cs new file mode 100644 index 00000000..62bee2dd --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/IEncryptedExportedSecret.cs @@ -0,0 +1,71 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Cryptography.Symmetric; +using System; +using System.IO; +using System.Security; + +namespace RapidField.SolidInstruments.Cryptography.Secrets +{ + /// + /// Represents a symmetrically encrypted . + /// + public interface IEncryptedExportedSecret : IEncryptedModel + { + /// + /// Decrypts and reconstitutes the secret. + /// + /// + /// The key that is used to decrypt the exported secret. + /// + /// + /// The plaintext secret. + /// + /// + /// is . + /// + /// + /// The type assembly or one of its dependencies is invalid. + /// + /// + /// The type assembly could not be loaded. + /// + /// + /// An exception was raised during decryption or deserialization. + /// + public IReadOnlySecret ToPlaintextSecret(ISymmetricKey key); + + /// + /// Decrypts and reconstitutes the secret. + /// + /// + /// The key that is used to decrypt the exported secret. + /// + /// + /// The plaintext secret. + /// + /// + /// is . + /// + /// + /// The type assembly or one of its dependencies is invalid. + /// + /// + /// The type assembly could not be loaded. + /// + /// + /// An exception was raised during decryption or deserialization. + /// + public IReadOnlySecret ToPlaintextSecret(ICascadingSymmetricKey key); + + /// + /// Gets the name of the secret associated with the key that was used to encrypt the exported secret. + /// + public String KeyName + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/IExportedSecret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/IExportedSecret.cs new file mode 100644 index 00000000..0ec10fd3 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/IExportedSecret.cs @@ -0,0 +1,72 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using System; +using System.IO; + +namespace RapidField.SolidInstruments.Cryptography.Secrets +{ + /// + /// Represents a serializable that was exported from an . + /// + public interface IExportedSecret : IModel + { + /// + /// Converts the current to its representation. + /// + /// + /// The representation of the current . + /// + public IReadOnlySecret ToSecret(); + + /// + /// Gets a value indicating whether or not the secret has a value. + /// + public Boolean HasValue + { + get; + } + + /// + /// Gets a textual name that uniquely identifies the secret. + /// + public String Name + { + get; + } + + /// + /// Gets the secret value as a Base64 string, or if is + /// . + /// + public String PlaintextValue + { + get; + } + + /// + /// Gets the type of the secret value, or if is not a + /// valid, assembly-qualified type name. + /// + /// + /// The type assembly or one of its dependencies is invalid. + /// + /// + /// The type assembly could not be loaded. + /// + public Type ValueType + { + get; + } + + /// + /// Gets the assembly-qualified name of the type of the secret value. + /// + public String ValueTypeAssemblyQualifiedName + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretVault.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretVault.cs index 4378bf92..b57e9029 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretVault.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretVault.cs @@ -172,6 +172,147 @@ public interface ISecretVault : IAsyncDisposable, IDisposable /// public void Clear(); + /// + /// Asynchronously exports the specified secret and encrypts it using the vault's master key. + /// + /// + /// The textual name of the secret to export. + /// + /// + /// A task representing the asynchronous operation and containing the exported encrypted secret. + /// + /// + /// is empty. + /// + /// + /// The secret vault does not contain a secret with the specified name. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during encryption or serialization. + /// + public Task ExportEncryptedSecretAsync(String name); + + /// + /// Asynchronously exports the specified secret and encrypts it using the specified key. + /// + /// + /// The textual name of the secret to export. + /// + /// + /// The name of the secret associated with a key that is used to encrypt the exported secret. + /// + /// + /// A task representing the asynchronous operation and containing the exported encrypted secret. + /// + /// + /// is empty -or- is empty. + /// + /// + /// The secret vault does not contain a secret with the specified name -or- the secret vault does not contain a key with the + /// specified name. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during encryption or serialization. + /// + public Task ExportEncryptedSecretAsync(String name, String keyName); + + /// + /// Asynchronously exports the specified secret in plaintext form. + /// + /// + /// The textual name of the secret to export. + /// + /// + /// A task representing the asynchronous operation and containing the exported plaintext secret. + /// + /// + /// is empty. + /// + /// + /// The secret vault does not contain a secret with the specified name. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public Task ExportSecretAsync(String name); + + /// + /// Decrypts the specified secret using the vault's master key and imports it. + /// + /// + /// When using this method, note that the master key of the current must match the key that was + /// used when exporting the secret. If the exporting vault is not the current vault, the master keys will need to have been + /// synchronized beforehand. + /// + /// + /// The encrypted secret to import. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during decryption or deserialization. + /// + public void ImportEncryptedSecret(IEncryptedExportedSecret secret); + + /// + /// Decrypts the specified secret using the specified key and imports it. + /// + /// + /// The encrypted secret to import. + /// + /// + /// The name of the secret associated with a key that was used to encrypt the exported secret. + /// + /// + /// is empty. + /// + /// + /// The secret vault does not contain a key with the specified name. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during decryption or deserialization. + /// + public void ImportEncryptedSecret(IEncryptedExportedSecret secret, String keyName); + + /// + /// Imports the specified secret in plaintext form. + /// + /// + /// The plaintext secret to import. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public void ImportSecret(IExportedSecret secret); + /// /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read /// operation against it as a thread-safe, atomic operation. diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/Secret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/Secret.cs index ca2ae958..eb3ec130 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/Secret.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/Secret.cs @@ -309,6 +309,24 @@ public void Write(Func writeFunction) /// public Task WriteAsync(Func writeFunction) => Task.Factory.StartNew(() => Write(writeFunction)); + /// + /// Writes and encrypts the specified value. + /// + /// + /// The value to write and encrypt. + /// + /// + /// is . + /// + [DebuggerHidden] + internal void Write(Byte[] value) + { + using (var valueMemory = new ReadOnlyPinnedMemory(value)) + { + Write(valueMemory: valueMemory); + } + } + /// /// Creates a using the provided bytes. /// @@ -456,6 +474,45 @@ private void Read(Action readAction, IConcurrencyControlToken controlTok } } + /// + /// Writes and encrypts the specified value memory. + /// + /// + /// The memory to write and encrypt. + /// + /// + /// is . + /// + [DebuggerHidden] + private void Write(IReadOnlyPinnedMemory valueMemory) + { + if (valueMemory.RejectIf().IsNull(nameof(valueMemory)).TargetArgument.IsEmpty) + { + // Secure memory cannot be empty. + SecureValueMemory?.Dispose(); + SecureValueMemory = null; + HasValue = true; + return; + } + + if (SecureValueMemory is null) + { + SecureValueMemory = new SecureMemory(valueMemory.LengthInBytes); + } + else if (SecureValueMemory.LengthInBytes != valueMemory.LengthInBytes) + { + SecureValueMemory.Dispose(); + SecureValueMemory = new SecureMemory(valueMemory.LengthInBytes); + } + + SecureValueMemory.Access(memory => + { + valueMemory.ReadOnlySpan.CopyTo(memory.Span); + }); + + HasValue = true; + } + /// /// Performs the specified write operation and encrypts the resulting value as a thread-safe, atomic operation. /// @@ -485,31 +542,7 @@ private void Write(Func writeFunction, IConcurrencyControlToken controlT using (var valueMemory = ConvertValueToBytes(value, controlToken)) { - if (valueMemory.IsEmpty) - { - // Secure memory cannot be empty. - SecureValueMemory?.Dispose(); - SecureValueMemory = null; - HasValue = true; - return; - } - - if (SecureValueMemory is null) - { - SecureValueMemory = new SecureMemory(valueMemory.LengthInBytes); - } - else if (SecureValueMemory.LengthInBytes != valueMemory.LengthInBytes) - { - SecureValueMemory.Dispose(); - SecureValueMemory = new SecureMemory(valueMemory.LengthInBytes); - } - - SecureValueMemory.Access(memory => - { - valueMemory.ReadOnlySpan.CopyTo(memory.Span); - }); - - HasValue = true; + Write(valueMemory); } } catch (ObjectDisposedException) diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretVault.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretVault.cs index 37c0582e..0716b342 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretVault.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretVault.cs @@ -5,6 +5,7 @@ using RapidField.SolidInstruments.Collections; using RapidField.SolidInstruments.Core; using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Core.Concurrency; using RapidField.SolidInstruments.Core.Extensions; using RapidField.SolidInstruments.Cryptography.Symmetric; using System; @@ -33,6 +34,33 @@ public SecretVault() Secrets = new Dictionary(); } + /// + /// Initializes a new instance of the class. + /// + /// + /// A 13+ character password from which a master key is derived, which is used as the default encryption key for exported + /// secrets. A random master key is generated on demand if the parameterless constructor is used. + /// + /// + /// is empty. + /// + /// + /// is shorter than thirteen characters. + /// + /// + /// is . + /// + public SecretVault(String password) + : this() + { + using (var masterKey = CascadingSymmetricKey.FromPassword(password)) + { + var masterKeySecret = CascadingSymmetricKeySecret.FromValue(MasterKeyName, masterKey); + ReferenceManager.AddObject(masterKeySecret); + Secrets.Add(masterKeySecret.Name, masterKeySecret); + } + } + /// /// Adds the specified secret using the specified name to the current , or updates it if a secret /// with the same name already exists. @@ -213,6 +241,238 @@ public void Clear() } } + /// + /// Asynchronously exports the specified secret and encrypts it using the vault's master key. + /// + /// + /// The textual name of the secret to export. + /// + /// + /// A task representing the asynchronous operation and containing the exported encrypted secret. + /// + /// + /// is empty. + /// + /// + /// The secret vault does not contain a secret with the specified name. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during encryption or serialization. + /// + public Task ExportEncryptedSecretAsync(String name) => ExportEncryptedSecretAsync(name, MasterKeyName); + + /// + /// Asynchronously exports the specified secret and encrypts it using the specified key. + /// + /// + /// The textual name of the secret to export. + /// + /// + /// The name of the secret associated with a key that is used to encrypt the exported secret. + /// + /// + /// A task representing the asynchronous operation and containing the exported encrypted secret. + /// + /// + /// is empty -or- is empty. + /// + /// + /// The secret vault does not contain a secret with the specified name -or- the secret vault does not contain a key with the + /// specified name. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during encryption or serialization. + /// + public async Task ExportEncryptedSecretAsync(String name, String keyName) + { + RejectIfDisposed(); + + if (Secrets.ContainsKey(name.RejectIf().IsNullOrEmpty(nameof(name)))) + { + var exportedSecret = await ExportSecretAsync(name).ConfigureAwait(false); + + try + { + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + + if (Secrets.ContainsKey(keyName.RejectIf().IsNullOrEmpty(nameof(keyName)))) + { + return await ExportEncryptedSecretAsync(exportedSecret, Secrets[keyName]).ConfigureAwait(false); + } + else if (keyName == MasterKeyName) + { + using (var masterKey = CascadingSymmetricKey.New()) + { + var masterKeySecret = CascadingSymmetricKeySecret.FromValue(keyName, masterKey); + AddOrUpdate(MasterKeyName, masterKeySecret, controlToken); + return await ExportEncryptedSecretAsync(exportedSecret, masterKeySecret).ConfigureAwait(false); + } + } + + throw new ArgumentException($"The secret vault does not contain a key with the specified name, \"{keyName}\".", nameof(keyName)); + } + } + finally + { + exportedSecret.ClearValue(); + } + } + + throw new ArgumentException($"The secret vault does not contain a secret with the specified name, \"{name}\".", nameof(name)); + } + + /// + /// Asynchronously exports the specified secret in plaintext form. + /// + /// + /// The textual name of the secret to export. + /// + /// + /// A task representing the asynchronous operation and containing the exported plaintext secret. + /// + /// + /// is empty. + /// + /// + /// The secret vault does not contain a secret with the specified name. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public Task ExportSecretAsync(String name) => Task.Factory.StartNew(() => + { + return ExportSecret(name); + }); + + /// + /// Decrypts the specified secret using the vault's master key and imports it. + /// + /// + /// When using this method, note that the master key of the current must match the key that was + /// used when exporting the secret. If the exporting vault is not the current vault, the master keys will need to have been + /// synchronized beforehand. + /// + /// + /// The encrypted secret to import. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during decryption or deserialization. + /// + public void ImportEncryptedSecret(IEncryptedExportedSecret secret) => ImportEncryptedSecret(secret, MasterKeyName); + + /// + /// Decrypts the specified secret using the specified key and imports it. + /// + /// + /// The encrypted secret to import. + /// + /// + /// The name of the secret associated with a key that was used to encrypt the exported secret. + /// + /// + /// is empty. + /// + /// + /// The secret vault does not contain a key with the specified name. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during decryption or deserialization. + /// + public void ImportEncryptedSecret(IEncryptedExportedSecret secret, String keyName) + { + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + + if (Secrets.ContainsKey(keyName.RejectIf().IsNullOrEmpty(nameof(keyName)))) + { + var keySecret = Secrets[keyName]; + + try + { + if (keySecret.ValueType == typeof(SymmetricKey)) + { + ((SymmetricKeySecret)keySecret).ReadAsync((SymmetricKey key) => + { + ImportSecret(secret.ToPlaintextModel(key), controlToken); + }).Wait(); + return; + } + else if (keySecret.ValueType == typeof(CascadingSymmetricKey)) + { + ((CascadingSymmetricKeySecret)keySecret).ReadAsync((CascadingSymmetricKey key) => + { + ImportSecret(secret.ToPlaintextModel(key), controlToken); + }).Wait(); + return; + } + + throw new ArgumentException($"The specified key name, \"{keyName}\" does not reference a valid key.", nameof(keySecret.Name)); + } + catch (AggregateException exception) + { + throw new SecretAccessException($"The specified key, \"{keyName}\", could not be used to import the specified secret. Decryption or deserialization failed.", exception); + } + } + else if (keyName == MasterKeyName) + { + throw new SecretAccessException("The encrypted secret cannot be imported without specifying an explicit key because the secret vault does not have a master key."); + } + + throw new ArgumentException($"The secret vault does not contain a key with the specified name, \"{keyName}\".", nameof(keyName)); + } + } + + /// + /// Imports the specified secret in plaintext form. + /// + /// + /// The plaintext secret to import. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public void ImportSecret(IExportedSecret secret) + { + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + ImportSecret(secret, controlToken); + } + } + /// /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read /// operation against it as a thread-safe, atomic operation. @@ -527,6 +787,110 @@ protected override void Dispose(Boolean disposing) /// [DebuggerHidden] private void AddOrUpdate(String name, IReadOnlySecret secret) + { + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + AddOrUpdate(name, secret, controlToken); + } + } + + /// + /// Adds the specified secret using the specified name to the current , or updates it if a secret + /// with the same name already exists. + /// + /// + /// A textual name that uniquely identifies . + /// + /// + /// The secret value. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + [DebuggerHidden] + private void AddOrUpdate(String name, IReadOnlySecret secret, IConcurrencyControlToken controlToken) + { + if (Secrets.ContainsKey(name.RejectIf().IsNullOrEmpty(nameof(name)))) + { + if (Secrets.Remove(name, out var oldSecret)) + { + oldSecret?.Dispose(); + } + } + + ReferenceManager.AddObject(secret); + Secrets.Add(name, secret.RejectIf().IsNull(nameof(secret)).TargetArgument); + } + + /// + /// Asynchronously exports the specified secret and encrypts it using the specified key. + /// + /// + /// The secret to export. + /// + /// + /// The key secret that is used to encrypt the exported secret. + /// + /// + /// The specified key secret is not a valid key. + /// + /// + /// An exception was raised during encryption or serialization. + /// + [DebuggerHidden] + private async Task ExportEncryptedSecretAsync(ExportedSecret exportedSecret, IReadOnlySecret keySecret) + { + var encryptedExportedSecret = (EncryptedExportedSecret)null; + var keyName = keySecret.Name; + + if (keySecret.ValueType == typeof(SymmetricKey)) + { + await ((SymmetricKeySecret)keySecret).ReadAsync((SymmetricKey key) => + { + encryptedExportedSecret = new EncryptedExportedSecret(exportedSecret, key, keyName); + }).ConfigureAwait(false); + } + else if (keySecret.ValueType == typeof(CascadingSymmetricKey)) + { + await ((CascadingSymmetricKeySecret)keySecret).ReadAsync((CascadingSymmetricKey key) => + { + encryptedExportedSecret = new EncryptedExportedSecret(exportedSecret, key, keyName); + }).ConfigureAwait(false); + } + + return encryptedExportedSecret ?? throw new ArgumentException($"The specified key name, \"{keyName}\" does not reference a valid key.", nameof(keySecret)); + } + + /// + /// Exports the specified secret. + /// + /// + /// The textual name of the secret to export. + /// + /// + /// The exported plaintext secret. + /// + /// + /// is empty. + /// + /// + /// The secret vault does not contain a secret with the specified name. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + [DebuggerHidden] + private ExportedSecret ExportSecret(String name) { using (var controlToken = StateControl.Enter()) { @@ -534,17 +898,28 @@ private void AddOrUpdate(String name, IReadOnlySecret secret) if (Secrets.ContainsKey(name.RejectIf().IsNullOrEmpty(nameof(name)))) { - if (Secrets.Remove(name, out var oldSecret)) - { - oldSecret?.Dispose(); - } + return new ExportedSecret(Secrets[name]); } - ReferenceManager.AddObject(secret); - Secrets.Add(name, secret.RejectIf().IsNull(nameof(secret)).TargetArgument); + throw new ArgumentException($"The secret vault does not contain a secret with the specified name, \"{name}\".", nameof(name)); } } + /// + /// Imports the specified secret in plaintext form. + /// + /// + /// The plaintext secret to import. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + /// + /// is . + /// + [DebuggerHidden] + private void ImportSecret(IExportedSecret secret, IConcurrencyControlToken controlToken) => AddOrUpdate(secret.RejectIf().IsNull(nameof(secret)).TargetArgument.Name, secret.ToSecret(), controlToken); + /// /// Decrypts the specified named secret, pins a copy of it in memory, and performs the specified read operation against it /// as a thread-safe, atomic operation. @@ -591,10 +966,10 @@ private void Read(String name, Action readAction) return; } - throw new SecretAccessException("The secret vault does not contain a valid secret of the specified type."); + throw new SecretAccessException($"The secret vault does not contain a valid secret of the specified type, \"{typeof(T).FullName}\"."); } - throw new ArgumentException("The secret vault does not contain a secret with the specified name.", nameof(name)); + throw new ArgumentException($"The secret vault does not contain a secret with the specified name, \"{name}\".", nameof(name)); } } @@ -687,6 +1062,12 @@ public IEnumerable Names [DebuggerBrowsable(DebuggerBrowsableState.Never)] private IReferenceManager ReferenceManager => LazyReferenceManager.Value; + /// + /// Represents the name of the master key stored within every instance. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal const String MasterKeyName = "__MasterKey"; + /// /// Represents the lazily-initialized utility that disposes of the secrets that are managed by the current /// . diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/EncryptedModel.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/EncryptedModel.cs new file mode 100644 index 00000000..98f5872d --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Symmetric/EncryptedModel.cs @@ -0,0 +1,325 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Core.Extensions; +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.Serialization; +using System.Security; + +namespace RapidField.SolidInstruments.Cryptography.Symmetric +{ + /// + /// Represents a symmetrically encrypted . + /// + /// + /// is the default implementation of . + /// + /// + /// The type of the plaintext model. + /// + [DataContract] + public class EncryptedModel : EncryptedModel, IEncryptedModel + where TModel : class, IModel + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The ciphertext of the model. + /// + /// + /// is empty. + /// + /// + /// is . + /// + [DebuggerHidden] + internal EncryptedModel(Byte[] ciphertext) + : base(ciphertext, typeof(TModel)) + { + return; + } + + /// + /// Decrypts and returns the plaintext . + /// + /// + /// The symmetric key that was used to encrypt the model. + /// + /// + /// The plaintext . + /// + /// + /// is . + /// + /// + /// The type assembly or one of its dependencies is invalid. + /// + /// + /// The type assembly could not be loaded. + /// + /// + /// An exception was raised during decryption or deserialization. + /// + public TModel ToPlaintextModel(ISymmetricKey key) => ToPlaintextModel(key); + + /// + /// Decrypts and returns the plaintext . + /// + /// + /// The symmetric key that was used to encrypt the model. + /// + /// + /// The plaintext . + /// + /// + /// is . + /// + /// + /// The type assembly or one of its dependencies is invalid. + /// + /// + /// The type assembly could not be loaded. + /// + /// + /// An exception was raised during decryption or deserialization. + /// + public TModel ToPlaintextModel(ICascadingSymmetricKey key) => ToPlaintextModel(key); + + /// + /// Converts the current to a serializable . + /// + /// + /// A serializable representation of the current . + /// + public EncryptedModel ToSerializableModel() => new EncryptedModel(CiphertextBytes, ModelType); + } + + /// + /// Represents a symmetrically encrypted . + /// + /// + /// is the default implementation of . + /// + [DataContract] + public class EncryptedModel : Model, IEncryptedModel + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The ciphertext of the model. + /// + /// + /// The type of the model. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + [DebuggerHidden] + internal EncryptedModel(Byte[] ciphertext, Type modelType) + : base() + { + CiphertextBytes = ciphertext.RejectIf().IsNullOrEmpty(nameof(ciphertext)); + ModelTypeAssemblyQualifiedName = modelType.RejectIf().IsNull(nameof(modelType)).TargetArgument.AssemblyQualifiedName; + } + + /// + /// Encrypts the specified and returns a new . + /// + /// + /// The type of the plaintext model. + /// + /// + /// The plaintext model to encrypt. + /// + /// + /// The symmetric key that is used to encrypt the model. + /// + /// + /// A new . + /// + /// + /// is -or- is . + /// + /// + /// An exception was raised during encryption or serialization. + /// + public static IEncryptedModel FromPlaintextModel(TModel model, ISymmetricKey key) + where TModel : class, IModel => new EncryptedModel(SymmetricProcessor.ForType().Encrypt(model.RejectIf().IsNull(nameof(model)), key.RejectIf().IsNull(nameof(key)).TargetArgument)); + + /// + /// Encrypts the specified and returns a new . + /// + /// + /// The type of the plaintext model. + /// + /// + /// The plaintext model to encrypt. + /// + /// + /// The symmetric key that is used to encrypt the model. + /// + /// + /// A new . + /// + /// + /// is -or- is . + /// + /// + /// An exception was raised during encryption or serialization. + /// + public static IEncryptedModel FromPlaintextModel(TModel model, ICascadingSymmetricKey key) + where TModel : class, IModel => new EncryptedModel(SymmetricProcessor.ForType().Encrypt(model.RejectIf().IsNull(nameof(model)), key.RejectIf().IsNull(nameof(key)).TargetArgument)); + + /// + /// Decrypts and returns the plaintext . + /// + /// + /// The type of the plaintext model. + /// + /// + /// The symmetric key that was used to encrypt the model. + /// + /// + /// The plaintext . + /// + /// + /// does not match the model type of the encrypted model. + /// + /// + /// is . + /// + /// + /// The type assembly or one of its dependencies is invalid. + /// + /// + /// The type assembly could not be loaded. + /// + /// + /// An exception was raised during decryption or deserialization. + /// + public TModel ToPlaintextModel(ISymmetricKey key) + where TModel : class, IModel + { + var modelType = typeof(TModel); + + if (ModelType == modelType) + { + return SymmetricProcessor.ForType().Decrypt(CiphertextBytes, key.RejectIf().IsNull(nameof(key)).TargetArgument); + } + + throw new ArgumentException($"The specified type, {modelType.FullName}, does not match the model type of the encrypted model, {ModelType?.FullName ?? "[unknown]"}.", nameof(TModel)); + } + + /// + /// Decrypts and returns the plaintext . + /// + /// + /// The type of the plaintext model. + /// + /// + /// The symmetric key that was used to encrypt the model. + /// + /// + /// The plaintext . + /// + /// + /// does not match the model type of the encrypted model. + /// + /// + /// is . + /// + /// + /// The type assembly or one of its dependencies is invalid. + /// + /// + /// The type assembly could not be loaded. + /// + /// + /// An exception was raised during decryption or deserialization. + /// + public TModel ToPlaintextModel(ICascadingSymmetricKey key) + where TModel : class, IModel + { + var modelType = typeof(TModel); + + if (ModelType == modelType) + { + return SymmetricProcessor.ForType().Decrypt(CiphertextBytes, key.RejectIf().IsNull(nameof(key)).TargetArgument); + } + + throw new ArgumentException($"The specified type, {modelType.FullName}, does not match the model type of the encrypted model, {ModelType?.FullName ?? "[unknown]"}.", nameof(TModel)); + } + + /// + /// Gets or sets the ciphertext of the model as a Base64 string. + /// + /// + /// The specified string is empty. + /// + /// + /// The specified string is . + /// + /// + /// The specified string is not a valid Base64 string. + /// + [DataMember] + public String Ciphertext + { + get => CiphertextBytes.IsNullOrEmpty() ? null : Convert.ToBase64String(CiphertextBytes); + set => CiphertextBytes = Convert.FromBase64String(value.RejectIf().IsNullOrEmpty(nameof(Ciphertext))); + } + + /// + /// Gets the type of the plaintext model, or if is not + /// a valid, assembly-qualified type name. + /// + /// + /// The type assembly or one of its dependencies is invalid. + /// + /// + /// The type assembly could not be loaded. + /// + [IgnoreDataMember] + public Type ModelType => ModelTypeAssemblyQualifiedName.IsNullOrEmpty() ? null : Type.GetType(ModelTypeAssemblyQualifiedName, false, true); + + /// + /// Gets or sets the assembly-qualified name of the type of the plaintext model. + /// + /// + /// The specified string is empty. + /// + /// + /// The specified string is . + /// + [DataMember] + public String ModelTypeAssemblyQualifiedName + { + get => ModelTypeAssemblyQualifiedNameReference; + set => ModelTypeAssemblyQualifiedNameReference = value.RejectIf().IsNullOrEmpty(nameof(ModelTypeAssemblyQualifiedName)); + } + + /// + /// Represents the ciphertext of the model. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + [IgnoreDataMember] + internal Byte[] CiphertextBytes; + + /// + /// Represents the assembly-qualified name of the type of the plaintext model. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + [IgnoreDataMember] + private String ModelTypeAssemblyQualifiedNameReference; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/IEncryptedModel.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/IEncryptedModel.cs new file mode 100644 index 00000000..ac5ff236 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Symmetric/IEncryptedModel.cs @@ -0,0 +1,172 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using System; +using System.IO; +using System.Security; + +namespace RapidField.SolidInstruments.Cryptography.Symmetric +{ + /// + /// Represents a symmetrically encrypted . + /// + /// + /// The type of the plaintext model. + /// + public interface IEncryptedModel : IEncryptedModel + where TModel : class, IModel + { + /// + /// Decrypts and returns the plaintext . + /// + /// + /// The symmetric key that was used to encrypt the model. + /// + /// + /// The plaintext . + /// + /// + /// is . + /// + /// + /// The type assembly or one of its dependencies is invalid. + /// + /// + /// The type assembly could not be loaded. + /// + /// + /// An exception was raised during decryption or deserialization. + /// + public TModel ToPlaintextModel(ISymmetricKey key); + + /// + /// Decrypts and returns the plaintext . + /// + /// + /// The symmetric key that was used to encrypt the model. + /// + /// + /// The plaintext . + /// + /// + /// is . + /// + /// + /// The type assembly or one of its dependencies is invalid. + /// + /// + /// The type assembly could not be loaded. + /// + /// + /// An exception was raised during decryption or deserialization. + /// + public TModel ToPlaintextModel(ICascadingSymmetricKey key); + + /// + /// Converts the current to a serializable . + /// + /// + /// A serializable representation of the current . + /// + public EncryptedModel ToSerializableModel(); + } + + /// + /// Represents a symmetrically encrypted . + /// + public interface IEncryptedModel : IModel + { + /// + /// Decrypts and returns the plaintext . + /// + /// + /// The type of the plaintext model. + /// + /// + /// The symmetric key that was used to encrypt the model. + /// + /// + /// The plaintext . + /// + /// + /// does not match the model type of the encrypted model. + /// + /// + /// is . + /// + /// + /// The type assembly or one of its dependencies is invalid. + /// + /// + /// The type assembly could not be loaded. + /// + /// + /// An exception was raised during decryption or deserialization. + /// + public TModel ToPlaintextModel(ISymmetricKey key) + where TModel : class, IModel; + + /// + /// Decrypts and returns the plaintext . + /// + /// + /// The type of the plaintext model. + /// + /// + /// The symmetric key that was used to encrypt the model. + /// + /// + /// The plaintext . + /// + /// + /// does not match the model type of the encrypted model. + /// + /// + /// is . + /// + /// + /// The type assembly or one of its dependencies is invalid. + /// + /// + /// The type assembly could not be loaded. + /// + /// + /// An exception was raised during decryption or deserialization. + /// + public TModel ToPlaintextModel(ICascadingSymmetricKey key) + where TModel : class, IModel; + + /// + /// Gets the ciphertext of the model as a Base64 string. + /// + public String Ciphertext + { + get; + } + + /// + /// Gets the type of the plaintext model, or if is not + /// a valid, assembly-qualified type name. + /// + /// + /// The type assembly or one of its dependencies is invalid. + /// + /// + /// The type assembly could not be loaded. + /// + public Type ModelType + { + get; + } + + /// + /// Gets the assembly-qualified name of the type of the plaintext model. + /// + public String ModelTypeAssemblyQualifiedName + { + get; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Extensions/IModelExtensionsTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Extensions/IModelExtensionsTests.cs new file mode 100644 index 00000000..56b63657 --- /dev/null +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Extensions/IModelExtensionsTests.cs @@ -0,0 +1,58 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RapidField.SolidInstruments.Cryptography.Extensions; +using RapidField.SolidInstruments.Cryptography.Symmetric; +using System.Security.Cryptography; + +namespace RapidField.SolidInstruments.Cryptography.UnitTests.Extensions +{ + [TestClass] + public class IModelExtensionsTests + { + [TestMethod] + public void Encrypt_ShouldProduceDesiredResults_ForValidCascadingSymmetricKey() + { + using (var randomnessProvider = RandomNumberGenerator.Create()) + { + // Arrange. + var target = SimulatedModel.Random(randomnessProvider); + var modelType = target.GetType(); + var key = CascadingSymmetricKey.New(); + + // Act. + var result = target.Encrypt(key); + + // Assert. + result.Should().NotBeNull(); + result.Ciphertext.Should().NotBeNullOrEmpty(); + result.ModelType.Should().Be(modelType); + result.ModelTypeAssemblyQualifiedName.Should().Be(modelType.AssemblyQualifiedName); + } + } + + [TestMethod] + public void Encrypt_ShouldProduceDesiredResults_ForValidSymmetricKey() + { + using (var randomnessProvider = RandomNumberGenerator.Create()) + { + // Arrange. + var target = SimulatedModel.Random(randomnessProvider); + var modelType = target.GetType(); + var key = SymmetricKey.New(); + + // Act. + var result = target.Encrypt(key); + + // Assert. + result.Should().NotBeNull(); + result.Ciphertext.Should().NotBeNullOrEmpty(); + result.ModelType.Should().Be(modelType); + result.ModelTypeAssemblyQualifiedName.Should().Be(modelType.AssemblyQualifiedName); + } + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/ExportedSecretTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/ExportedSecretTests.cs new file mode 100644 index 00000000..20e4fd50 --- /dev/null +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/ExportedSecretTests.cs @@ -0,0 +1,160 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RapidField.SolidInstruments.Cryptography.Secrets; +using RapidField.SolidInstruments.Cryptography.Symmetric; +using RapidField.SolidInstruments.Serialization; +using System; +using System.Security.Cryptography.X509Certificates; + +namespace RapidField.SolidInstruments.Cryptography.UnitTests.Secrets +{ + [TestClass] + public class ExportedSecretTests + { + [TestMethod] + public void ShouldBeSerializable_UsingBinaryFormat() + { + // Arrange. + var format = SerializationFormat.Binary; + + // Assert. + ShouldBeSerializable(format); + } + + [TestMethod] + public void ShouldBeSerializable_UsingCompressedJsonFormat() + { + // Arrange. + var format = SerializationFormat.CompressedJson; + + // Assert. + ShouldBeSerializable(format); + } + + [TestMethod] + public void ShouldBeSerializable_UsingCompressedXmlFormat() + { + // Arrange. + var format = SerializationFormat.CompressedXml; + + // Assert. + ShouldBeSerializable(format); + } + + [TestMethod] + public void ShouldBeSerializable_UsingJsonFormat() + { + // Arrange. + var format = SerializationFormat.Json; + + // Assert. + ShouldBeSerializable(format); + } + + [TestMethod] + public void ShouldBeSerializable_UsingXmlFormat() + { + // Arrange. + var format = SerializationFormat.Xml; + + // Assert. + ShouldBeSerializable(format); + } + + private static void ShouldBeSerializable(SerializationFormat format) + { + ShouldBeSerializable_ForCascadingSymmetricKeySecret(format); + ShouldBeSerializable_ForGuidSecret(format); + ShouldBeSerializable_ForNumericSecret(format); + ShouldBeSerializable_ForStringSecret(format); + ShouldBeSerializable_ForSymmetricKeySecret(format); + ShouldBeSerializable_ForX509CertificateSecret(format); + } + + private static void ShouldBeSerializable(TValue value, TSecret secret, SerializationFormat format) + where TSecret : ISecret + { + var serializer = new DynamicSerializer(format); + var target = new ExportedSecret(secret); + + // Act. + var serializedTarget = serializer.Serialize(target); + var deserializedResult = serializer.Deserialize(serializedTarget); + + // Assert. + deserializedResult.ValueType.Should().Be(value.GetType()); + deserializedResult.Name.Should().Be(secret.Name); + deserializedResult.Should().Be(target); + } + + private static void ShouldBeSerializable_ForCascadingSymmetricKeySecret(SerializationFormat format) + { + // Arrange. + var name = "foo"; + var value = CascadingSymmetricKey.New(); + using var secret = CascadingSymmetricKeySecret.FromValue(name, value); + + // Act. + ShouldBeSerializable(value, secret, format); + } + + private static void ShouldBeSerializable_ForGuidSecret(SerializationFormat format) + { + // Arrange. + var name = "foo"; + var value = Guid.NewGuid(); + using var secret = GuidSecret.FromValue(name, value); + + // Act. + ShouldBeSerializable(value, secret, format); + } + + private static void ShouldBeSerializable_ForNumericSecret(SerializationFormat format) + { + // Arrange. + var name = "foo"; + var value = 1234.56789d; + using var secret = NumericSecret.FromValue(name, value); + + // Act. + ShouldBeSerializable(value, secret, format); + } + + private static void ShouldBeSerializable_ForStringSecret(SerializationFormat format) + { + // Arrange. + var name = "foo"; + var value = "bar"; + using var secret = StringSecret.FromValue(name, value); + + // Act. + ShouldBeSerializable(value, secret, format); + } + + private static void ShouldBeSerializable_ForSymmetricKeySecret(SerializationFormat format) + { + // Arrange. + var name = "foo"; + var value = SymmetricKey.New(); + using var secret = SymmetricKeySecret.FromValue(name, value); + + // Act. + ShouldBeSerializable(value, secret, format); + } + + private static void ShouldBeSerializable_ForX509CertificateSecret(SerializationFormat format) + { + // Arrange. + var name = "foo"; + var value = new X509Certificate2("TestRootOne.testcert"); + using var secret = X509CertificateSecret.FromValue(name, value); + + // Act. + ShouldBeSerializable(value, secret, format); + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SecretVaultTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SecretVaultTests.cs index b027348e..c9581113 100644 --- a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SecretVaultTests.cs +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SecretVaultTests.cs @@ -10,6 +10,7 @@ using RapidField.SolidInstruments.Cryptography.Symmetric; using System; using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; namespace RapidField.SolidInstruments.Cryptography.UnitTests.Secrets { @@ -17,17 +18,689 @@ namespace RapidField.SolidInstruments.Cryptography.UnitTests.Secrets public class SecretVaultTests { [TestMethod] - public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForBinarySecrets() + public void ExportEncryptedSecret_ShouldBeReversible_ForCascadingSymmetricKeySecrets_AsCiphertext_UsingExplicitKey() + { + // Arrange. + var secretName = "foo"; + var keyName = "bar"; + using var secret = CascadingSymmetricKey.New(); + using var key = SymmetricKey.New(); + using var targetOne = new SecretVault(); + using var targetTwo = new SecretVault(); + targetOne.AddOrUpdate(keyName, key); + targetTwo.AddOrUpdate(keyName, key); + + // Act. + targetOne.AddOrUpdate(secretName, secret); + using var exportSecretTaskOne = targetOne.ExportEncryptedSecretAsync(secretName, keyName); + exportSecretTaskOne.Wait(); + targetTwo.ImportEncryptedSecret(exportSecretTaskOne.Result, keyName); + using var exportSecretTaskTwo = targetTwo.ExportEncryptedSecretAsync(secretName, keyName); + exportSecretTaskTwo.Wait(); + targetOne.ImportEncryptedSecret(exportSecretTaskTwo.Result, keyName); + + // Assert. + targetOne.ReadAsync(secretName, (CascadingSymmetricKey value) => + { + value.Should().NotBeNull(); + }).Wait(); + targetTwo.ReadAsync(secretName, (CascadingSymmetricKey value) => + { + value.Should().NotBeNull(); + }).Wait(); + } + + [TestMethod] + public void ExportEncryptedSecret_ShouldBeReversible_ForCascadingSymmetricKeySecrets_AsCiphertext_UsingMasterKey() + { + // Arrange. + var secretName = "foo"; + var password = "12345 bar BAZ !"; + using var secret = CascadingSymmetricKey.New(); + using var key = SymmetricKey.New(); + using var targetOne = new SecretVault(password); + using var targetTwo = new SecretVault(password); + + // Act. + targetOne.AddOrUpdate(secretName, secret); + using var exportSecretTaskOne = targetOne.ExportEncryptedSecretAsync(secretName); + exportSecretTaskOne.Wait(); + targetTwo.ImportEncryptedSecret(exportSecretTaskOne.Result); + using var exportSecretTaskTwo = targetTwo.ExportEncryptedSecretAsync(secretName); + exportSecretTaskTwo.Wait(); + targetOne.ImportEncryptedSecret(exportSecretTaskTwo.Result); + + // Assert. + targetOne.ReadAsync(secretName, (CascadingSymmetricKey value) => + { + value.Should().NotBeNull(); + }).Wait(); + targetTwo.ReadAsync(secretName, (CascadingSymmetricKey value) => + { + value.Should().NotBeNull(); + }).Wait(); + } + + [TestMethod] + public void ExportEncryptedSecret_ShouldBeReversible_ForGuidSecrets_AsCiphertext_UsingExplicitKey() + { + // Arrange. + var secretName = "foo"; + var keyName = "bar"; + var secret = Guid.NewGuid(); + using var key = SymmetricKey.New(); + using var targetOne = new SecretVault(); + using var targetTwo = new SecretVault(); + targetOne.AddOrUpdate(keyName, key); + targetTwo.AddOrUpdate(keyName, key); + + // Act. + targetOne.AddOrUpdate(secretName, secret); + using var exportSecretTaskOne = targetOne.ExportEncryptedSecretAsync(secretName, keyName); + exportSecretTaskOne.Wait(); + targetTwo.ImportEncryptedSecret(exportSecretTaskOne.Result, keyName); + using var exportSecretTaskTwo = targetTwo.ExportEncryptedSecretAsync(secretName, keyName); + exportSecretTaskTwo.Wait(); + targetOne.ImportEncryptedSecret(exportSecretTaskTwo.Result, keyName); + + // Assert. + targetOne.ReadAsync(secretName, (Guid value) => + { + value.Should().Be(secret); + }).Wait(); + targetTwo.ReadAsync(secretName, (Guid value) => + { + value.Should().Be(secret); + }).Wait(); + } + + [TestMethod] + public void ExportEncryptedSecret_ShouldBeReversible_ForGuidSecrets_AsCiphertext_UsingMasterKey() + { + // Arrange. + var password = "12345 bar BAZ !"; + var secretName = "foo"; + var secret = Guid.NewGuid(); + using var key = SymmetricKey.New(); + using var targetOne = new SecretVault(password); + using var targetTwo = new SecretVault(password); + + // Act. + targetOne.AddOrUpdate(secretName, secret); + using var exportSecretTaskOne = targetOne.ExportEncryptedSecretAsync(secretName); + exportSecretTaskOne.Wait(); + targetTwo.ImportEncryptedSecret(exportSecretTaskOne.Result); + using var exportSecretTaskTwo = targetTwo.ExportEncryptedSecretAsync(secretName); + exportSecretTaskTwo.Wait(); + targetOne.ImportEncryptedSecret(exportSecretTaskTwo.Result); + + // Assert. + targetOne.ReadAsync(secretName, (Guid value) => + { + value.Should().Be(secret); + }).Wait(); + targetTwo.ReadAsync(secretName, (Guid value) => + { + value.Should().Be(secret); + }).Wait(); + } + + [TestMethod] + public void ExportEncryptedSecret_ShouldBeReversible_ForNumericSecrets_AsCiphertext_UsingExplicitKey() + { + // Arrange. + var secretName = "foo"; + var keyName = "bar"; + var secret = 1234.56789d; + using var key = SymmetricKey.New(); + using var targetOne = new SecretVault(); + using var targetTwo = new SecretVault(); + targetOne.AddOrUpdate(keyName, key); + targetTwo.AddOrUpdate(keyName, key); + + // Act. + targetOne.AddOrUpdate(secretName, secret); + using var exportSecretTaskOne = targetOne.ExportEncryptedSecretAsync(secretName, keyName); + exportSecretTaskOne.Wait(); + targetTwo.ImportEncryptedSecret(exportSecretTaskOne.Result, keyName); + using var exportSecretTaskTwo = targetTwo.ExportEncryptedSecretAsync(secretName, keyName); + exportSecretTaskTwo.Wait(); + targetOne.ImportEncryptedSecret(exportSecretTaskTwo.Result, keyName); + + // Assert. + targetOne.ReadAsync(secretName, (Double value) => + { + value.Should().Be(secret); + }).Wait(); + targetTwo.ReadAsync(secretName, (Double value) => + { + value.Should().Be(secret); + }).Wait(); + } + + [TestMethod] + public void ExportEncryptedSecret_ShouldBeReversible_ForNumericSecrets_AsCiphertext_UsingMasterKey() + { + // Arrange. + var password = "12345 bar BAZ !"; + var secretName = "foo"; + var secret = 1234.56789d; + using var key = SymmetricKey.New(); + using var targetOne = new SecretVault(password); + using var targetTwo = new SecretVault(password); + + // Act. + targetOne.AddOrUpdate(secretName, secret); + using var exportSecretTaskOne = targetOne.ExportEncryptedSecretAsync(secretName); + exportSecretTaskOne.Wait(); + targetTwo.ImportEncryptedSecret(exportSecretTaskOne.Result); + using var exportSecretTaskTwo = targetTwo.ExportEncryptedSecretAsync(secretName); + exportSecretTaskTwo.Wait(); + targetOne.ImportEncryptedSecret(exportSecretTaskTwo.Result); + + // Assert. + targetOne.ReadAsync(secretName, (Double value) => + { + value.Should().Be(secret); + }).Wait(); + targetTwo.ReadAsync(secretName, (Double value) => + { + value.Should().Be(secret); + }).Wait(); + } + + [TestMethod] + public void ExportEncryptedSecret_ShouldBeReversible_ForStringSecrets_AsCiphertext_UsingExplicitKey() + { + // Arrange. + var secretName = "foo"; + var keyName = "bar"; + var secret = "baz"; + using var key = SymmetricKey.New(); + using var targetOne = new SecretVault(); + using var targetTwo = new SecretVault(); + targetOne.AddOrUpdate(keyName, key); + targetTwo.AddOrUpdate(keyName, key); + + // Act. + targetOne.AddOrUpdate(secretName, secret); + using var exportSecretTaskOne = targetOne.ExportEncryptedSecretAsync(secretName, keyName); + exportSecretTaskOne.Wait(); + targetTwo.ImportEncryptedSecret(exportSecretTaskOne.Result, keyName); + using var exportSecretTaskTwo = targetTwo.ExportEncryptedSecretAsync(secretName, keyName); + exportSecretTaskTwo.Wait(); + targetOne.ImportEncryptedSecret(exportSecretTaskTwo.Result, keyName); + + // Assert. + targetOne.ReadAsync(secretName, (String value) => + { + value.Should().NotBeNullOrEmpty(); + value.Should().Be(secret); + }).Wait(); + targetTwo.ReadAsync(secretName, (String value) => + { + value.Should().NotBeNullOrEmpty(); + value.Should().Be(secret); + }).Wait(); + } + + [TestMethod] + public void ExportEncryptedSecret_ShouldBeReversible_ForStringSecrets_AsCiphertext_UsingMasterKey() + { + // Arrange. + var password = "12345 bar BAZ !"; + var secretName = "foo"; + var secret = "baz"; + using var key = SymmetricKey.New(); + using var targetOne = new SecretVault(password); + using var targetTwo = new SecretVault(password); + + // Act. + targetOne.AddOrUpdate(secretName, secret); + using var exportSecretTaskOne = targetOne.ExportEncryptedSecretAsync(secretName); + exportSecretTaskOne.Wait(); + targetTwo.ImportEncryptedSecret(exportSecretTaskOne.Result); + using var exportSecretTaskTwo = targetTwo.ExportEncryptedSecretAsync(secretName); + exportSecretTaskTwo.Wait(); + targetOne.ImportEncryptedSecret(exportSecretTaskTwo.Result); + + // Assert. + targetOne.ReadAsync(secretName, (String value) => + { + value.Should().NotBeNullOrEmpty(); + value.Should().Be(secret); + }).Wait(); + targetTwo.ReadAsync(secretName, (String value) => + { + value.Should().NotBeNullOrEmpty(); + value.Should().Be(secret); + }).Wait(); + } + + [TestMethod] + public void ExportEncryptedSecret_ShouldBeReversible_ForSymmetricKeySecrets_AsCiphertext_UsingExplicitKey() + { + // Arrange. + var secretName = "foo"; + var keyName = "bar"; + using var secret = SymmetricKey.New(); + using var key = SymmetricKey.New(); + using var targetOne = new SecretVault(); + using var targetTwo = new SecretVault(); + targetOne.AddOrUpdate(keyName, key); + targetTwo.AddOrUpdate(keyName, key); + + // Act. + targetOne.AddOrUpdate(secretName, secret); + using var exportSecretTaskOne = targetOne.ExportEncryptedSecretAsync(secretName, keyName); + exportSecretTaskOne.Wait(); + targetTwo.ImportEncryptedSecret(exportSecretTaskOne.Result, keyName); + using var exportSecretTaskTwo = targetTwo.ExportEncryptedSecretAsync(secretName, keyName); + exportSecretTaskTwo.Wait(); + targetOne.ImportEncryptedSecret(exportSecretTaskTwo.Result, keyName); + + // Assert. + targetOne.ReadAsync(secretName, (SymmetricKey value) => + { + value.Should().NotBeNull(); + }).Wait(); + targetTwo.ReadAsync(secretName, (SymmetricKey value) => + { + value.Should().NotBeNull(); + }).Wait(); + } + + [TestMethod] + public void ExportEncryptedSecret_ShouldBeReversible_ForSymmetricKeySecrets_AsCiphertext_UsingMasterKey() + { + // Arrange. + var password = "12345 bar BAZ !"; + var secretName = "foo"; + using var secret = SymmetricKey.New(); + using var key = SymmetricKey.New(); + using var targetOne = new SecretVault(password); + using var targetTwo = new SecretVault(password); + + // Act. + targetOne.AddOrUpdate(secretName, secret); + using var exportSecretTaskOne = targetOne.ExportEncryptedSecretAsync(secretName); + exportSecretTaskOne.Wait(); + targetTwo.ImportEncryptedSecret(exportSecretTaskOne.Result); + using var exportSecretTaskTwo = targetTwo.ExportEncryptedSecretAsync(secretName); + exportSecretTaskTwo.Wait(); + targetOne.ImportEncryptedSecret(exportSecretTaskTwo.Result); + + // Assert. + targetOne.ReadAsync(secretName, (SymmetricKey value) => + { + value.Should().NotBeNull(); + }).Wait(); + targetTwo.ReadAsync(secretName, (SymmetricKey value) => + { + value.Should().NotBeNull(); + }).Wait(); + } + + [TestMethod] + public void ExportEncryptedSecret_ShouldBeReversible_ForX509CertificateSecrets_AsCiphertext_UsingExplicitKey() + { + // Arrange. + var secretName = "foo"; + var keyName = "bar"; + var fileName = "TestRootOne.testcert"; + var subject = "CN=TestRootOne"; + var secret = new X509Certificate2(fileName); + using var key = SymmetricKey.New(); + using var targetOne = new SecretVault(); + using var targetTwo = new SecretVault(); + targetOne.AddOrUpdate(keyName, key); + targetTwo.AddOrUpdate(keyName, key); + + // Act. + targetOne.AddOrUpdate(secretName, secret); + using var exportSecretTaskOne = targetOne.ExportEncryptedSecretAsync(secretName, keyName); + exportSecretTaskOne.Wait(); + targetTwo.ImportEncryptedSecret(exportSecretTaskOne.Result, keyName); + using var exportSecretTaskTwo = targetTwo.ExportEncryptedSecretAsync(secretName, keyName); + exportSecretTaskTwo.Wait(); + targetOne.ImportEncryptedSecret(exportSecretTaskTwo.Result, keyName); + + // Assert. + targetOne.ReadAsync(secretName, (X509Certificate2 value) => + { + value.Should().NotBeNull(); + value.Subject.Should().Be(subject); + }).Wait(); + targetTwo.ReadAsync(secretName, (X509Certificate2 value) => + { + value.Should().NotBeNull(); + value.Subject.Should().Be(subject); + }).Wait(); + } + + [TestMethod] + public void ExportEncryptedSecret_ShouldBeReversible_ForX509CertificateSecrets_AsCiphertext_UsingMasterKey() + { + // Arrange. + var password = "12345 bar BAZ !"; + var secretName = "foo"; + var fileName = "TestRootOne.testcert"; + var subject = "CN=TestRootOne"; + var secret = new X509Certificate2(fileName); + using var key = SymmetricKey.New(); + using var targetOne = new SecretVault(password); + using var targetTwo = new SecretVault(password); + + // Act. + targetOne.AddOrUpdate(secretName, secret); + using var exportSecretTaskOne = targetOne.ExportEncryptedSecretAsync(secretName); + exportSecretTaskOne.Wait(); + targetTwo.ImportEncryptedSecret(exportSecretTaskOne.Result); + using var exportSecretTaskTwo = targetTwo.ExportEncryptedSecretAsync(secretName); + exportSecretTaskTwo.Wait(); + targetOne.ImportEncryptedSecret(exportSecretTaskTwo.Result); + + // Assert. + targetOne.ReadAsync(secretName, (X509Certificate2 value) => + { + value.Should().NotBeNull(); + value.Subject.Should().Be(subject); + }).Wait(); + targetTwo.ReadAsync(secretName, (X509Certificate2 value) => + { + value.Should().NotBeNull(); + value.Subject.Should().Be(subject); + }).Wait(); + } + + [TestMethod] + public void ExportSecret_ShouldBeReversible_ForCascadingSymmetricKeySecrets_AsPlaintext() + { + // Arrange. + var name = "foo"; + using var secret = CascadingSymmetricKey.New(); + using var targetOne = new SecretVault(); + using var targetTwo = new SecretVault(); + + // Act. + targetOne.AddOrUpdate(name, secret); + using var exportSecretTaskOne = targetOne.ExportSecretAsync(name); + exportSecretTaskOne.Wait(); + targetTwo.ImportSecret(exportSecretTaskOne.Result); + using var exportSecretTaskTwo = targetTwo.ExportSecretAsync(name); + exportSecretTaskTwo.Wait(); + targetOne.ImportSecret(exportSecretTaskTwo.Result); + + // Assert. + targetOne.ReadAsync(name, (CascadingSymmetricKey value) => + { + value.Should().NotBeNull(); + }).Wait(); + targetTwo.ReadAsync(name, (CascadingSymmetricKey value) => + { + value.Should().NotBeNull(); + }).Wait(); + } + + [TestMethod] + public void ExportSecret_ShouldBeReversible_ForGuidSecrets_AsPlaintext() + { + // Arrange. + var name = "foo"; + var secret = Guid.NewGuid(); + using var targetOne = new SecretVault(); + using var targetTwo = new SecretVault(); + + // Act. + targetOne.AddOrUpdate(name, secret); + using var exportSecretTaskOne = targetOne.ExportSecretAsync(name); + exportSecretTaskOne.Wait(); + targetTwo.ImportSecret(exportSecretTaskOne.Result); + using var exportSecretTaskTwo = targetTwo.ExportSecretAsync(name); + exportSecretTaskTwo.Wait(); + targetOne.ImportSecret(exportSecretTaskTwo.Result); + + // Assert. + targetOne.ReadAsync(name, (Guid value) => + { + value.Should().Be(secret); + }).Wait(); + targetTwo.ReadAsync(name, (Guid value) => + { + value.Should().Be(secret); + }).Wait(); + } + + [TestMethod] + public void ExportSecret_ShouldBeReversible_ForNumericSecrets_AsPlaintext() + { + // Arrange. + var name = "foo"; + var secret = 1234.56789d; + using var targetOne = new SecretVault(); + using var targetTwo = new SecretVault(); + + // Act. + targetOne.AddOrUpdate(name, secret); + using var exportSecretTaskOne = targetOne.ExportSecretAsync(name); + exportSecretTaskOne.Wait(); + targetTwo.ImportSecret(exportSecretTaskOne.Result); + using var exportSecretTaskTwo = targetTwo.ExportSecretAsync(name); + exportSecretTaskTwo.Wait(); + targetOne.ImportSecret(exportSecretTaskTwo.Result); + + // Assert. + targetOne.ReadAsync(name, (Double value) => + { + value.Should().Be(secret); + }).Wait(); + targetTwo.ReadAsync(name, (Double value) => + { + value.Should().Be(secret); + }).Wait(); + } + + [TestMethod] + public void ExportSecret_ShouldBeReversible_ForStandardSecrets_AsPlaintext() + { + // Arrange. + var name = "foo"; + var secret = new Byte[3] { 0xff, 0xa9, 0x00 }; + using var targetOne = new SecretVault(); + using var targetTwo = new SecretVault(); + + // Act. + targetOne.AddOrUpdate(name, secret); + using var exportSecretTaskOne = targetOne.ExportSecretAsync(name); + exportSecretTaskOne.Wait(); + targetTwo.ImportSecret(exportSecretTaskOne.Result); + using var exportSecretTaskTwo = targetTwo.ExportSecretAsync(name); + exportSecretTaskTwo.Wait(); + targetOne.ImportSecret(exportSecretTaskTwo.Result); + + // Assert. + targetOne.ReadAsync(name, (IReadOnlyPinnedMemory value) => + { + value.Should().NotBeNull(); + value.Should().BeEquivalentTo(secret); + }).Wait(); + targetTwo.ReadAsync(name, (IReadOnlyPinnedMemory value) => + { + value.Should().NotBeNull(); + value.Should().BeEquivalentTo(secret); + }).Wait(); + } + + [TestMethod] + public void ExportSecret_ShouldBeReversible_ForStringSecrets_AsPlaintext() + { + // Arrange. + var name = "foo"; + var secret = "bar"; + using var targetOne = new SecretVault(); + using var targetTwo = new SecretVault(); + + // Act. + targetOne.AddOrUpdate(name, secret); + using var exportSecretTaskOne = targetOne.ExportSecretAsync(name); + exportSecretTaskOne.Wait(); + targetTwo.ImportSecret(exportSecretTaskOne.Result); + using var exportSecretTaskTwo = targetTwo.ExportSecretAsync(name); + exportSecretTaskTwo.Wait(); + targetOne.ImportSecret(exportSecretTaskTwo.Result); + + // Assert. + targetOne.ReadAsync(name, (String value) => + { + value.Should().NotBeNullOrEmpty(); + value.Should().Be(secret); + }).Wait(); + targetTwo.ReadAsync(name, (String value) => + { + value.Should().NotBeNullOrEmpty(); + value.Should().Be(secret); + }).Wait(); + } + + [TestMethod] + public void ExportSecret_ShouldBeReversible_ForSymmetricKeySecrets_AsPlaintext() + { + // Arrange. + var name = "foo"; + using var secret = SymmetricKey.New(); + using var targetOne = new SecretVault(); + using var targetTwo = new SecretVault(); + + // Act. + targetOne.AddOrUpdate(name, secret); + using var exportSecretTaskOne = targetOne.ExportSecretAsync(name); + exportSecretTaskOne.Wait(); + targetTwo.ImportSecret(exportSecretTaskOne.Result); + using var exportSecretTaskTwo = targetTwo.ExportSecretAsync(name); + exportSecretTaskTwo.Wait(); + targetOne.ImportSecret(exportSecretTaskTwo.Result); + + // Assert. + targetOne.ReadAsync(name, (SymmetricKey value) => + { + value.Should().NotBeNull(); + }).Wait(); + targetTwo.ReadAsync(name, (SymmetricKey value) => + { + value.Should().NotBeNull(); + }).Wait(); + } + + [TestMethod] + public void ExportSecret_ShouldBeReversible_ForX509CertificateSecrets_AsPlaintext() + { + // Arrange. + var name = "foo"; + var fileName = "TestRootOne.testcert"; + var subject = "CN=TestRootOne"; + var secret = new X509Certificate2(fileName); + using var targetOne = new SecretVault(); + using var targetTwo = new SecretVault(); + + // Act. + targetOne.AddOrUpdate(name, secret); + using var exportSecretTaskOne = targetOne.ExportSecretAsync(name); + exportSecretTaskOne.Wait(); + targetTwo.ImportSecret(exportSecretTaskOne.Result); + using var exportSecretTaskTwo = targetTwo.ExportSecretAsync(name); + exportSecretTaskTwo.Wait(); + targetOne.ImportSecret(exportSecretTaskTwo.Result); + + // Assert. + targetOne.ReadAsync(name, (X509Certificate2 value) => + { + value.Should().NotBeNull(); + value.Subject.Should().Be(subject); + }).Wait(); + targetTwo.ReadAsync(name, (X509Certificate2 value) => + { + value.Should().NotBeNull(); + value.Subject.Should().Be(subject); + }).Wait(); + } + + [TestMethod] + public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForCascadingSymmetricKeySecrets() + { + // Arrange. + using var secretOne = CascadingSymmetricKey.New(); + using var secretTwo = CascadingSymmetricKey.New(); + using var secretThree = CascadingSymmetricKey.New(); + var secretOneName = "foo"; + var secretTwoName = "bar"; + + using (var target = new SecretVault()) + { + // Assert. + target.Count.Should().Be(0); + target.Names.Should().BeEmpty(); + + // Act. + target.AddOrUpdate(secretOneName, secretOne); + + // Assert. + target.Count.Should().Be(1); + target.Names.Should().Contain(secretOneName); + target.ReadAsync(secretOneName, (CascadingSymmetricKey value) => + { + value.Should().NotBeNull(); + }).Wait(); + + // Act. + target.AddOrUpdate(secretTwoName, secretTwo); + + // Assert. + target.Count.Should().Be(2); + target.Names.Should().Contain(secretOneName); + target.Names.Should().Contain(secretTwoName); + target.ReadAsync(secretTwoName, (CascadingSymmetricKey value) => + { + value.Should().NotBeNull(); + }).Wait(); + + // Act. + target.AddOrUpdate(secretOneName, secretThree); + + // Assert. + target.Count.Should().Be(2); + target.Names.Should().Contain(secretOneName); + target.ReadAsync(secretOneName, (CascadingSymmetricKey value) => + { + value.Should().NotBeNull(); + }).Wait(); + + // Act. + target.TryRemove("baz").Should().BeFalse(); + target.TryRemove(secretOneName).Should().BeTrue(); + + // Assert. + target.Count.Should().Be(1); + target.Names.Should().NotContain(secretOneName); + target.Names.Should().Contain(secretTwoName); + + // Act. + target.Clear(); + + // Assert. + target.Count.Should().Be(0); + target.Names.Should().BeEmpty(); + } + } + + [TestMethod] + public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForGuidSecrets() { using (var randomnessProvider = RandomNumberGenerator.Create()) { // Arrange. - var secretOne = new Byte[3]; - var secretTwo = new Byte[21]; - var secretThree = new Byte[89]; - randomnessProvider.GetBytes(secretOne); - randomnessProvider.GetBytes(secretTwo); - randomnessProvider.GetBytes(secretThree); + var secretOne = Guid.NewGuid(); + var secretTwo = Guid.NewGuid(); + var secretThree = Guid.NewGuid(); var secretOneName = "foo"; var secretTwoName = "bar"; @@ -43,9 +716,9 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForBinarySecrets( // Assert. target.Count.Should().Be(1); target.Names.Should().Contain(secretOneName); - target.ReadAsync(secretOneName, (IReadOnlyPinnedMemory value) => + target.ReadAsync(secretOneName, (Guid value) => { - value.Should().BeEquivalentTo(secretOne); + value.Should().Be(secretOne); }).Wait(); // Act. @@ -55,9 +728,9 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForBinarySecrets( target.Count.Should().Be(2); target.Names.Should().Contain(secretOneName); target.Names.Should().Contain(secretTwoName); - target.ReadAsync(secretTwoName, (IReadOnlyPinnedMemory value) => + target.ReadAsync(secretTwoName, (Guid value) => { - value.Should().BeEquivalentTo(secretTwo); + value.Should().Be(secretTwo); }).Wait(); // Act. @@ -66,9 +739,9 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForBinarySecrets( // Assert. target.Count.Should().Be(2); target.Names.Should().Contain(secretOneName); - target.ReadAsync(secretOneName, (IReadOnlyPinnedMemory value) => + target.ReadAsync(secretOneName, (Guid value) => { - value.Should().BeEquivalentTo(secretThree); + value.Should().Be(secretThree); }).Wait(); // Act. @@ -91,14 +764,14 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForBinarySecrets( } [TestMethod] - public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForGuidSecrets() + public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForNumericSecrets() { using (var randomnessProvider = RandomNumberGenerator.Create()) { // Arrange. - var secretOne = Guid.NewGuid(); - var secretTwo = Guid.NewGuid(); - var secretThree = Guid.NewGuid(); + var secretOne = randomnessProvider.GetDouble(); + var secretTwo = randomnessProvider.GetDouble(); + var secretThree = randomnessProvider.GetDouble(); var secretOneName = "foo"; var secretTwoName = "bar"; @@ -114,7 +787,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForGuidSecrets() // Assert. target.Count.Should().Be(1); target.Names.Should().Contain(secretOneName); - target.ReadAsync(secretOneName, (Guid value) => + target.ReadAsync(secretOneName, (Double value) => { value.Should().Be(secretOne); }).Wait(); @@ -126,7 +799,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForGuidSecrets() target.Count.Should().Be(2); target.Names.Should().Contain(secretOneName); target.Names.Should().Contain(secretTwoName); - target.ReadAsync(secretTwoName, (Guid value) => + target.ReadAsync(secretTwoName, (Double value) => { value.Should().Be(secretTwo); }).Wait(); @@ -137,7 +810,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForGuidSecrets() // Assert. target.Count.Should().Be(2); target.Names.Should().Contain(secretOneName); - target.ReadAsync(secretOneName, (Guid value) => + target.ReadAsync(secretOneName, (Double value) => { value.Should().Be(secretThree); }).Wait(); @@ -162,14 +835,17 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForGuidSecrets() } [TestMethod] - public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForNumericSecrets() + public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForStandardSecrets() { using (var randomnessProvider = RandomNumberGenerator.Create()) { // Arrange. - var secretOne = randomnessProvider.GetDouble(); - var secretTwo = randomnessProvider.GetDouble(); - var secretThree = randomnessProvider.GetDouble(); + var secretOne = new Byte[3]; + var secretTwo = new Byte[21]; + var secretThree = new Byte[89]; + randomnessProvider.GetBytes(secretOne); + randomnessProvider.GetBytes(secretTwo); + randomnessProvider.GetBytes(secretThree); var secretOneName = "foo"; var secretTwoName = "bar"; @@ -185,9 +861,9 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForNumericSecrets // Assert. target.Count.Should().Be(1); target.Names.Should().Contain(secretOneName); - target.ReadAsync(secretOneName, (Double value) => + target.ReadAsync(secretOneName, (IReadOnlyPinnedMemory value) => { - value.Should().Be(secretOne); + value.Should().BeEquivalentTo(secretOne); }).Wait(); // Act. @@ -197,9 +873,9 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForNumericSecrets target.Count.Should().Be(2); target.Names.Should().Contain(secretOneName); target.Names.Should().Contain(secretTwoName); - target.ReadAsync(secretTwoName, (Double value) => + target.ReadAsync(secretTwoName, (IReadOnlyPinnedMemory value) => { - value.Should().Be(secretTwo); + value.Should().BeEquivalentTo(secretTwo); }).Wait(); // Act. @@ -208,9 +884,9 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForNumericSecrets // Assert. target.Count.Should().Be(2); target.Names.Should().Contain(secretOneName); - target.ReadAsync(secretOneName, (Double value) => + target.ReadAsync(secretOneName, (IReadOnlyPinnedMemory value) => { - value.Should().Be(secretThree); + value.Should().BeEquivalentTo(secretThree); }).Wait(); // Act. @@ -302,5 +978,150 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForStringSecrets( } } } + + [TestMethod] + public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForSymmetricKeySecrets() + { + // Arrange. + using var secretOne = SymmetricKey.New(); + using var secretTwo = SymmetricKey.New(); + using var secretThree = SymmetricKey.New(); + var secretOneName = "foo"; + var secretTwoName = "bar"; + + using (var target = new SecretVault()) + { + // Assert. + target.Count.Should().Be(0); + target.Names.Should().BeEmpty(); + + // Act. + target.AddOrUpdate(secretOneName, secretOne); + + // Assert. + target.Count.Should().Be(1); + target.Names.Should().Contain(secretOneName); + target.ReadAsync(secretOneName, (SymmetricKey value) => + { + value.Should().NotBeNull(); + }).Wait(); + + // Act. + target.AddOrUpdate(secretTwoName, secretTwo); + + // Assert. + target.Count.Should().Be(2); + target.Names.Should().Contain(secretOneName); + target.Names.Should().Contain(secretTwoName); + target.ReadAsync(secretTwoName, (SymmetricKey value) => + { + value.Should().NotBeNull(); + }).Wait(); + + // Act. + target.AddOrUpdate(secretOneName, secretThree); + + // Assert. + target.Count.Should().Be(2); + target.Names.Should().Contain(secretOneName); + target.ReadAsync(secretOneName, (SymmetricKey value) => + { + value.Should().NotBeNull(); + }).Wait(); + + // Act. + target.TryRemove("baz").Should().BeFalse(); + target.TryRemove(secretOneName).Should().BeTrue(); + + // Assert. + target.Count.Should().Be(1); + target.Names.Should().NotContain(secretOneName); + target.Names.Should().Contain(secretTwoName); + + // Act. + target.Clear(); + + // Assert. + target.Count.Should().Be(0); + target.Names.Should().BeEmpty(); + } + } + + [TestMethod] + public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForX509CertificateSecrets() + { + // Arrange. + var fileNameOne = "TestRootOne.testcert"; + var fileNameTwo = "TestRootTwo.testcert"; + var fileNameThree = "TestRootThree.testcert"; + var subjectOne = "CN=TestRootOne"; + var subjectTwo = "CN=TestRootTwo"; + var subjectThree = "CN=TestRootThree"; + var secretOne = new X509Certificate2(fileNameOne); + var secretTwo = new X509Certificate2(fileNameTwo); + var secretThree = new X509Certificate2(fileNameThree); + var secretOneName = "foo"; + var secretTwoName = "bar"; + + using (var target = new SecretVault()) + { + // Assert. + target.Count.Should().Be(0); + target.Names.Should().BeEmpty(); + + // Act. + target.AddOrUpdate(secretOneName, secretOne); + + // Assert. + target.Count.Should().Be(1); + target.Names.Should().Contain(secretOneName); + target.ReadAsync(secretOneName, (X509Certificate2 value) => + { + value.Should().NotBeNull(); + value.Subject.Should().Be(subjectOne); + }).Wait(); + + // Act. + target.AddOrUpdate(secretTwoName, secretTwo); + + // Assert. + target.Count.Should().Be(2); + target.Names.Should().Contain(secretOneName); + target.Names.Should().Contain(secretTwoName); + target.ReadAsync(secretTwoName, (X509Certificate2 value) => + { + value.Should().NotBeNull(); + value.Subject.Should().Be(subjectTwo); + }).Wait(); + + // Act. + target.AddOrUpdate(secretOneName, secretThree); + + // Assert. + target.Count.Should().Be(2); + target.Names.Should().Contain(secretOneName); + target.ReadAsync(secretOneName, (X509Certificate2 value) => + { + value.Should().NotBeNull(); + value.Subject.Should().Be(subjectThree); + }).Wait(); + + // Act. + target.TryRemove("baz").Should().BeFalse(); + target.TryRemove(secretOneName).Should().BeTrue(); + + // Assert. + target.Count.Should().Be(1); + target.Names.Should().NotContain(secretOneName); + target.Names.Should().Contain(secretTwoName); + + // Act. + target.Clear(); + + // Assert. + target.Count.Should().Be(0); + target.Names.Should().BeEmpty(); + } + } } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/SimulatedModel.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/SimulatedModel.cs new file mode 100644 index 00000000..29ad5e9a --- /dev/null +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/SimulatedModel.cs @@ -0,0 +1,181 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Cryptography.Extensions; +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using System.Security.Cryptography; + +namespace RapidField.SolidInstruments.Cryptography.UnitTests +{ + /// + /// Represents an derivative that is used for testing. + /// + [DataContract] + internal class SimulatedModel : Model + { + /// + /// Initializes a new instance of the class. + /// + public SimulatedModel() + : this(TimeOfDay.NowUtc) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A value. The default value is . + /// + /// + /// is . + /// + public SimulatedModel(TimeOfDay time) + : this(time, Array.Empty()) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A value. The default value is . + /// + /// + /// An collection. The default value is an empty collection. + /// + /// + /// is -or- is + /// . + /// + public SimulatedModel(TimeOfDay time, IEnumerable integerCollection) + : this(time, integerCollection, Array.Empty()) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A value. The default value is . + /// + /// + /// An collection. The default value is an empty collection. + /// + /// + /// A collection. The default value is an empty collection. + /// + /// + /// is -or- is + /// -or- is . + /// + public SimulatedModel(TimeOfDay time, IEnumerable integerCollection, IEnumerable modelCollection) + : this(time, integerCollection, modelCollection, null) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A value. The default value is . + /// + /// + /// An collection. The default value is an empty collection. + /// + /// + /// A collection. The default value is an empty collection. + /// + /// + /// A value. This argument can be . The default value is + /// . + /// + /// + /// is -or- is + /// -or- is . + /// + public SimulatedModel(TimeOfDay time, IEnumerable integerCollection, IEnumerable modelCollection, String stringValue) + : base() + { + IntegerCollection = new List(integerCollection.RejectIf().IsNull(nameof(integerCollection)).TargetArgument); + ModelCollection = new List(modelCollection.RejectIf().IsNull(nameof(modelCollection)).TargetArgument); + StringValue = stringValue; + Time = time.RejectIf().IsNull(nameof(time)); + } + + /// + /// Generates a randomly-generated . + /// + /// + /// A random number generator that is used to generate the random model. + /// + /// + /// A randomly-generated . + /// + public static SimulatedModel Random(RandomNumberGenerator randomnessProvider) + { + var hour = randomnessProvider.GetInt32(0, 23); + var minute = randomnessProvider.GetInt32(0, 59); + var second = randomnessProvider.GetInt32(0, 59); + var time = new TimeOfDay(TimeZoneInfo.Utc, hour, minute, second); + var integerCollectionLength = randomnessProvider.GetInt32(0, 2); + var integerCollection = new Int32[integerCollectionLength]; + var modelCollectionLength = randomnessProvider.GetInt32(0, 2); + + if (modelCollectionLength == 2 && randomnessProvider.GetBoolean()) + { + // Reduce the likelihood of deep test graphs. + modelCollectionLength = 1; + } + + var modelCollection = new SimulatedModel[modelCollectionLength]; + var stringLength = randomnessProvider.GetInt32(3, 5); + var stringValue = randomnessProvider.GetBoolean() ? randomnessProvider.GetString(stringLength, false, true, true, true, false, false, false) : null; + randomnessProvider.FillInt32Array(integerCollection); + + for (var i = 0; i < modelCollectionLength; i++) + { + modelCollection[i] = Random(randomnessProvider); + } + + return new SimulatedModel(time, integerCollection, modelCollection, stringValue); + } + + /// + /// Gets or sets a value. + /// + [DataMember] + public TimeOfDay Time + { + get; + set; + } + + /// + /// Represents an collection. + /// + [DataMember] + public readonly List IntegerCollection; + + /// + /// Represents a collection. + /// + [DataMember] + public readonly List ModelCollection; + + /// + /// Represents a value. + /// + [DataMember] + public String StringValue; + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/EncryptedModelTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/EncryptedModelTests.cs new file mode 100644 index 00000000..f455b3ab --- /dev/null +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/EncryptedModelTests.cs @@ -0,0 +1,210 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RapidField.SolidInstruments.Cryptography.Symmetric; +using RapidField.SolidInstruments.Serialization; +using System.Security.Cryptography; + +namespace RapidField.SolidInstruments.Cryptography.UnitTests.Symmetric +{ + [TestClass] + public class EncryptedModelTests + { + [TestMethod] + public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForValidCascadingSymmetricKey() + { + using (var randomnessProvider = RandomNumberGenerator.Create()) + { + // Arrange. + var model = SimulatedModel.Random(randomnessProvider); + var modelType = model.GetType(); + var key = CascadingSymmetricKey.New(); + + // Act. + var targetOne = EncryptedModel.FromPlaintextModel(model, key); + var targetTwo = EncryptedModel.FromPlaintextModel(model, key); + + // Assert. + targetOne.Should().NotBeNull(); + targetTwo.Should().NotBeNull(); + targetOne.Should().NotBeSameAs(targetTwo); + targetOne.Ciphertext.Should().NotBeNullOrEmpty(); + targetTwo.Ciphertext.Should().NotBeNullOrEmpty(); + targetOne.Ciphertext.Should().NotBe(targetTwo.Ciphertext); + targetOne.ModelType.Should().Be(modelType); + targetTwo.ModelType.Should().Be(modelType); + targetOne.ModelTypeAssemblyQualifiedName.Should().Be(modelType.AssemblyQualifiedName); + targetTwo.ModelTypeAssemblyQualifiedName.Should().Be(modelType.AssemblyQualifiedName); + + // Act. + var decryptedModelOne = targetOne.ToPlaintextModel(key); + var decryptedModelTwo = targetTwo.ToPlaintextModel(key); + var decryptedModelThree = targetTwo.ToPlaintextModel(key); + + // Assert. + decryptedModelOne.Should().NotBeNull(); + decryptedModelTwo.Should().NotBeNull(); + decryptedModelThree.Should().NotBeNull(); + decryptedModelOne.Should().BeOfType(modelType); + decryptedModelTwo.Should().BeOfType(modelType); + decryptedModelThree.Should().BeOfType(modelType); + decryptedModelOne.Should().Be(model); + decryptedModelTwo.Should().Be(model); + decryptedModelThree.Should().Be(model); + decryptedModelOne.Should().NotBeSameAs(model); + decryptedModelTwo.Should().NotBeSameAs(model); + decryptedModelThree.Should().NotBeSameAs(model); + decryptedModelOne.Should().NotBeSameAs(decryptedModelTwo); + decryptedModelTwo.Should().NotBeSameAs(decryptedModelThree); + + // Act. + var targetThree = EncryptedModel.FromPlaintextModel(decryptedModelThree, key); + + // Assert. + targetThree.Should().NotBeNull(); + targetThree.Should().NotBeSameAs(targetOne); + targetThree.Should().NotBeSameAs(targetTwo); + targetThree.Ciphertext.Should().NotBeNullOrEmpty(); + targetThree.Ciphertext.Should().NotBe(targetOne.Ciphertext); + targetThree.Ciphertext.Should().NotBe(targetTwo.Ciphertext); + targetThree.ModelType.Should().Be(modelType); + targetThree.ModelTypeAssemblyQualifiedName.Should().Be(modelType.AssemblyQualifiedName); + } + } + + [TestMethod] + public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForValidSymmetricKey() + { + using (var randomnessProvider = RandomNumberGenerator.Create()) + { + // Arrange. + var model = SimulatedModel.Random(randomnessProvider); + var modelType = model.GetType(); + var key = SymmetricKey.New(); + + // Act. + var targetOne = EncryptedModel.FromPlaintextModel(model, key); + var targetTwo = EncryptedModel.FromPlaintextModel(model, key); + + // Assert. + targetOne.Should().NotBeNull(); + targetTwo.Should().NotBeNull(); + targetOne.Should().NotBeSameAs(targetTwo); + targetOne.Ciphertext.Should().NotBeNullOrEmpty(); + targetTwo.Ciphertext.Should().NotBeNullOrEmpty(); + targetOne.Ciphertext.Should().NotBe(targetTwo.Ciphertext); + targetOne.ModelType.Should().Be(modelType); + targetTwo.ModelType.Should().Be(modelType); + targetOne.ModelTypeAssemblyQualifiedName.Should().Be(modelType.AssemblyQualifiedName); + targetTwo.ModelTypeAssemblyQualifiedName.Should().Be(modelType.AssemblyQualifiedName); + + // Act. + var decryptedModelOne = targetOne.ToPlaintextModel(key); + var decryptedModelTwo = targetTwo.ToPlaintextModel(key); + var decryptedModelThree = targetTwo.ToPlaintextModel(key); + + // Assert. + decryptedModelOne.Should().NotBeNull(); + decryptedModelTwo.Should().NotBeNull(); + decryptedModelThree.Should().NotBeNull(); + decryptedModelOne.Should().BeOfType(modelType); + decryptedModelTwo.Should().BeOfType(modelType); + decryptedModelThree.Should().BeOfType(modelType); + decryptedModelOne.Should().Be(model); + decryptedModelTwo.Should().Be(model); + decryptedModelThree.Should().Be(model); + decryptedModelOne.Should().NotBeSameAs(model); + decryptedModelTwo.Should().NotBeSameAs(model); + decryptedModelThree.Should().NotBeSameAs(model); + decryptedModelOne.Should().NotBeSameAs(decryptedModelTwo); + decryptedModelTwo.Should().NotBeSameAs(decryptedModelThree); + + // Act. + var targetThree = EncryptedModel.FromPlaintextModel(decryptedModelThree, key); + + // Assert. + targetThree.Should().NotBeNull(); + targetThree.Should().NotBeSameAs(targetOne); + targetThree.Should().NotBeSameAs(targetTwo); + targetThree.Ciphertext.Should().NotBeNullOrEmpty(); + targetThree.Ciphertext.Should().NotBe(targetOne.Ciphertext); + targetThree.Ciphertext.Should().NotBe(targetTwo.Ciphertext); + targetThree.ModelType.Should().Be(modelType); + targetThree.ModelTypeAssemblyQualifiedName.Should().Be(modelType.AssemblyQualifiedName); + } + } + + [TestMethod] + public void ShouldBeSerializable_UsingBinaryFormat() + { + // Arrange. + var format = SerializationFormat.Binary; + + // Assert. + ShouldBeSerializable(format); + } + + [TestMethod] + public void ShouldBeSerializable_UsingCompressedJsonFormat() + { + // Arrange. + var format = SerializationFormat.CompressedJson; + + // Assert. + ShouldBeSerializable(format); + } + + [TestMethod] + public void ShouldBeSerializable_UsingCompressedXmlFormat() + { + // Arrange. + var format = SerializationFormat.CompressedXml; + + // Assert. + ShouldBeSerializable(format); + } + + [TestMethod] + public void ShouldBeSerializable_UsingJsonFormat() + { + // Arrange. + var format = SerializationFormat.Json; + + // Assert. + ShouldBeSerializable(format); + } + + [TestMethod] + public void ShouldBeSerializable_UsingXmlFormat() + { + // Arrange. + var format = SerializationFormat.Xml; + + // Assert. + ShouldBeSerializable(format); + } + + private static void ShouldBeSerializable(SerializationFormat format) + { + using (var randomnessProvider = RandomNumberGenerator.Create()) + { + // Arrange. + var model = SimulatedModel.Random(randomnessProvider); + var serializer = new DynamicSerializer(format); + var key = SymmetricKey.New(); + var target = EncryptedModel.FromPlaintextModel(model, key).ToSerializableModel(); + + // Act. + var serializedTarget = serializer.Serialize(target); + var deserializedResult = serializer.Deserialize(serializedTarget); + + // Assert. + deserializedResult.Should().Be(target); + deserializedResult.ToPlaintextModel(key).Should().Be(model); + } + } + } +} \ No newline at end of file From 5b195e92df76e008421bf7d5619ea6fb939a3805 Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Wed, 10 Jun 2020 13:00:07 -0500 Subject: [PATCH 33/55] Tightening numerous cryptographic facilities. --- en-US_User.dic | 5 + .../Extensions/ByteCollectionExtensions.cs | 11 + ...HashingAlgorithmSpecificationExtensions.cs | 28 +- .../Hashing/HashingAlgorithmSpecification.cs | 12 +- .../Hashing/HashingBinaryProcessor.cs | 31 - .../Hashing/HashingProcessor.cs | 158 ++++- .../Hashing/HashingStringProcessor.cs | 5 + .../Hashing/IHashingProcessor.cs | 109 ++-- .../Hashing/Pbkdf2/Pbkdf2HashAlgorithm.cs | 152 +++++ .../ISecureMemory.cs | 18 + .../Secrets/EncryptedExportedSecretVault.cs | 179 ++++++ .../Secrets/ExportedSecret.cs | 2 +- .../Secrets/ExportedSecretVault.cs | 98 ++++ .../Secrets/IEncryptedExportedSecretVault.cs | 72 +++ .../Secrets/IExportedSecret.cs | 2 +- .../Secrets/IExportedSecretVault.cs | 41 ++ .../Secrets/IPassword.cs | 106 ++++ .../IPasswordCompositionRequirements.cs | 69 +++ .../Secrets/IReadOnlySecret.cs | 26 + .../Secrets/ISecretManager.cs | 244 ++++++++ .../Secrets/ISecretReader.cs | 236 ++++++++ .../Secrets/ISecretVault.cs | 539 +----------------- .../Secrets/ISecretVaultBasicInformation.cs | 46 ++ .../Secrets/ISecretWriter.cs | 164 ++++++ .../Secrets/Password.cs | 480 ++++++++++++++++ .../PasswordCompositionRequirements.cs | 170 ++++++ .../Secrets/Secret.cs | 189 ++++-- .../Secrets/SecretVault.cs | 515 ++++++++++++----- .../Secrets/StringSecret.cs | 58 +- .../SecureMemory.cs | 268 +++++++-- .../Symmetric/CascadingSymmetricKey.cs | 41 +- .../Symmetric/SymmetricKey.cs | 55 +- .../Symmetric/SymmetricProcessor.cs | 19 +- .../ByteCollectionExtensionsTests.cs | 35 ++ .../Hashing/HashingProcessorTests.cs | 64 ++- .../Secrets/PasswordTests.cs | 303 ++++++++++ .../Secrets/SecretTests.cs | 9 + .../Secrets/SecretVaultTests.cs | 204 ++++--- .../SecureMemoryTests.cs | 13 +- .../Symmetric/CascadingSymmetricKeyTests.cs | 9 +- .../Symmetric/SymmetricKeyTests.cs | 50 +- 41 files changed, 3799 insertions(+), 1036 deletions(-) delete mode 100644 src/RapidField.SolidInstruments.Cryptography/Hashing/HashingBinaryProcessor.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Hashing/Pbkdf2/Pbkdf2HashAlgorithm.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Secrets/EncryptedExportedSecretVault.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Secrets/ExportedSecretVault.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Secrets/IEncryptedExportedSecretVault.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Secrets/IExportedSecretVault.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Secrets/IPassword.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Secrets/IPasswordCompositionRequirements.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretManager.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretReader.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretVaultBasicInformation.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretWriter.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Secrets/Password.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Secrets/PasswordCompositionRequirements.cs create mode 100644 test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/PasswordTests.cs diff --git a/en-US_User.dic b/en-US_User.dic index 1d611e4d..ca1db95b 100644 --- a/en-US_User.dic +++ b/en-US_User.dic @@ -29,6 +29,7 @@ cbmm cbyd ccm cd +Centre checksums choco Chocolatey @@ -47,6 +48,7 @@ cryptographically csharp csprng css +Cyber dal datetimerange ddd @@ -66,6 +68,7 @@ Deserialize deserialized Deserializes Destructors +deterministically di docfx docurl @@ -108,6 +111,7 @@ hotfix href html iframe +iloveyou img initializer initializers @@ -181,6 +185,7 @@ prerelease psake quantized que +qwertyuiop rabbitmq readonly Recurse diff --git a/src/RapidField.SolidInstruments.Core/Extensions/ByteCollectionExtensions.cs b/src/RapidField.SolidInstruments.Core/Extensions/ByteCollectionExtensions.cs index 06872b30..7e4f5d2e 100644 --- a/src/RapidField.SolidInstruments.Core/Extensions/ByteCollectionExtensions.cs +++ b/src/RapidField.SolidInstruments.Core/Extensions/ByteCollectionExtensions.cs @@ -232,6 +232,17 @@ public static Byte[] PerformCircularBitShift(this Byte[] target, BitShiftDirecti return outputBuffer; } + /// + /// Converts the current to its textual Base64 representation. + /// + /// + /// The current instance of the . + /// + /// + /// A Base64 string representation of the byte collection. + /// + public static String ToBase64String(this IEnumerable target) => Convert.ToBase64String(target.ToArray()); + /// /// Computes a 128-bit hash using the bytes in the current . /// diff --git a/src/RapidField.SolidInstruments.Cryptography/Extensions/HashingAlgorithmSpecificationExtensions.cs b/src/RapidField.SolidInstruments.Cryptography/Extensions/HashingAlgorithmSpecificationExtensions.cs index 7252c9dc..30e122ca 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Extensions/HashingAlgorithmSpecificationExtensions.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Extensions/HashingAlgorithmSpecificationExtensions.cs @@ -2,7 +2,9 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= +using RapidField.SolidInstruments.Core; using RapidField.SolidInstruments.Cryptography.Hashing; +using RapidField.SolidInstruments.Cryptography.Hashing.Pbkdf2; using System; using System.Diagnostics; using System.Security.Cryptography; @@ -28,10 +30,11 @@ internal static class HashingAlgorithmSpecificationExtensions { HashingAlgorithmSpecification.Unspecified => default, HashingAlgorithmSpecification.Md5 => 128, + HashingAlgorithmSpecification.Pbkdf2 => Pbkdf2HashAlgorithm.DigestLengthInBits, HashingAlgorithmSpecification.ShaTwo256 => 256, HashingAlgorithmSpecification.ShaTwo384 => 384, HashingAlgorithmSpecification.ShaTwo512 => 512, - _ => throw new ArgumentException($"{target} is not a supported {nameof(HashingAlgorithmSpecification)}.", nameof(target)) + _ => throw new UnsupportedSpecificationException($"{target} is not a supported {nameof(HashingAlgorithmSpecification)}.") }; /// @@ -48,10 +51,31 @@ internal static class HashingAlgorithmSpecificationExtensions { HashingAlgorithmSpecification.Unspecified => null, HashingAlgorithmSpecification.Md5 => MD5.Create(), + HashingAlgorithmSpecification.Pbkdf2 => Pbkdf2HashAlgorithm.Create(), HashingAlgorithmSpecification.ShaTwo256 => SHA256.Create(), HashingAlgorithmSpecification.ShaTwo384 => SHA384.Create(), HashingAlgorithmSpecification.ShaTwo512 => SHA512.Create(), - _ => throw new ArgumentException($"{target} is not a supported {nameof(HashingAlgorithmSpecification)}.", nameof(target)) + _ => throw new UnsupportedSpecificationException($"{target} is not a supported {nameof(HashingAlgorithmSpecification)}.") + }; + + /// + /// Returns a new matching the current . + /// + /// + /// The current . + /// + /// + /// A new matching the current . + /// + [DebuggerHidden] + internal static HashAlgorithmName ToHashAlgorithmName(this HashingAlgorithmSpecification target) => target switch + { + HashingAlgorithmSpecification.Unspecified => default, + HashingAlgorithmSpecification.Md5 => HashAlgorithmName.MD5, + HashingAlgorithmSpecification.ShaTwo256 => HashAlgorithmName.SHA256, + HashingAlgorithmSpecification.ShaTwo384 => HashAlgorithmName.SHA384, + HashingAlgorithmSpecification.ShaTwo512 => HashAlgorithmName.SHA512, + _ => throw new UnsupportedSpecificationException($"{target} is not a supported {nameof(HashingAlgorithmSpecification)}.") }; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Hashing/HashingAlgorithmSpecification.cs b/src/RapidField.SolidInstruments.Cryptography/Hashing/HashingAlgorithmSpecification.cs index 6c7b7849..ab9f60dd 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Hashing/HashingAlgorithmSpecification.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Hashing/HashingAlgorithmSpecification.cs @@ -21,19 +21,25 @@ public enum HashingAlgorithmSpecification : Byte /// Md5 = 0x01, + /// + /// Specifies the PBKDF2 key-derivation function (PRF: 512-bit SHA-2, 256-bit salt) using 17,711 iterations to produce a + /// 256-bit digest. + /// + Pbkdf2 = 0x02, + /// /// Specifies the SHA-2 hashing algorithm using a 256-bit digest. /// - ShaTwo256 = 0x02, + ShaTwo256 = 0x03, /// /// Specifies the SHA-2 hashing algorithm using a 384-bit digest. /// - ShaTwo384 = 0x03, + ShaTwo384 = 0x04, /// /// Specifies the SHA-2 hashing algorithm using a 512-bit digest. /// - ShaTwo512 = 0x04 + ShaTwo512 = 0x05 } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Hashing/HashingBinaryProcessor.cs b/src/RapidField.SolidInstruments.Cryptography/Hashing/HashingBinaryProcessor.cs deleted file mode 100644 index 3d31e08f..00000000 --- a/src/RapidField.SolidInstruments.Cryptography/Hashing/HashingBinaryProcessor.cs +++ /dev/null @@ -1,31 +0,0 @@ -// ================================================================================================================================= -// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. -// ================================================================================================================================= - -using RapidField.SolidInstruments.Serialization; -using System; -using System.Security.Cryptography; - -namespace RapidField.SolidInstruments.Cryptography.Hashing -{ - /// - /// Provides facilities for hashing byte arrays. - /// - public sealed class HashingBinaryProcessor : HashingProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// - /// A random number generator that is used to generate salt values. - /// - /// - /// is . - /// - public HashingBinaryProcessor(RandomNumberGenerator randomnessProvider) - : base(randomnessProvider, new PassThroughSerializer()) - { - return; - } - } -} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Hashing/HashingProcessor.cs b/src/RapidField.SolidInstruments.Cryptography/Hashing/HashingProcessor.cs index 8d80de53..85b30efc 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Hashing/HashingProcessor.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Hashing/HashingProcessor.cs @@ -2,6 +2,7 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= +using RapidField.SolidInstruments.Core; using RapidField.SolidInstruments.Core.ArgumentValidation; using RapidField.SolidInstruments.Cryptography.Extensions; using RapidField.SolidInstruments.Serialization; @@ -13,6 +14,47 @@ namespace RapidField.SolidInstruments.Cryptography.Hashing { + /// + /// Provides facilities for hashing byte arrays. + /// + /// + /// is the default implementation of . + /// + public sealed class HashingProcessor : HashingProcessor, IHashingProcessor + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A random number generator that is used to generate salt values. + /// + /// + /// is . + /// + public HashingProcessor(RandomNumberGenerator randomnessProvider) + : base(randomnessProvider, new PassThroughSerializer()) + { + return; + } + + /// + /// Creates a new for the specified serializable type. + /// + /// + /// The serializable object type that the processor can hash. + /// + /// + /// A new for the specified serializable type. + /// + public static IHashingProcessor ForType() + where T : class => new HashingProcessor(); + + /// + /// Represents a singleton instance of the class. + /// + public static readonly IHashingProcessor Instance = new HashingProcessor(HardenedRandomNumberGenerator.Instance); + } + /// /// Provides facilities for hashing typed objects and byte arrays. /// @@ -25,6 +67,21 @@ namespace RapidField.SolidInstruments.Cryptography.Hashing public class HashingProcessor : IHashingProcessor where T : class { + /// + /// Initializes a new instance of the class. + /// + /// + /// A random number generator that is used to generate salt values. + /// + /// + /// is . + /// + public HashingProcessor(RandomNumberGenerator randomnessProvider) + : this(randomnessProvider, DefaultSerializer) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -70,6 +127,16 @@ public HashingProcessor(RandomNumberGenerator randomnessProvider, ISerializer SaltLengthInBytes = saltLengthInBytes.RejectIf().IsLessThan(1, nameof(saltLengthInBytes)); } + /// + /// Initializes a new instance of the class. + /// + [DebuggerHidden] + internal HashingProcessor() + : this(HardenedRandomNumberGenerator.Instance) + { + return; + } + /// /// Calculates a hash value for the specified plaintext byte array. /// @@ -82,6 +149,15 @@ public HashingProcessor(RandomNumberGenerator randomnessProvider, ISerializer /// /// The resulting hash value. /// + /// + /// is equal to . + /// + /// + /// is empty. + /// + /// + /// is . + /// /// /// An exception was raised during hashing or serialization. /// @@ -103,20 +179,30 @@ public HashingProcessor(RandomNumberGenerator randomnessProvider, ISerializer /// /// The resulting hash value. /// + /// + /// is equal to . + /// + /// + /// is empty. + /// + /// + /// is . + /// /// /// An exception was raised during hashing or serialization. /// public Byte[] CalculateHash(Byte[] plaintext, HashingAlgorithmSpecification algorithm, Byte[] salt) { + var applySalt = (salt is null == false); + var saltLengthInBytes = (applySalt ? salt.Length : 0); + var plaintextLengthInBytes = plaintext.RejectIf().IsNullOrEmpty(nameof(plaintext)).TargetArgument.Length; + var saltedPlaintextLengthInBytes = (plaintextLengthInBytes + saltLengthInBytes); + var digestLengthInBytes = (algorithm.RejectIf().IsEqualToValue(HashingAlgorithmSpecification.Unspecified, nameof(algorithm)).TargetArgument.ToDigestBitLength() / 8); + var hashLengthInBytes = (digestLengthInBytes + saltLengthInBytes); + var hashValue = new Byte[hashLengthInBytes]; + try { - var applySalt = (salt is null == false); - var saltLengthInBytes = (applySalt ? salt.Length : 0); - var plaintextLengthInBytes = plaintext.Length; - var saltedPlaintextLengthInBytes = (plaintextLengthInBytes + saltLengthInBytes); - var digestLengthInBytes = (algorithm.ToDigestBitLength() / 8); - var hashLengthInBytes = (digestLengthInBytes + saltLengthInBytes); - var hashValue = new Byte[hashLengthInBytes]; Byte[] processedPlaintextBytes; if (applySalt) @@ -163,6 +249,12 @@ public Byte[] CalculateHash(Byte[] plaintext, HashingAlgorithmSpecification algo /// /// The resulting hash value. /// + /// + /// is equal to . + /// + /// + /// is . + /// /// /// An exception was raised during hashing or serialization. /// @@ -183,6 +275,14 @@ public Byte[] CalculateHash(T plaintextObject, HashingAlgorithmSpecification alg return CalculateHash(plaintextObject, algorithm, null); } } + catch (ArgumentNullException) + { + throw; + } + catch (ArgumentException) + { + throw; + } catch { throw new SecurityException("The hashing operation failed."); @@ -208,6 +308,15 @@ public Byte[] CalculateHash(T plaintextObject, HashingAlgorithmSpecification alg /// if the resulting hash value matches , otherwise /// . /// + /// + /// is equal to . + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// /// /// An exception was raised during hashing or serialization. /// @@ -215,7 +324,7 @@ public Boolean EvaluateHash(Byte[] hash, T plaintextObject, HashingAlgorithmSpec { try { - var digestLength = (algorithm.ToDigestBitLength() / 8); + var digestLength = (algorithm.RejectIf().IsEqualToValue(HashingAlgorithmSpecification.Unspecified, nameof(algorithm)).TargetArgument.ToDigestBitLength() / 8); Byte[] processedHash; Byte[] calculatedHash; @@ -224,7 +333,7 @@ public Boolean EvaluateHash(Byte[] hash, T plaintextObject, HashingAlgorithmSpec case SaltingMode.Salted: var salt = new Byte[SaltLengthInBytes]; - processedHash = hash.Take(digestLength).ToArray(); + processedHash = hash.RejectIf().IsNullOrEmpty(nameof(hash)).TargetArgument.Take(digestLength).ToArray(); salt = hash.Skip(digestLength).Take(SaltLengthInBytes).ToArray(); calculatedHash = CalculateHash(plaintextObject, algorithm, salt).Take(digestLength).ToArray(); break; @@ -257,6 +366,18 @@ public Boolean EvaluateHash(Byte[] hash, T plaintextObject, HashingAlgorithmSpec return true; } + catch (ArgumentEmptyException) + { + throw; + } + catch (ArgumentNullException) + { + throw; + } + catch (ArgumentException) + { + throw; + } catch { throw new SecurityException("The hashing operation failed."); @@ -278,8 +399,17 @@ public Boolean EvaluateHash(Byte[] hash, T plaintextObject, HashingAlgorithmSpec /// /// The resulting hash value. /// + /// + /// is equal to . + /// + /// + /// is . + /// + /// + /// An exception was raised during hashing or serialization. + /// [DebuggerHidden] - private Byte[] CalculateHash(T plaintextObject, HashingAlgorithmSpecification algorithm, Byte[] salt) => CalculateHash(Serializer.Serialize(plaintextObject), algorithm, salt); + private Byte[] CalculateHash(T plaintextObject, HashingAlgorithmSpecification algorithm, Byte[] salt) => CalculateHash(Serializer.Serialize(plaintextObject.RejectIf().IsNull(nameof(plaintextObject)).TargetArgument), algorithm, salt); /// /// Gets the salt length, in bytes, to use when calculating and evaluating hash values. @@ -293,7 +423,13 @@ public Int32 SaltLengthInBytes /// Represents the default salt length, in bytes, to use when calculating and evaluating hash values. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const Int32 DefaultSaltLengthInBytes = 8; + private const Int32 DefaultSaltLengthInBytes = 16; + + /// + /// Represents the default serializer that is used to transform plaintext. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static readonly ISerializer DefaultSerializer = new CompressedJsonSerializer(); /// /// Represents a random number generator that is used to generate salt values. diff --git a/src/RapidField.SolidInstruments.Cryptography/Hashing/HashingStringProcessor.cs b/src/RapidField.SolidInstruments.Cryptography/Hashing/HashingStringProcessor.cs index 05da1142..5cad949f 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Hashing/HashingStringProcessor.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Hashing/HashingStringProcessor.cs @@ -27,5 +27,10 @@ public HashingStringProcessor(RandomNumberGenerator randomnessProvider) { return; } + + /// + /// Represents a singleton instance of the class. + /// + public static readonly IHashingProcessor Instance = new HashingStringProcessor(HardenedRandomNumberGenerator.Instance); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Hashing/IHashingProcessor.cs b/src/RapidField.SolidInstruments.Cryptography/Hashing/IHashingProcessor.cs index ad19c2ad..fd174f40 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Hashing/IHashingProcessor.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Hashing/IHashingProcessor.cs @@ -2,105 +2,140 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= +using RapidField.SolidInstruments.Core; using System; using System.Security; namespace RapidField.SolidInstruments.Cryptography.Hashing { + /// + /// Provides facilities for hashing byte arrays. + /// + public interface IHashingProcessor : IHashingProcessor + { + } + /// /// Provides facilities for hashing typed objects and byte arrays. /// /// /// The type of the object that can be hashed. /// - public interface IHashingProcessor : IHashingProcessor + public interface IHashingProcessor { /// - /// Calculates a hash value for the specified plaintext object. + /// Calculates a hash value for the specified plaintext byte array. /// - /// - /// The plaintext object to hash. + /// + /// The plaintext byte array to hash. /// /// /// The algorithm specification used to transform the plaintext. /// - /// - /// A value specifying whether or not salt is applied to the plaintext. - /// /// /// The resulting hash value. /// + /// + /// is equal to . + /// + /// + /// is empty. + /// + /// + /// is . + /// /// /// An exception was raised during hashing or serialization. /// - public Byte[] CalculateHash(T plaintextObject, HashingAlgorithmSpecification algorithm, SaltingMode saltingMode); + public Byte[] CalculateHash(Byte[] plaintext, HashingAlgorithmSpecification algorithm); /// - /// Calculates a hash value for the specified plaintext object and compares the result with the specified hash value. + /// Calculates a hash value for the specified plaintext byte array. /// - /// - /// The hash value to evaluate. - /// - /// - /// The plaintext object to evaluate. + /// + /// The plaintext byte array to hash. /// /// /// The algorithm specification used to transform the plaintext. /// - /// - /// A value specifying whether or not salt is applied to the plaintext. + /// + /// The salt to apply to the plaintext, or if the plaintext is unsalted. The default value is + /// . /// /// - /// if the resulting hash value matches , otherwise - /// . + /// The resulting hash value. /// + /// + /// is equal to . + /// + /// + /// is empty. + /// + /// + /// is . + /// /// /// An exception was raised during hashing or serialization. /// - public Boolean EvaluateHash(Byte[] hash, T plaintextObject, HashingAlgorithmSpecification algorithm, SaltingMode saltingMode); - } + public Byte[] CalculateHash(Byte[] plaintext, HashingAlgorithmSpecification algorithm, Byte[] salt); - /// - /// Provides facilities for hashing typed objects and byte arrays. - /// - public interface IHashingProcessor - { /// - /// Calculates a hash value for the specified plaintext byte array. + /// Calculates a hash value for the specified plaintext object. /// - /// - /// The plaintext byte array to hash. + /// + /// The plaintext object to hash. /// /// /// The algorithm specification used to transform the plaintext. /// + /// + /// A value specifying whether or not salt is applied to the plaintext. + /// /// /// The resulting hash value. /// + /// + /// is equal to . + /// + /// + /// is . + /// /// /// An exception was raised during hashing or serialization. /// - public Byte[] CalculateHash(Byte[] plaintext, HashingAlgorithmSpecification algorithm); + public Byte[] CalculateHash(T plaintextObject, HashingAlgorithmSpecification algorithm, SaltingMode saltingMode); /// - /// Calculates a hash value for the specified plaintext byte array. + /// Calculates a hash value for the specified plaintext object and compares the result with the specified hash value. /// - /// - /// The plaintext byte array to hash. + /// + /// The hash value to evaluate. + /// + /// + /// The plaintext object to evaluate. /// /// /// The algorithm specification used to transform the plaintext. /// - /// - /// The salt to apply to the plaintext, or if the plaintext is unsalted. The default value is - /// . + /// + /// A value specifying whether or not salt is applied to the plaintext. /// /// - /// The resulting hash value. + /// if the resulting hash value matches , otherwise + /// . /// + /// + /// is equal to . + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// /// /// An exception was raised during hashing or serialization. /// - public Byte[] CalculateHash(Byte[] plaintext, HashingAlgorithmSpecification algorithm, Byte[] salt); + public Boolean EvaluateHash(Byte[] hash, T plaintextObject, HashingAlgorithmSpecification algorithm, SaltingMode saltingMode); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Hashing/Pbkdf2/Pbkdf2HashAlgorithm.cs b/src/RapidField.SolidInstruments.Cryptography/Hashing/Pbkdf2/Pbkdf2HashAlgorithm.cs new file mode 100644 index 00000000..0ebc3fe0 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Hashing/Pbkdf2/Pbkdf2HashAlgorithm.cs @@ -0,0 +1,152 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Collections; +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.Extensions; +using RapidField.SolidInstruments.Cryptography.Extensions; +using System; +using System.Diagnostics; +using System.Linq; +using System.Security.Cryptography; + +namespace RapidField.SolidInstruments.Cryptography.Hashing.Pbkdf2 +{ + /// + /// Represents a implementation of the PBKDF2 key derivation function. + /// + internal sealed class Pbkdf2HashAlgorithm : HashAlgorithm + { + /// + /// Initializes a new instance of the class. + /// + [DebuggerHidden] + private Pbkdf2HashAlgorithm() + : base() + { + return; + } + + /// + /// Resets the hash algorithm to its initial state. + /// + public override void Initialize() => HashValue = Array.Empty(); + + /// + /// Creates a new instance of the class. + /// + /// + /// A new instance of the class. + /// + [DebuggerHidden] + internal static new Pbkdf2HashAlgorithm Create() => new Pbkdf2HashAlgorithm(); + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Routes data written to the object into the hash algorithm. + /// + /// + /// The input to compute the hash code for. + /// + /// + /// The offset into the byte array from which to begin using data. + /// + /// + /// The number of bytes in the byte array to use as data. + /// + protected override void HashCore(Byte[] array, Int32 ibStart, Int32 cbSize) + { + using var plaintext = new PinnedMemory(array.Skip(ibStart).Take(cbSize).ToArray(), true); + using var salt = DeriveSaltValue(plaintext); + using var keyDerivationFunction = new Rfc2898DeriveBytes(plaintext, salt, IterationCount, KeyDerivationHashAlgorithm.ToHashAlgorithmName()); + HashValue = keyDerivationFunction.GetBytes(DigestLengthInBytes); + } + + /// + /// Finalizes the hash computation after the last data is processed by the cryptographic stream object. + /// + /// + /// The computed hash code. + /// + protected override Byte[] HashFinal() => HashValue; + + /// + /// Derives a deterministic 256-bit salt value from the specified plaintext bytes. + /// + /// + /// Salt bytes are derived deterministically because handles salt generation and + /// application. Therefore, contains both plaintext and salt bytes and the PBKDF2 salt input + /// can safely be derived from them. + /// + /// + /// Plaintext bytes from which to derive a salt value. + /// + /// + /// The derived salt bytes. + /// + [DebuggerHidden] + private static PinnedMemory DeriveSaltValue(Byte[] plaintext) + { + using var saltDerivationAlgorithm = SaltDerivationHashAlgorithm.ToHashAlgorithm(); + var saltBytes = saltDerivationAlgorithm.ComputeHash(plaintext); + saltBytes.ReverseStaggerSort(); + saltBytes = saltBytes.PerformCircularBitShift(SaltDerivationBitShiftDirection, SaltDerivationBitShiftCount); + return new PinnedMemory(saltBytes, true); + } + + /// + /// Gets the length, in bits, of digests produced by instances. + /// + public override Int32 HashSize => DigestLengthInBits; + + /// + /// Represents the length, in bits, of digests produced by instances. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal const Int32 DigestLengthInBits = 256; + + /// + /// Represents the length, in bytes, of digests produced by instances. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Int32 DigestLengthInBytes = DigestLengthInBits / 8; + + /// + /// Represents the number of PBKDF2 iterations to perform. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Int32 IterationCount = 17711; + + /// + /// Represents the hashing algorithm that is used to derive PBKDF2 key bytes from the plaintext. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const HashingAlgorithmSpecification KeyDerivationHashAlgorithm = HashingAlgorithmSpecification.ShaTwo512; + + /// + /// Represents the bit shift count that is used to derive PBKDF2 salt bytes from the plaintext. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Int32 SaltDerivationBitShiftCount = 5; + + /// + /// Represents the bit shift direction that is used to derive PBKDF2 salt bytes from the plaintext. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const BitShiftDirection SaltDerivationBitShiftDirection = BitShiftDirection.Right; + + /// + /// Represents the hashing algorithm that is used to derive PBKDF2 salt bytes from the plaintext. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const HashingAlgorithmSpecification SaltDerivationHashAlgorithm = HashingAlgorithmSpecification.ShaTwo256; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/ISecureMemory.cs b/src/RapidField.SolidInstruments.Cryptography/ISecureMemory.cs index 2c9f1376..bb87cfbe 100644 --- a/src/RapidField.SolidInstruments.Cryptography/ISecureMemory.cs +++ b/src/RapidField.SolidInstruments.Cryptography/ISecureMemory.cs @@ -46,6 +46,15 @@ public interface ISecureMemory : IAsyncDisposable, IDisposable /// public Task AccessAsync(Action action); + /// + /// Regenerates and replaces the source bytes for the private key that is used to secure the current + /// . + /// + /// + /// The object is disposed. + /// + internal void RegeneratePrivateKey(); + /// /// Gets the length of the bit field, in bytes. /// @@ -53,5 +62,14 @@ public Int32 LengthInBytes { get; } + + /// + /// Gets the current zero-based ordinal version of the bits comprising the private key that is used to secure the current + /// . + /// + internal UInt32 PrivateKeyVersion + { + get; + } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/EncryptedExportedSecretVault.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/EncryptedExportedSecretVault.cs new file mode 100644 index 00000000..7bade6e7 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/EncryptedExportedSecretVault.cs @@ -0,0 +1,179 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Cryptography.Symmetric; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.Serialization; +using System.Security; + +namespace RapidField.SolidInstruments.Cryptography.Secrets +{ + /// + /// Represents a symmetrically encrypted . + /// + /// + /// is the default implementation of . + /// + [DataContract] + public sealed class EncryptedExportedSecretVault : EncryptedModel, IEncryptedExportedSecretVault + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The exported secret collection which is encrypted and wrapped by the model. + /// + /// + /// The key that is used to encrypt . + /// + /// + /// The name of the secret associated with . + /// + /// + /// is empty. + /// + /// + /// is -or- is + /// + /// -or- is . + /// + /// + /// An exception was raised during encryption or serialization. + /// + [DebuggerHidden] + internal EncryptedExportedSecretVault(ExportedSecretVault exportedSecretVault, ISymmetricKey key, String keyName) + : this(SymmetricProcessor.ForType().Encrypt(exportedSecretVault.RejectIf().IsNull(nameof(exportedSecretVault)), key.RejectIf().IsNull(nameof(key)).TargetArgument), keyName) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The exported secret collection which is encrypted and wrapped by the model. + /// + /// + /// The key that is used to encrypt . + /// + /// + /// The name of the secret associated with . + /// + /// + /// is empty. + /// + /// + /// is -or- is + /// + /// -or- is . + /// + /// + /// An exception was raised during encryption or serialization. + /// + [DebuggerHidden] + internal EncryptedExportedSecretVault(ExportedSecretVault exportedSecretVault, ICascadingSymmetricKey key, String keyName) + : this(SymmetricProcessor.ForType().Encrypt(exportedSecretVault.RejectIf().IsNull(nameof(exportedSecretVault)), key.RejectIf().IsNull(nameof(key)).TargetArgument), keyName) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The ciphertext of the exported secret. + /// + /// + /// The name of the secret associated with the key that was used to produce . + /// + /// + /// is empty. + /// + /// + /// is + /// -or- is . + /// + [DebuggerHidden] + private EncryptedExportedSecretVault(Byte[] ciphertext, String keyName) + : base(ciphertext) + { + KeyName = keyName.RejectIf().IsNullOrEmpty(nameof(keyName)); + } + + /// + /// Decrypts and reconstitutes the exported secrets. + /// + /// + /// The key that is used to decrypt the exported secrets. + /// + /// + /// The plaintext secrets. + /// + /// + /// is . + /// + /// + /// The type assembly or one of its dependencies is invalid. + /// + /// + /// The type assembly could not be loaded. + /// + /// + /// An exception was raised during decryption or deserialization. + /// + public IEnumerable ToPlaintextSecrets(ISymmetricKey key) => ToPlaintextModel(key).GetExportedSecrets().Select(exportedSecret => exportedSecret.ToSecret()); + + /// + /// Decrypts and reconstitutes the exported secrets. + /// + /// + /// The key that is used to decrypt the exported secrets. + /// + /// + /// The plaintext secrets. + /// + /// + /// is . + /// + /// + /// The type assembly or one of its dependencies is invalid. + /// + /// + /// The type assembly could not be loaded. + /// + /// + /// An exception was raised during decryption or deserialization. + /// + public IEnumerable ToPlaintextSecrets(ICascadingSymmetricKey key) => ToPlaintextModel(key).GetExportedSecrets().Select(exportedSecret => exportedSecret.ToSecret()); + + /// + /// Gets or sets the name of the secret associated with the key that was used to encrypt the exported secrets. + /// + /// + /// The specified string is empty. + /// + /// + /// The specified string is . + /// + [DataMember] + public String KeyName + { + get => KeyNameReference; + set => KeyNameReference = value.RejectIf().IsNullOrEmpty(nameof(KeyName)); + } + + /// + /// Represents the name of the secret associated with the key that was used to encrypt the exported secrets. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + [IgnoreDataMember] + private String KeyNameReference; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/ExportedSecret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/ExportedSecret.cs index e6da2f2a..a2ab1bf3 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/ExportedSecret.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/ExportedSecret.cs @@ -15,7 +15,7 @@ namespace RapidField.SolidInstruments.Cryptography.Secrets { /// - /// Represents a serializable that was exported from an . + /// Represents a serializable that was exported from an . /// /// /// is the default implementation of . diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/ExportedSecretVault.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/ExportedSecretVault.cs new file mode 100644 index 00000000..b2b06c47 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/ExportedSecretVault.cs @@ -0,0 +1,98 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.Serialization; + +namespace RapidField.SolidInstruments.Cryptography.Secrets +{ + /// + /// Represents a serializable collection of objects that were exported from an + /// . + /// + /// + /// is the default implementation of . + /// + [DataContract] + public sealed class ExportedSecretVault : Model, IExportedSecretVault + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The unique semantic identifier for the associated . + /// + /// + /// The secrets that were exported from the associated . + /// + /// + /// contains one or more elements. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + [DebuggerHidden] + internal ExportedSecretVault(String identifier, IEnumerable secrets) + : base() + { + Identifier = identifier.RejectIf().IsNullOrEmpty(nameof(identifier)); + Secrets = new List(secrets.RejectIf().IsNull(nameof(secrets)).OrIf(collection => collection.Any(secret => secret is null), nameof(secrets), "The specified secret collection contains one or more null elements.").TargetArgument); + } + + /// + /// Returns the collection of secrets that were exported from the associated . + /// + /// + /// The secrets that were exported from the associated . + /// + public IEnumerable GetExportedSecrets() => Secrets ?? new List(); + + /// + /// Gets or sets the unique semantic identifier for the associated . + /// + /// + /// The specified string is empty. + /// + /// + /// The specified string is . + /// + [DataMember] + public String Identifier + { + get => IdentifierReference; + set => IdentifierReference = value.RejectIf().IsNullOrEmpty(nameof(Identifier)); + } + + /// + /// Gets the number of secrets that were exported from the associated . + /// + [IgnoreDataMember] + public Int32 SecretCount => Secrets?.Count ?? 0; + + /// + /// Gets or sets the secrets that were exported from the associated . + /// + [DataMember] + public List Secrets + { + get; + set; + } + + /// + /// Represents the unique semantic identifier for the associated . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + [IgnoreDataMember] + private String IdentifierReference; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/IEncryptedExportedSecretVault.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/IEncryptedExportedSecretVault.cs new file mode 100644 index 00000000..7f8f2d3d --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/IEncryptedExportedSecretVault.cs @@ -0,0 +1,72 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Cryptography.Symmetric; +using System; +using System.Collections.Generic; +using System.IO; +using System.Security; + +namespace RapidField.SolidInstruments.Cryptography.Secrets +{ + /// + /// Represents a symmetrically encrypted . + /// + public interface IEncryptedExportedSecretVault : IEncryptedModel + { + /// + /// Decrypts and reconstitutes the exported secrets. + /// + /// + /// The key that is used to decrypt the exported secrets. + /// + /// + /// The plaintext secrets. + /// + /// + /// is . + /// + /// + /// The type assembly or one of its dependencies is invalid. + /// + /// + /// The type assembly could not be loaded. + /// + /// + /// An exception was raised during decryption or deserialization. + /// + public IEnumerable ToPlaintextSecrets(ISymmetricKey key); + + /// + /// Decrypts and reconstitutes the exported secrets. + /// + /// + /// The key that is used to decrypt the exported secrets. + /// + /// + /// The plaintext secrets. + /// + /// + /// is . + /// + /// + /// The type assembly or one of its dependencies is invalid. + /// + /// + /// The type assembly could not be loaded. + /// + /// + /// An exception was raised during decryption or deserialization. + /// + public IEnumerable ToPlaintextSecrets(ICascadingSymmetricKey key); + + /// + /// Gets the name of the secret associated with the key that was used to encrypt the exported secrets. + /// + public String KeyName + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/IExportedSecret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/IExportedSecret.cs index 0ec10fd3..363f6500 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/IExportedSecret.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/IExportedSecret.cs @@ -9,7 +9,7 @@ namespace RapidField.SolidInstruments.Cryptography.Secrets { /// - /// Represents a serializable that was exported from an . + /// Represents a serializable that was exported from an . /// public interface IExportedSecret : IModel { diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/IExportedSecretVault.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/IExportedSecretVault.cs new file mode 100644 index 00000000..eecbdf9f --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/IExportedSecretVault.cs @@ -0,0 +1,41 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using System; +using System.Collections.Generic; + +namespace RapidField.SolidInstruments.Cryptography.Secrets +{ + /// + /// Represents a serializable collection of objects that were exported from an + /// . + /// + public interface IExportedSecretVault : IModel + { + /// + /// Returns the collection of secrets that were exported from the associated . + /// + /// + /// The secrets that were exported from the associated . + /// + public IEnumerable GetExportedSecrets(); + + /// + /// Gets the unique semantic identifier for the associated . + /// + public String Identifier + { + get; + } + + /// + /// Gets the number of secrets that were exported from the associated . + /// + public Int32 SecretCount + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/IPassword.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/IPassword.cs new file mode 100644 index 00000000..8c98cd0d --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/IPassword.cs @@ -0,0 +1,106 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Collections; +using RapidField.SolidInstruments.Core; +using System; + +namespace RapidField.SolidInstruments.Cryptography.Secrets +{ + /// + /// Represents a named textual password that is pinned in memory and encrypted at rest. + /// + public interface IPassword : IReadOnlySecret + { + /// + /// Calculates a cryptographically secure salted hash value for the current using the PBKDF2 + /// algorithm and returns it as a Base64 string. + /// + /// + /// A Base64-encoded digest for the current . + /// + /// + /// The object is disposed. + /// + public String CalculateSecureHashString(); + + /// + /// Calculates a cryptographically secure salted hash value for the current using the PBKDF2 + /// algorithm. + /// + /// + /// A digest for the current . + /// + /// + /// The object is disposed. + /// + public IReadOnlyPinnedMemory CalculateSecureHashValue(); + + /// + /// Calculates a cryptographically secure salted hash value for the current using the PBKDF2 + /// algorithm and compares the result with the specified Base64-encoded digest. + /// + /// + /// The salted hash string to evaluate. + /// + /// + /// if the resulting hash value matches , otherwise + /// . + /// + /// + /// is empty. + /// + /// + /// is . + /// + public Boolean EvaluateSecureHashString(String hashString); + + /// + /// Calculates a cryptographically secure salted hash value for the current using the PBKDF2 + /// algorithm and compares the result with the specified salted hash value. + /// + /// + /// The salted hash value to evaluate. + /// + /// + /// if the resulting hash value matches , otherwise + /// . + /// + /// + /// is empty. + /// + /// + /// is . + /// + public Boolean EvaluateSecureHashValue(Byte[] hashValue); + + /// + /// Returns the number of characters comprising the current . + /// + /// + /// The character length of the current . + /// + /// + /// The object is disposed. + /// + public Int32 GetCharacterLength(); + + /// + /// Determines whether or not the current complies with the specified composition requirements. + /// + /// + /// The password composition requirements against which the password is evaluated. + /// + /// + /// if the password is in compliance, otherwise . + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public Boolean MeetsRequirements(IPasswordCompositionRequirements requirements); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/IPasswordCompositionRequirements.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/IPasswordCompositionRequirements.cs new file mode 100644 index 00000000..20b5bcd6 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/IPasswordCompositionRequirements.cs @@ -0,0 +1,69 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using System; + +namespace RapidField.SolidInstruments.Cryptography.Secrets +{ + /// + /// Represents a definition for the minimum permissible complexity of an . + /// + public interface IPasswordCompositionRequirements : IModel + { + /// + /// Gets a value indicating whether or not the use of passwords which frequently appear in breach publications should be + /// forbidden. + /// + public Boolean ForbidCommonBreachedPasswords + { + get; + } + + /// + /// Gets the minimum number of lowercase Latin alphabetic characters which an must contain, or zero + /// if no such characters are required. + /// + public Int32 MinimumLowercaseAlphabeticCharacterCount + { + get; + } + + /// + /// Gets the minimum number of non-alphanumeric characters which an must contain, or zero if no + /// such characters are required. + /// + public Int32 MinimumNonAlphanumericCharacterCount + { + get; + } + + /// + /// Gets the minimum number of numeric characters which an must contain, or zero if no such + /// characters are required. + /// + public Int32 MinimumNumericCharacterCount + { + get; + } + + /// + /// Gets the minimum number of total characters which an must contain, or zero if no total length + /// requirement is imposed. + /// + public Int32 MinimumTotalCharacterCount + { + get; + } + + /// + /// Gets the minimum number of uppercase Latin alphabetic characters which an must contain, or zero + /// if no such characters are required. + /// + public Int32 MinimumUppercaseAlphabeticCharacterCount + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/IReadOnlySecret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/IReadOnlySecret.cs index 2cd09df4..d7b4107b 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/IReadOnlySecret.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/IReadOnlySecret.cs @@ -116,6 +116,23 @@ public interface IReadOnlySecret : IAsyncDisposable, IDisposable /// public Task ReadAsync(Action> readAction); + /// + /// Regenerates and replaces the in-memory key that is used to secure the current . + /// + /// + /// The object is disposed. + /// + internal void RegenerateInMemoryKey(); + + /// + /// Gets a globally unique identifier that is derived from a cryptographically secure hash of the secret value, or + /// if is . + /// + public Guid DerivedIdentity + { + get; + } + /// /// Gets a value indicating whether or not the current has a value. /// @@ -139,5 +156,14 @@ public Type ValueType { get; } + + /// + /// Gets the date and time when the in-memory key that is used to secure the current + /// was generated. + /// + internal DateTime InMemoryKeyTimeStamp + { + get; + } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretManager.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretManager.cs new file mode 100644 index 00000000..ce2d6dea --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretManager.cs @@ -0,0 +1,244 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using System; +using System.Threading.Tasks; + +namespace RapidField.SolidInstruments.Cryptography.Secrets +{ + /// + /// Represents a management facility for named secret values which are encrypted and pinned in memory at rest. + /// + public interface ISecretManager : ISecretVaultBasicInformation + { + /// + /// Removes and safely disposes of all secrets that are stored by the current . + /// + /// + /// The object is disposed. + /// + public void Clear(); + + /// + /// Asynchronously exports the specified secret and encrypts it using the vault's master key. + /// + /// + /// The textual name of the secret to export. + /// + /// + /// A task representing the asynchronous operation and containing the exported encrypted secret. + /// + /// + /// is empty. + /// + /// + /// The secret vault does not contain a secret with the specified name. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during encryption or serialization. + /// + public Task ExportEncryptedSecretAsync(String name); + + /// + /// Asynchronously exports the specified secret and encrypts it using the specified key. + /// + /// + /// The textual name of the secret to export. + /// + /// + /// The name of the secret associated with a key that is used to encrypt the exported secret. + /// + /// + /// A task representing the asynchronous operation and containing the exported encrypted secret. + /// + /// + /// is empty -or- is empty. + /// + /// + /// The secret vault does not contain a secret with the specified name -or- the secret vault does not contain a key with the + /// specified name. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during encryption or serialization. + /// + public Task ExportEncryptedSecretAsync(String name, String keyName); + + /// + /// Asynchronously exports the vault's master key in plaintext form. + /// + /// + /// A task representing the asynchronous operation and containing the exported master key. + /// + /// + /// The object is disposed. + /// + public Task ExportMasterKeyAsync(); + + /// + /// Asynchronously exports the specified secret in plaintext form. + /// + /// + /// The textual name of the secret to export. + /// + /// + /// A task representing the asynchronous operation and containing the exported plaintext secret. + /// + /// + /// is empty. + /// + /// + /// The secret vault does not contain a secret with the specified name. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public Task ExportSecretAsync(String name); + + /// + /// Decrypts the specified secret using the master key of the current and imports it. + /// + /// + /// When using this method, note that the master key of the current must match the key that + /// was used when exporting the secret. If the exporting vault is not the current , the master + /// keys will need to have been synchronized beforehand. + /// + /// + /// The encrypted secret to import. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during decryption or deserialization. + /// + public void ImportEncryptedSecret(IEncryptedExportedSecret secret); + + /// + /// Decrypts the specified secret using the specified key and imports it. + /// + /// + /// The encrypted secret to import. + /// + /// + /// The name of the secret associated with a key that was used to encrypt the exported secret. + /// + /// + /// is empty. + /// + /// + /// The secret vault does not contain a key with the specified name. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during decryption or deserialization. + /// + public void ImportEncryptedSecret(IEncryptedExportedSecret secret, String keyName); + + /// + /// Decrypts the specified secret vault using the master key of the current and imports it. + /// + /// + /// When using this method, note that the master key of the current must match the key that + /// was used when exporting the secret vault. If the exporting vault is not the current , the + /// master keys will need to have been synchronized beforehand. + /// + /// + /// The encrypted secret vault to import. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during decryption or deserialization. + /// + public void ImportEncryptedSecretVault(IEncryptedExportedSecretVault secretVault); + + /// + /// Decrypts the specified secret vault using the specified key and imports it. + /// + /// + /// The encrypted secret vault to import. + /// + /// + /// The name of the secret associated with a key that was used to encrypt the exported secret vault. + /// + /// + /// is empty. + /// + /// + /// The secret vault does not contain a key with the specified name. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during decryption or deserialization. + /// + public void ImportEncryptedSecretVault(IEncryptedExportedSecretVault secretVault, String keyName); + + /// + /// Imports the specified secret in plaintext form. + /// + /// + /// The plaintext secret to import. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public void ImportSecret(IExportedSecret secret); + + /// + /// Attempts to remove a secret with the specified name. + /// + /// + /// A textual name that uniquely identifies the target secret. + /// + /// + /// if the secret was removed, otherwise . + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public Boolean TryRemove(String name); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretReader.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretReader.cs new file mode 100644 index 00000000..672f5a13 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretReader.cs @@ -0,0 +1,236 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Collections; +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Cryptography.Symmetric; +using System; +using System.Security.Cryptography.X509Certificates; +using System.Threading.Tasks; + +namespace RapidField.SolidInstruments.Cryptography.Secrets +{ + /// + /// Represents a read facility for named secret values which are encrypted and pinned in memory at rest. + /// + public interface ISecretReader : ISecretVaultBasicInformation + { + /// + /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read + /// operation against it as a thread-safe, atomic operation. + /// + /// + /// A textual name that uniquely identifies the target secret. + /// + /// + /// The read operation to perform. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is empty. + /// + /// + /// The secret vault does not contain a secret with the specified name. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// raised an exception -or- the secret vault does not contain a valid secret of the + /// specified type. + /// + public Task ReadAsync(String name, Action> readAction); + + /// + /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read + /// operation against it as a thread-safe, atomic operation. + /// + /// + /// A textual name that uniquely identifies the target secret. + /// + /// + /// The read operation to perform. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is empty. + /// + /// + /// The secret vault does not contain a secret with the specified name. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// raised an exception -or- the secret vault does not contain a valid secret of the + /// specified type. + /// + public Task ReadAsync(String name, Action readAction); + + /// + /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read + /// operation against it as a thread-safe, atomic operation. + /// + /// + /// A textual name that uniquely identifies the target secret. + /// + /// + /// The read operation to perform. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is empty. + /// + /// + /// The secret vault does not contain a secret with the specified name. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// raised an exception -or- the secret vault does not contain a valid secret of the + /// specified type. + /// + public Task ReadAsync(String name, Action readAction); + + /// + /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read + /// operation against it as a thread-safe, atomic operation. + /// + /// + /// A textual name that uniquely identifies the target secret. + /// + /// + /// The read operation to perform. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is empty. + /// + /// + /// The secret vault does not contain a secret with the specified name. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// raised an exception -or- the secret vault does not contain a valid secret of the + /// specified type. + /// + public Task ReadAsync(String name, Action readAction); + + /// + /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read + /// operation against it as a thread-safe, atomic operation. + /// + /// + /// A textual name that uniquely identifies the target secret. + /// + /// + /// The read operation to perform. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is empty. + /// + /// + /// The secret vault does not contain a secret with the specified name. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// raised an exception -or- the secret vault does not contain a valid secret of the + /// specified type. + /// + public Task ReadAsync(String name, Action readAction); + + /// + /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read + /// operation against it as a thread-safe, atomic operation. + /// + /// + /// A textual name that uniquely identifies the target secret. + /// + /// + /// The read operation to perform. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is empty. + /// + /// + /// The secret vault does not contain a secret with the specified name. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// raised an exception -or- the secret vault does not contain a valid secret of the + /// specified type. + /// + public Task ReadAsync(String name, Action readAction); + + /// + /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read + /// operation against it as a thread-safe, atomic operation. + /// + /// + /// A textual name that uniquely identifies the target secret. + /// + /// + /// The read operation to perform. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is empty. + /// + /// + /// The secret vault does not contain a secret with the specified name. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// raised an exception -or- the secret vault does not contain a valid secret of the + /// specified type. + /// + public Task ReadAsync(String name, Action readAction); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretVault.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretVault.cs index b57e9029..d7ddd3a8 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretVault.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretVault.cs @@ -2,12 +2,8 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= -using RapidField.SolidInstruments.Collections; using RapidField.SolidInstruments.Core; -using RapidField.SolidInstruments.Cryptography.Symmetric; using System; -using System.Collections.Generic; -using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; namespace RapidField.SolidInstruments.Cryptography.Secrets @@ -15,561 +11,46 @@ namespace RapidField.SolidInstruments.Cryptography.Secrets /// /// Represents a secure container for named secret values which are encrypted and pinned in memory at rest. /// - public interface ISecretVault : IAsyncDisposable, IDisposable + public interface ISecretVault : ISecretManager, ISecretReader, ISecretWriter { /// - /// Adds the specified secret using the specified name to the current , or updates it if a secret - /// with the same name already exists. + /// Asynchronously exports the current and encrypts it using the vault's master key. /// - /// - /// A textual name that uniquely identifies . - /// - /// - /// The secret value. - /// - /// - /// is empty. - /// - /// - /// is -or- is . - /// - /// - /// The object is disposed. - /// - public void AddOrUpdate(String name, Byte[] secret); - - /// - /// Adds the specified secret using the specified name to the current , or updates it if a secret - /// with the same name already exists. - /// - /// - /// A textual name that uniquely identifies . - /// - /// - /// The secret value. - /// - /// - /// is empty. - /// - /// - /// is -or- is . - /// - /// - /// The object is disposed. - /// - public void AddOrUpdate(String name, String secret); - - /// - /// Adds the specified secret using the specified name to the current , or updates it if a secret - /// with the same name already exists. - /// - /// - /// A textual name that uniquely identifies . - /// - /// - /// The secret value. - /// - /// - /// is empty. - /// - /// - /// is . - /// - /// - /// The object is disposed. - /// - public void AddOrUpdate(String name, Guid secret); - - /// - /// Adds the specified secret using the specified name to the current , or updates it if a secret - /// with the same name already exists. - /// - /// - /// A textual name that uniquely identifies . - /// - /// - /// The secret value. - /// - /// - /// is empty. - /// - /// - /// is . - /// - /// - /// The object is disposed. - /// - public void AddOrUpdate(String name, Double secret); - - /// - /// Adds the specified secret using the specified name to the current , or updates it if a secret - /// with the same name already exists. - /// - /// - /// A textual name that uniquely identifies . - /// - /// - /// The secret value. - /// - /// - /// is empty. - /// - /// - /// is -or- is . - /// - /// - /// The object is disposed. - /// - public void AddOrUpdate(String name, SymmetricKey secret); - - /// - /// Adds the specified secret using the specified name to the current , or updates it if a secret - /// with the same name already exists. - /// - /// - /// A textual name that uniquely identifies . - /// - /// - /// The secret value. - /// - /// - /// is empty. - /// - /// - /// is -or- is . - /// - /// - /// The object is disposed. - /// - public void AddOrUpdate(String name, CascadingSymmetricKey secret); - - /// - /// Adds the specified secret using the specified name to the current , or updates it if a secret - /// with the same name already exists. - /// - /// - /// A textual name that uniquely identifies . - /// - /// - /// The secret value. - /// - /// - /// is empty. - /// - /// - /// is -or- is . - /// - /// - /// The object is disposed. - /// - public void AddOrUpdate(String name, X509Certificate2 secret); - - /// - /// Removes and safely disposes of all secrets that are stored by the current . - /// - /// - /// The object is disposed. - /// - public void Clear(); - - /// - /// Asynchronously exports the specified secret and encrypts it using the vault's master key. - /// - /// - /// The textual name of the secret to export. - /// /// - /// A task representing the asynchronous operation and containing the exported encrypted secret. + /// A task representing the asynchronous operation and containing the exported encrypted secrets. /// - /// - /// is empty. - /// - /// - /// The secret vault does not contain a secret with the specified name. - /// - /// - /// is . - /// /// /// The object is disposed. /// /// /// An exception was raised during encryption or serialization. /// - public Task ExportEncryptedSecretAsync(String name); + public Task ExportAsync(); /// - /// Asynchronously exports the specified secret and encrypts it using the specified key. + /// Asynchronously exports the current and encrypts it using the specified key. /// - /// - /// The textual name of the secret to export. - /// /// - /// The name of the secret associated with a key that is used to encrypt the exported secret. - /// - /// - /// A task representing the asynchronous operation and containing the exported encrypted secret. - /// - /// - /// is empty -or- is empty. - /// - /// - /// The secret vault does not contain a secret with the specified name -or- the secret vault does not contain a key with the - /// specified name. - /// - /// - /// is -or- is . - /// - /// - /// The object is disposed. - /// - /// - /// An exception was raised during encryption or serialization. - /// - public Task ExportEncryptedSecretAsync(String name, String keyName); - - /// - /// Asynchronously exports the specified secret in plaintext form. - /// - /// - /// The textual name of the secret to export. + /// The name of the secret associated with a key that is used to encrypt the exported secrets. /// /// - /// A task representing the asynchronous operation and containing the exported plaintext secret. + /// A task representing the asynchronous operation and containing the exported encrypted secrets. /// /// - /// is empty. - /// - /// - /// The secret vault does not contain a secret with the specified name. - /// - /// - /// is . - /// - /// - /// The object is disposed. - /// - public Task ExportSecretAsync(String name); - - /// - /// Decrypts the specified secret using the vault's master key and imports it. - /// - /// - /// When using this method, note that the master key of the current must match the key that was - /// used when exporting the secret. If the exporting vault is not the current vault, the master keys will need to have been - /// synchronized beforehand. - /// - /// - /// The encrypted secret to import. - /// - /// - /// is . - /// - /// - /// The object is disposed. - /// - /// - /// An exception was raised during decryption or deserialization. - /// - public void ImportEncryptedSecret(IEncryptedExportedSecret secret); - - /// - /// Decrypts the specified secret using the specified key and imports it. - /// - /// - /// The encrypted secret to import. - /// - /// - /// The name of the secret associated with a key that was used to encrypt the exported secret. - /// - /// /// is empty. /// /// /// The secret vault does not contain a key with the specified name. /// /// - /// is -or- is . + /// is . /// /// /// The object is disposed. /// /// - /// An exception was raised during decryption or deserialization. - /// - public void ImportEncryptedSecret(IEncryptedExportedSecret secret, String keyName); - - /// - /// Imports the specified secret in plaintext form. - /// - /// - /// The plaintext secret to import. - /// - /// - /// is . - /// - /// - /// The object is disposed. - /// - public void ImportSecret(IExportedSecret secret); - - /// - /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read - /// operation against it as a thread-safe, atomic operation. - /// - /// - /// A textual name that uniquely identifies the target secret. - /// - /// - /// The read operation to perform. - /// - /// - /// A task representing the asynchronous operation. - /// - /// - /// is empty. - /// - /// - /// The secret vault does not contain a secret with the specified name. - /// - /// - /// is -or- is . - /// - /// - /// The object is disposed. - /// - /// - /// raised an exception -or- the secret vault does not contain a valid secret of the - /// specified type. - /// - public Task ReadAsync(String name, Action> readAction); - - /// - /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read - /// operation against it as a thread-safe, atomic operation. - /// - /// - /// A textual name that uniquely identifies the target secret. - /// - /// - /// The read operation to perform. - /// - /// - /// A task representing the asynchronous operation. - /// - /// - /// is empty. - /// - /// - /// The secret vault does not contain a secret with the specified name. - /// - /// - /// is -or- is . - /// - /// - /// The object is disposed. - /// - /// - /// raised an exception -or- the secret vault does not contain a valid secret of the - /// specified type. - /// - public Task ReadAsync(String name, Action readAction); - - /// - /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read - /// operation against it as a thread-safe, atomic operation. - /// - /// - /// A textual name that uniquely identifies the target secret. - /// - /// - /// The read operation to perform. - /// - /// - /// A task representing the asynchronous operation. - /// - /// - /// is empty. - /// - /// - /// The secret vault does not contain a secret with the specified name. - /// - /// - /// is -or- is . - /// - /// - /// The object is disposed. - /// - /// - /// raised an exception -or- the secret vault does not contain a valid secret of the - /// specified type. - /// - public Task ReadAsync(String name, Action readAction); - - /// - /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read - /// operation against it as a thread-safe, atomic operation. - /// - /// - /// A textual name that uniquely identifies the target secret. - /// - /// - /// The read operation to perform. - /// - /// - /// A task representing the asynchronous operation. - /// - /// - /// is empty. - /// - /// - /// The secret vault does not contain a secret with the specified name. - /// - /// - /// is -or- is . - /// - /// - /// The object is disposed. - /// - /// - /// raised an exception -or- the secret vault does not contain a valid secret of the - /// specified type. - /// - public Task ReadAsync(String name, Action readAction); - - /// - /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read - /// operation against it as a thread-safe, atomic operation. - /// - /// - /// A textual name that uniquely identifies the target secret. - /// - /// - /// The read operation to perform. - /// - /// - /// A task representing the asynchronous operation. - /// - /// - /// is empty. - /// - /// - /// The secret vault does not contain a secret with the specified name. - /// - /// - /// is -or- is . - /// - /// - /// The object is disposed. - /// - /// - /// raised an exception -or- the secret vault does not contain a valid secret of the - /// specified type. - /// - public Task ReadAsync(String name, Action readAction); - - /// - /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read - /// operation against it as a thread-safe, atomic operation. - /// - /// - /// A textual name that uniquely identifies the target secret. - /// - /// - /// The read operation to perform. - /// - /// - /// A task representing the asynchronous operation. - /// - /// - /// is empty. - /// - /// - /// The secret vault does not contain a secret with the specified name. - /// - /// - /// is -or- is . - /// - /// - /// The object is disposed. - /// - /// - /// raised an exception -or- the secret vault does not contain a valid secret of the - /// specified type. - /// - public Task ReadAsync(String name, Action readAction); - - /// - /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read - /// operation against it as a thread-safe, atomic operation. - /// - /// - /// A textual name that uniquely identifies the target secret. - /// - /// - /// The read operation to perform. - /// - /// - /// A task representing the asynchronous operation. - /// - /// - /// is empty. - /// - /// - /// The secret vault does not contain a secret with the specified name. - /// - /// - /// is -or- is . - /// - /// - /// The object is disposed. - /// - /// - /// raised an exception -or- the secret vault does not contain a valid secret of the - /// specified type. - /// - public Task ReadAsync(String name, Action readAction); - - /// - /// Attempts to remove a secret with the specified name. - /// - /// - /// A textual name that uniquely identifies the target secret. - /// - /// - /// if the secret was removed, otherwise . - /// - /// - /// is empty. - /// - /// - /// is . - /// - /// - /// The object is disposed. - /// - public Boolean TryRemove(String name); - - /// - /// Gets the number of secrets that are stored by the current . - /// - /// - /// The object is disposed. - /// - public Int32 Count - { - get; - } - - /// - /// Gets the textual names that uniquely identify the secrets that are stored by the current . - /// - /// - /// The object is disposed. + /// An exception was raised during encryption or serialization. /// - public IEnumerable Names - { - get; - } + public Task ExportAsync(String keyName); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretVaultBasicInformation.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretVaultBasicInformation.cs new file mode 100644 index 00000000..aa1a64fb --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretVaultBasicInformation.cs @@ -0,0 +1,46 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Collections.Generic; + +namespace RapidField.SolidInstruments.Cryptography.Secrets +{ + /// + /// Represents basic information about a secure container for named secret values which are encrypted and pinned in memory at + /// rest. + /// + public interface ISecretVaultBasicInformation : IAsyncDisposable, IDisposable + { + /// + /// Gets the number of secrets that are stored by the . + /// + /// + /// The object is disposed. + /// + public Int32 Count + { + get; + } + + /// + /// Gets the unique semantic identifier for the . + /// + public String Identifier + { + get; + } + + /// + /// Gets the textual names that uniquely identify the secrets that are stored by the . + /// + /// + /// The object is disposed. + /// + public IEnumerable SecretNames + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretWriter.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretWriter.cs new file mode 100644 index 00000000..b75f598f --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretWriter.cs @@ -0,0 +1,164 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Cryptography.Symmetric; +using System; +using System.Security.Cryptography.X509Certificates; + +namespace RapidField.SolidInstruments.Cryptography.Secrets +{ + /// + /// Represents a write facility for named secret values which are encrypted and pinned in memory at rest. + /// + public interface ISecretWriter : ISecretVaultBasicInformation + { + /// + /// Adds the specified secret using the specified name to the , or updates it if a secret with + /// the same name already exists. + /// + /// + /// A textual name that uniquely identifies . + /// + /// + /// The secret value. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + public void AddOrUpdate(String name, Byte[] secret); + + /// + /// Adds the specified secret using the specified name to the , or updates it if a secret with + /// the same name already exists. + /// + /// + /// A textual name that uniquely identifies . + /// + /// + /// The secret value. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + public void AddOrUpdate(String name, String secret); + + /// + /// Adds the specified secret using the specified name to the , or updates it if a secret with + /// the same name already exists. + /// + /// + /// A textual name that uniquely identifies . + /// + /// + /// The secret value. + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public void AddOrUpdate(String name, Guid secret); + + /// + /// Adds the specified secret using the specified name to the , or updates it if a secret with + /// the same name already exists. + /// + /// + /// A textual name that uniquely identifies . + /// + /// + /// The secret value. + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public void AddOrUpdate(String name, Double secret); + + /// + /// Adds the specified secret using the specified name to the , or updates it if a secret with + /// the same name already exists. + /// + /// + /// A textual name that uniquely identifies . + /// + /// + /// The secret value. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + public void AddOrUpdate(String name, SymmetricKey secret); + + /// + /// Adds the specified secret using the specified name to the , or updates it if a secret with + /// the same name already exists. + /// + /// + /// A textual name that uniquely identifies . + /// + /// + /// The secret value. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + public void AddOrUpdate(String name, CascadingSymmetricKey secret); + + /// + /// Adds the specified secret using the specified name to the , or updates it if a secret with + /// the same name already exists. + /// + /// + /// A textual name that uniquely identifies . + /// + /// + /// The secret value. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + public void AddOrUpdate(String name, X509Certificate2 secret); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/Password.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/Password.cs new file mode 100644 index 00000000..44c7891a --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/Password.cs @@ -0,0 +1,480 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Collections; +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Core.Extensions; +using RapidField.SolidInstruments.Cryptography.Extensions; +using RapidField.SolidInstruments.Cryptography.Hashing; +using System; +using System.Diagnostics; +using System.Linq; +using System.Text; + +namespace RapidField.SolidInstruments.Cryptography.Secrets +{ + /// + /// Represents a named textual password that is pinned in memory and encrypted at rest. + /// + /// + /// is the default implementation of . + /// + public sealed class Password : StringSecret, IPassword + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A textual name that uniquely identifies the secret. + /// + /// + /// The encoding that is use when converting the password string to and from bytes. The default value is + /// . + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + [DebuggerHidden] + private Password(String name, Encoding encoding) + : base(name, encoding) + { + return; + } + + /// + /// Creates a new using the specified ASCII password string. + /// + /// + /// The plaintext password. + /// + /// + /// A new . + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// contains invalid ASCII characters. + /// + public static Password FromAsciiString(String password) => FromAsciiString(password, NewDefaultPasswordName()); + + /// + /// Creates a new using the specified name and ASCII password string. + /// + /// + /// The plaintext password. + /// + /// + /// A textual name that uniquely identifies the password. + /// + /// + /// A new . + /// + /// + /// is empty -or- is empty. + /// + /// + /// is -or- is . + /// + /// + /// contains invalid ASCII characters. + /// + public static Password FromAsciiString(String password, String name) => FromString(password, name, Encoding.ASCII); + + /// + /// Creates a new using the specified Unicode password string. + /// + /// + /// The plaintext password. + /// + /// + /// A new . + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// contains invalid Unicode characters. + /// + public static Password FromUnicodeString(String password) => FromUnicodeString(password, NewDefaultPasswordName()); + + /// + /// Creates a new using the specified name and Unicode password string. + /// + /// + /// The plaintext password. + /// + /// + /// A textual name that uniquely identifies the password. + /// + /// + /// A new . + /// + /// + /// is empty -or- is empty. + /// + /// + /// is -or- is . + /// + /// + /// contains invalid Unicode characters. + /// + public static Password FromUnicodeString(String password, String name) => FromString(password, name, Encoding.Unicode); + + /// + /// Creates a new using the specified UTF-8 password string. + /// + /// + /// The plaintext password. + /// + /// + /// A new . + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// contains invalid UTF-8 characters. + /// + public static Password FromUtf8String(String password) => FromUtf8String(password, NewDefaultPasswordName()); + + /// + /// Creates a new using the specified name and UTF-8 password string. + /// + /// + /// The plaintext password. + /// + /// + /// A textual name that uniquely identifies the password. + /// + /// + /// A new . + /// + /// + /// is empty -or- is empty. + /// + /// + /// is -or- is . + /// + /// + /// contains invalid UTF-8 characters. + /// + public static Password FromUtf8String(String password, String name) => FromString(password, name, Encoding.UTF8); + + /// + /// Generates a random, strong password that complies with . + /// + /// + /// A new random, strong password. + /// + public static Password NewRandomStrongPassword() + { + while (true) + { + var plaintextPassword = HardenedRandomNumberGenerator.Instance.GetString(RandomStrongPasswordLengthLowerBoundary, RandomStrongPasswordLengthUpperBoundary, false, true, true, true, true, false, false); + var password = FromAsciiString(plaintextPassword); + + if (password.MeetsRequirements(PasswordCompositionRequirements.Strict)) + { + return password; + } + + password.Dispose(); + } + } + + /// + /// Calculates a cryptographically secure salted hash value for the current using the PBKDF2 + /// algorithm and returns it as a Base64 string. + /// + /// + /// A Base64-encoded digest for the current . + /// + /// + /// The object is disposed. + /// + public String CalculateSecureHashString() + { + using (var hashValue = CalculateSecureHashValue()) + { + return hashValue.ToBase64String(); + } + } + + /// + /// Calculates a cryptographically secure salted hash value for the current using the PBKDF2 + /// algorithm. + /// + /// + /// A digest for the current . + /// + /// + /// The object is disposed. + /// + public IReadOnlyPinnedMemory CalculateSecureHashValue() + { + var digest = (Byte[])null; + + Read((IReadOnlyPinnedMemory plaintextPassword) => + { + digest = HashingProcessor.Instance.CalculateHash(plaintextPassword.ToArray(), HashingAlgorithm, SaltingMode.Salted); + }); + + return new PinnedMemory(digest); + } + + /// + /// Calculates a cryptographically secure salted hash value for the current using the PBKDF2 + /// algorithm and compares the result with the specified Base64-encoded digest. + /// + /// + /// The salted hash string to evaluate. + /// + /// + /// if the resulting hash value matches , otherwise + /// . + /// + /// + /// is empty. + /// + /// + /// is . + /// + public Boolean EvaluateSecureHashString(String hashString) => EvaluateSecureHashValue(Convert.FromBase64String(hashString.RejectIf().IsNullOrEmpty(nameof(hashString)))); + + /// + /// Calculates a cryptographically secure salted hash value for the current using the PBKDF2 + /// algorithm and compares the result with the specified salted hash value. + /// + /// + /// The salted hash value to evaluate. + /// + /// + /// if the resulting hash value matches , otherwise + /// . + /// + /// + /// is empty. + /// + /// + /// is . + /// + public Boolean EvaluateSecureHashValue(Byte[] hashValue) + { + hashValue = hashValue.RejectIf().IsNullOrEmpty(nameof(hashValue)); + var hashValueMatchesPassword = default(Boolean); + + Read((IReadOnlyPinnedMemory plaintextPassword) => + { + hashValueMatchesPassword = HashingProcessor.Instance.EvaluateHash(hashValue, plaintextPassword.ToArray(), HashingAlgorithm, SaltingMode.Salted); + }); + + return hashValueMatchesPassword; + } + + /// + /// Returns the number of characters comprising the current . + /// + /// + /// The character length of the current . + /// + /// + /// The object is disposed. + /// + public Int32 GetCharacterLength() + { + var characterLength = default(Int32); + + Read((String plaintextPassword) => + { + characterLength = plaintextPassword.Length; + }); + + return characterLength; + } + + /// + /// Determines whether or not the current complies with the specified composition requirements. + /// + /// + /// The password composition requirements against which the password is evaluated. + /// + /// + /// if the password is in compliance, otherwise . + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public Boolean MeetsRequirements(IPasswordCompositionRequirements requirements) + { + requirements = requirements.RejectIf().IsNull(nameof(requirements)).TargetArgument; + var totalCharacterCount = 0; + var lowercaseAlphabeticCharacterCount = 0; + var uppercaseAlphabeticCharacterCount = 0; + var numericCharacterCount = 0; + var nonAlphanumericCharacterCount = 0; + var matchesCaseInsensitiveCommonBreachedPassword = false; + + Read((String plaintextPassword) => + { + totalCharacterCount = plaintextPassword.Length; + + if (totalCharacterCount == 0) + { + return; + } + + lowercaseAlphabeticCharacterCount = plaintextPassword.Count(character => character.IsLowercaseAlphabetic()); + uppercaseAlphabeticCharacterCount = plaintextPassword.Count(character => character.IsUppercaseAlphabetic()); + numericCharacterCount = plaintextPassword.Count(character => character.IsNumeric()); + nonAlphanumericCharacterCount = plaintextPassword.Count(character => character.IsSymbolic() || character.IsWhiteSpaceCharacter()); + matchesCaseInsensitiveCommonBreachedPassword = PasswordCompositionRequirements.CommonBreachedPasswords.Contains(plaintextPassword.ToLower()); + }); + + if (totalCharacterCount < requirements.MinimumTotalCharacterCount) + { + return false; + } + else if (lowercaseAlphabeticCharacterCount < requirements.MinimumLowercaseAlphabeticCharacterCount) + { + return false; + } + else if (uppercaseAlphabeticCharacterCount < requirements.MinimumUppercaseAlphabeticCharacterCount) + { + return false; + } + else if (numericCharacterCount < requirements.MinimumNumericCharacterCount) + { + return false; + } + else if (nonAlphanumericCharacterCount < requirements.MinimumNonAlphanumericCharacterCount) + { + return false; + } + else if (requirements.ForbidCommonBreachedPasswords && matchesCaseInsensitiveCommonBreachedPassword) + { + return false; + } + + return true; + } + + /// + /// Returns a new byte array containing the plaintext password bytes for the specified , using the + /// originally-specified text encoding. + /// + /// + /// A new byte array containing the plaintext password bytes. + /// + /// + /// is . + /// + /// + /// is disposed. + /// + [DebuggerHidden] + internal static Byte[] GetPasswordPlaintextBytes(IPassword password) + { + var byteArray = (Byte[])null; + + password.RejectIf().IsNull(nameof(password)).TargetArgument.Read((IReadOnlyPinnedMemory plaintextBytes) => + { + byteArray = plaintextBytes.ReadOnlySpan.ToArray(); + }); + + return byteArray; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Creates a new using the specified name and value. + /// + /// + /// The plaintext password. + /// + /// + /// A textual name that uniquely identifies the password. + /// + /// + /// The encoding that is use when converting the password string to and from bytes. + /// + /// + /// A new . + /// + /// + /// is empty -or- is empty. + /// + /// + /// is -or- is . + /// + /// + /// contains invalid characters for . + /// + [DebuggerHidden] + private static Password FromString(String password, String name, Encoding encoding) + { + password = password.RejectIf().IsNullOrEmpty(nameof(password)); + var secret = new Password(name, encoding); + secret.Write(() => password); + return secret; + } + + /// + /// Generates a new, unique default password name. + /// + /// + /// A new, unique default password name. + /// + [DebuggerHidden] + private static String NewDefaultPasswordName() => $"{DefaultPasswordNamePrefix}{NewRandomSemanticIdentifier()}"; + + /// + /// Represents the default textual prefix for names. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal const String DefaultPasswordNamePrefix = "__Password-"; + + /// + /// Represents the hashing algorithm that is used to produce cryptographically secure digests for + /// instances. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const HashingAlgorithmSpecification HashingAlgorithm = HashingAlgorithmSpecification.Pbkdf2; + + /// + /// Represents the lower boundary for the length of randomly-generated strong passwords. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static readonly Int32 RandomStrongPasswordLengthLowerBoundary = PasswordCompositionRequirements.Strict.MinimumTotalCharacterCount; + + /// + /// Represents the upper boundary for the length of randomly-generated strong passwords. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static readonly Int32 RandomStrongPasswordLengthUpperBoundary = RandomStrongPasswordLengthLowerBoundary + 5; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/PasswordCompositionRequirements.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/PasswordCompositionRequirements.cs new file mode 100644 index 00000000..e212d034 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/PasswordCompositionRequirements.cs @@ -0,0 +1,170 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace RapidField.SolidInstruments.Cryptography.Secrets +{ + /// + /// Represents a definition for the composition requirements of an . + /// + /// + /// is the default implementation of + /// . + /// + [DataContract] + public sealed class PasswordCompositionRequirements : Model, IPasswordCompositionRequirements + { + /// + /// Initializes a new instance of the class. + /// + public PasswordCompositionRequirements() + : base() + { + return; + } + + /// + /// Gets a that matches the requirements specified by NIST Special + /// Publication 800-63-3. + /// + [IgnoreDataMember] + public static PasswordCompositionRequirements Nist800633 => new PasswordCompositionRequirements() + { + ForbidCommonBreachedPasswords = true, + MinimumLowercaseAlphabeticCharacterCount = 0, + MinimumNonAlphanumericCharacterCount = 0, + MinimumNumericCharacterCount = 0, + MinimumTotalCharacterCount = 8, + MinimumUppercaseAlphabeticCharacterCount = 0 + }; + + /// + /// Gets a that specifies no requirements. + /// + [IgnoreDataMember] + public static PasswordCompositionRequirements None => new PasswordCompositionRequirements() + { + ForbidCommonBreachedPasswords = false, + MinimumLowercaseAlphabeticCharacterCount = 0, + MinimumNonAlphanumericCharacterCount = 0, + MinimumNumericCharacterCount = 0, + MinimumTotalCharacterCount = 0, + MinimumUppercaseAlphabeticCharacterCount = 0 + }; + + /// + /// Gets a that requires 13 or more total characters with at least one + /// character in every category and forbids commonly breached passwords. + /// + [IgnoreDataMember] + public static PasswordCompositionRequirements Strict => new PasswordCompositionRequirements() + { + ForbidCommonBreachedPasswords = true, + MinimumLowercaseAlphabeticCharacterCount = 1, + MinimumNonAlphanumericCharacterCount = 1, + MinimumNumericCharacterCount = 1, + MinimumTotalCharacterCount = 13, + MinimumUppercaseAlphabeticCharacterCount = 1 + }; + + /// + /// Gets or sets a value indicating whether or not the use of passwords which frequently appear in breach publications + /// should be forbidden. + /// + [DataMember] + public Boolean ForbidCommonBreachedPasswords + { + get; + set; + } + + /// + /// Gets or sets the minimum number of lowercase Latin alphabetic characters which an must contain, + /// or zero if no such characters are required. + /// + [DataMember] + public Int32 MinimumLowercaseAlphabeticCharacterCount + { + get; + set; + } + + /// + /// Gets or sets the minimum number of non-alphanumeric characters which an must contain, or zero + /// if no such characters are required. + /// + [DataMember] + public Int32 MinimumNonAlphanumericCharacterCount + { + get; + set; + } + + /// + /// Gets or sets the minimum number of numeric characters which an must contain, or zero if no such + /// characters are required. + /// + [DataMember] + public Int32 MinimumNumericCharacterCount + { + get; + set; + } + + /// + /// Gets or sets the minimum number of total characters which an must contain, or zero if no total + /// length requirement is imposed. + /// + [DataMember] + public Int32 MinimumTotalCharacterCount + { + get; + set; + } + + /// + /// Gets or sets the minimum number of uppercase Latin alphabetic characters which an must contain, + /// or zero if no such characters are required. + /// + [DataMember] + public Int32 MinimumUppercaseAlphabeticCharacterCount + { + get; + set; + } + + /// + /// Represents an ordered collection of the 20 most common lowercase passwords that appeared in breached password lists in + /// 2019, per the National Cyber Security Centre (NCSC). + /// + [IgnoreDataMember] + public static readonly IEnumerable CommonBreachedPasswords = new String[] + { + "123456", + "123456789", + "qwerty", + "password", + "1111111", + "12345678", + "abc123", + "1234567", + "password1", + "12345", + "1234567890", + "123123", + "0", + "iloveyou", + "1234", + "1q2w3e4r5t", + "qwertyuiop", + "123", + "monkey", + "dragon" + }; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/Secret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/Secret.cs index eb3ec130..0ff7e166 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/Secret.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/Secret.cs @@ -6,8 +6,12 @@ using RapidField.SolidInstruments.Core; using RapidField.SolidInstruments.Core.ArgumentValidation; using RapidField.SolidInstruments.Core.Concurrency; +using RapidField.SolidInstruments.Core.Extensions; +using RapidField.SolidInstruments.Cryptography.Extensions; +using RapidField.SolidInstruments.Cryptography.Hashing; using System; using System.Diagnostics; +using System.Text; using System.Threading.Tasks; namespace RapidField.SolidInstruments.Cryptography.Secrets @@ -126,6 +130,7 @@ protected Secret(String name) : base() { HasValue = false; + InMemoryKeyTimeStampValue = TimeStamp.Current; Name = name.RejectIf().IsNullOrEmpty(nameof(name)); SecureValueMemory = null; } @@ -136,17 +141,7 @@ protected Secret(String name) /// /// A 32-bit signed integer hash code. /// - public override Int32 GetHashCode() - { - var hashCode = (Name?.GetHashCode() ?? 0) ^ (ValueType?.FullName.GetHashCode() ?? 0); - - if (HasValue && SecureValueMemory is null == false) - { - hashCode ^= SecureValueMemory.GetHashCode(); - } - - return hashCode; - } + public override Int32 GetHashCode() => IsDisposedOrDisposing ? 0 : GetDerivedIdentity().GetHashCode(); /// /// Decrypts the secret value, pins it in memory and performs the specified read operation against the resulting bytes as a @@ -170,11 +165,9 @@ public override Int32 GetHashCode() /// public void Read(Action> readAction) { - using (var controlToken = StateControl.Enter()) - { - RejectIfDisposed(); - Read(readAction.RejectIf().IsNull(nameof(readAction)), controlToken); - } + using var controlToken = StateControl.Enter(); + RejectIfDisposed(); + Read(readAction.RejectIf().IsNull(nameof(readAction)), controlToken); } /// @@ -199,11 +192,9 @@ public void Read(Action> readAction) /// public void Read(Action readAction) { - using (var controlToken = StateControl.Enter()) - { - RejectIfDisposed(); - Read(readAction.RejectIf().IsNull(nameof(readAction)), controlToken); - } + using var controlToken = StateControl.Enter(); + RejectIfDisposed(); + Read(readAction.RejectIf().IsNull(nameof(readAction)), controlToken); } /// @@ -256,6 +247,28 @@ public void Read(Action readAction) /// public Task ReadAsync(Action readAction) => Task.Factory.StartNew(() => Read(readAction)); + /// + /// Regenerates and replaces the in-memory key that is used to secure the current . + /// + /// + /// The object is disposed. + /// + [DebuggerHidden] + void IReadOnlySecret.RegenerateInMemoryKey() + { + using var controlToken = StateControl.Enter(); + RejectIfDisposed(); + + try + { + SecureValueMemory.RegeneratePrivateKey(); + } + finally + { + InMemoryKeyTimeStampValue = TimeStamp.Current; + } + } + /// /// Converts the value of the current to its equivalent string representation. /// @@ -281,11 +294,9 @@ public void Read(Action readAction) /// public void Write(Func writeFunction) { - using (var controlToken = StateControl.Enter()) - { - RejectIfDisposed(); - Write(writeFunction.RejectIf().IsNull(nameof(writeFunction)), controlToken); - } + using var controlToken = StateControl.Enter(); + RejectIfDisposed(); + Write(writeFunction.RejectIf().IsNull(nameof(writeFunction)), controlToken); } /// @@ -309,6 +320,15 @@ public void Write(Func writeFunction) /// public Task WriteAsync(Func writeFunction) => Task.Factory.StartNew(() => Write(writeFunction)); + /// + /// Generates a new, unique textual identifier. + /// + /// + /// A new, unique textual identifier. + /// + [DebuggerHidden] + internal static String NewRandomSemanticIdentifier() => HardenedRandomNumberGenerator.Instance.GetString(8, false, true, false, true, false, false, false); + /// /// Writes and encrypts the specified value. /// @@ -321,10 +341,8 @@ public void Write(Func writeFunction) [DebuggerHidden] internal void Write(Byte[] value) { - using (var valueMemory = new ReadOnlyPinnedMemory(value)) - { - Write(valueMemory: valueMemory); - } + using var valueMemory = new ReadOnlyPinnedMemory(value); + Write(valueMemory: valueMemory); } /// @@ -369,6 +387,7 @@ protected override void Dispose(Boolean disposing) if (disposing) { SecureValueMemory?.Dispose(); + SecureValueMemory = null; } } finally @@ -377,6 +396,56 @@ protected override void Dispose(Boolean disposing) } } + /// + /// Gets a globally unique identifier that is derived from a cryptographically secure hash of the secret value and secret + /// name. + /// + /// + /// A that is derived from a cryptographically secure hash of the secret value and secret name. + /// + /// + /// The object is disposed. + /// + [DebuggerHidden] + private Guid GetDerivedIdentity() + { + RejectIfDisposed(); + var algorithm = DerivedIdentityHashingAlgorithm; + var hashLengthInBytes = algorithm.ToDigestBitLength() / 8; + var hashPairLengthInBytes = hashLengthInBytes * 2; + + using var hashPair = new PinnedMemory(hashPairLengthInBytes, true); + + if (Name.IsNullOrEmpty()) + { + DerivedIdentityEmptyValueHashBytes.CopyTo(hashPair, 0); + } + else + { + HashingProcessor.Instance.CalculateHash(Name.ToByteArray(Encoding.Unicode), algorithm).CopyTo(hashPair, 0); + } + + if (HasValue) + { + Read((IReadOnlyPinnedMemory value) => + { + if (value.IsNullOrEmpty()) + { + DerivedIdentityEmptyValueHashBytes.CopyTo(hashPair, hashLengthInBytes); + return; + } + + HashingProcessor.Instance.CalculateHash(value.ReadOnlySpan.ToArray(), algorithm).CopyTo(hashPair, hashLengthInBytes); + }); + } + else + { + DerivedIdentityEmptyValueHashBytes.CopyTo(hashPair, hashLengthInBytes); + } + + return hashPair.GenerateChecksumIdentity(); + } + /// /// Decrypts the secret value, pins it in memory and performs the specified read operation against the resulting bytes as a /// thread-safe, atomic operation. @@ -405,13 +474,10 @@ private void Read(Action> readAction, IConcurrencyCo { if (SecureValueMemory is null) { - using (var memory = new ReadOnlyPinnedMemory(0)) - { - // Because secure memory cannot have length zero, this is here to handle cases in which write operations - // produce empty memory. - readAction(memory); - } - + // Because secure memory cannot have length zero, this is here to handle cases in which write operations produce + // empty memory. + using var memory = new ReadOnlyPinnedMemory(0); + readAction(memory); return; } @@ -540,10 +606,8 @@ private void Write(Func writeFunction, IConcurrencyControlToken controlT throw new SecretAccessException("The specified write function produced a null secret value."); } - using (var valueMemory = ConvertValueToBytes(value, controlToken)) - { - Write(valueMemory); - } + using var valueMemory = ConvertValueToBytes(value, controlToken); + Write(valueMemory); } catch (ObjectDisposedException) { @@ -559,6 +623,15 @@ private void Write(Func writeFunction, IConcurrencyControlToken controlT } } + /// + /// Gets a globally unique identifier that is derived from a cryptographically secure hash of the secret value and secret + /// name. + /// + /// + /// The object is disposed. + /// + public Guid DerivedIdentity => GetDerivedIdentity(); + /// /// Gets a value indicating whether or not the current has a value. /// @@ -568,6 +641,13 @@ public Boolean HasValue private set; } + /// + /// Gets the date and time when the in-memory key that is used to secure the current was + /// generated. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + DateTime IReadOnlySecret.InMemoryKeyTimeStamp => InMemoryKeyTimeStampValue; + /// /// Gets a textual name that uniquely identifies the current . /// @@ -581,6 +661,33 @@ public String Name /// public Type ValueType => typeof(TValue); + /// + /// Represents the hashing algorithm that is used to produce . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const HashingAlgorithmSpecification DerivedIdentityHashingAlgorithm = HashingAlgorithmSpecification.Pbkdf2; + + /// + /// Represents an array of bytes that are used in place of plaintext bytes for producing when + /// the plaintext is empty. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static readonly Byte[] DerivedIdentityEmptyValueBytes = new Byte[] { 0xf0, 0x55, 0xcc, 0x99, 0x0f, 0xaa, 0x33, 0x66 }; + + /// + /// Represents an array of bytes that are used in place of ciphertext bytes for producing + /// when the plaintext is empty. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static readonly Byte[] DerivedIdentityEmptyValueHashBytes = HashingProcessor.Instance.CalculateHash(DerivedIdentityEmptyValueBytes, DerivedIdentityHashingAlgorithm); + + /// + /// Represents the date and time when the in-memory key that is used to secure the current was + /// generated. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private DateTime InMemoryKeyTimeStampValue; + /// /// Represents the encrypted field in which the secure value is stored. /// diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretVault.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretVault.cs index 0716b342..774eae1c 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretVault.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretVault.cs @@ -7,10 +7,12 @@ using RapidField.SolidInstruments.Core.ArgumentValidation; using RapidField.SolidInstruments.Core.Concurrency; using RapidField.SolidInstruments.Core.Extensions; +using RapidField.SolidInstruments.Cryptography.Extensions; using RapidField.SolidInstruments.Cryptography.Symmetric; using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; @@ -32,33 +34,32 @@ public SecretVault() { LazyReferenceManager = new Lazy(() => new ReferenceManager(), LazyThreadSafetyMode.ExecutionAndPublication); Secrets = new Dictionary(); + SemanticIdentity = Secret.NewRandomSemanticIdentifier(); } /// /// Initializes a new instance of the class. /// - /// + /// /// A 13+ character password from which a master key is derived, which is used as the default encryption key for exported /// secrets. A random master key is generated on demand if the parameterless constructor is used. /// - /// - /// is empty. - /// /// - /// is shorter than thirteen characters. + /// contains a string value that is shorter than thirteen characters. /// /// - /// is . + /// is . /// - public SecretVault(String password) + /// + /// is disposed. + /// + public SecretVault(IPassword masterPassword) : this() { - using (var masterKey = CascadingSymmetricKey.FromPassword(password)) - { - var masterKeySecret = CascadingSymmetricKeySecret.FromValue(MasterKeyName, masterKey); - ReferenceManager.AddObject(masterKeySecret); - Secrets.Add(masterKeySecret.Name, masterKeySecret); - } + using var masterKey = CascadingSymmetricKey.FromPassword(masterPassword); + var masterKeySecret = CascadingSymmetricKeySecret.FromValue(MasterKeyName, masterKey); + ReferenceManager.AddObject(masterKeySecret); + Secrets.Add(masterKeySecret.Name, masterKeySecret); } /// @@ -216,29 +217,95 @@ public SecretVault(String password) /// public void Clear() { - using (var controlToken = StateControl.Enter()) - { - RejectIfDisposed(); + using var controlToken = StateControl.Enter(); + RejectIfDisposed(); - try + try + { + foreach (var secret in Secrets.Values) { - foreach (var secret in Secrets.Values) + try { - try - { - secret?.Dispose(); - } - catch - { - continue; - } + secret?.Dispose(); + } + catch + { + continue; } } - finally - { - Secrets.Clear(); - } } + finally + { + Secrets.Clear(); + } + } + + /// + /// Asynchronously exports the current and encrypts it using the vault's master key. + /// + /// + /// A task representing the asynchronous operation and containing the exported encrypted secrets. + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during encryption or serialization. + /// + public Task ExportAsync() => ExportAsync(MasterKeyName); + + /// + /// Asynchronously exports the current and encrypts it using the specified key. + /// + /// + /// The name of the secret associated with a key that is used to encrypt the exported secrets. + /// + /// + /// A task representing the asynchronous operation and containing the exported encrypted secrets. + /// + /// + /// is empty. + /// + /// + /// The secret vault does not contain a key with the specified name. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during encryption or serialization. + /// + public async Task ExportAsync(String keyName) + { + var names = SecretNames.ToArray(); + var secrets = new List(); + + foreach (var name in names) + { + var exportedSecret = await ExportSecretAsync(name).ConfigureAwait(false); + secrets.Add(exportedSecret); + } + + var exportedSecretVault = new ExportedSecretVault(Identifier, secrets); + using var controlToken = StateControl.Enter(); + RejectIfDisposed(); + + if (Secrets.ContainsKey(keyName.RejectIf().IsNullOrEmpty(nameof(keyName)))) + { + return await ExportEncryptedSecretVaultAsync(exportedSecretVault, Secrets[keyName]).ConfigureAwait(false); + } + else if (keyName == MasterKeyName) + { + using var masterKey = CascadingSymmetricKey.New(); + var masterKeySecret = CascadingSymmetricKeySecret.FromValue(keyName, masterKey); + AddOrUpdate(MasterKeyName, masterKeySecret, controlToken); + return await ExportEncryptedSecretVaultAsync(exportedSecretVault, masterKeySecret).ConfigureAwait(false); + } + + throw new ArgumentException($"The secret vault does not contain a key with the specified name, \"{keyName}\".", nameof(keyName)); } /// @@ -305,26 +372,22 @@ public async Task ExportEncryptedSecretAsync(String nam try { - using (var controlToken = StateControl.Enter()) - { - RejectIfDisposed(); + using var controlToken = StateControl.Enter(); + RejectIfDisposed(); - if (Secrets.ContainsKey(keyName.RejectIf().IsNullOrEmpty(nameof(keyName)))) - { - return await ExportEncryptedSecretAsync(exportedSecret, Secrets[keyName]).ConfigureAwait(false); - } - else if (keyName == MasterKeyName) - { - using (var masterKey = CascadingSymmetricKey.New()) - { - var masterKeySecret = CascadingSymmetricKeySecret.FromValue(keyName, masterKey); - AddOrUpdate(MasterKeyName, masterKeySecret, controlToken); - return await ExportEncryptedSecretAsync(exportedSecret, masterKeySecret).ConfigureAwait(false); - } - } - - throw new ArgumentException($"The secret vault does not contain a key with the specified name, \"{keyName}\".", nameof(keyName)); + if (Secrets.ContainsKey(keyName.RejectIf().IsNullOrEmpty(nameof(keyName)))) + { + return await ExportEncryptedSecretAsync(exportedSecret, Secrets[keyName]).ConfigureAwait(false); + } + else if (keyName == MasterKeyName) + { + using var masterKey = CascadingSymmetricKey.New(); + var masterKeySecret = CascadingSymmetricKeySecret.FromValue(keyName, masterKey); + AddOrUpdate(MasterKeyName, masterKeySecret, controlToken); + return await ExportEncryptedSecretAsync(exportedSecret, masterKeySecret).ConfigureAwait(false); } + + throw new ArgumentException($"The secret vault does not contain a key with the specified name, \"{keyName}\".", nameof(keyName)); } finally { @@ -335,6 +398,33 @@ public async Task ExportEncryptedSecretAsync(String nam throw new ArgumentException($"The secret vault does not contain a secret with the specified name, \"{name}\".", nameof(name)); } + /// + /// Asynchronously exports the vault's master key in plaintext form. + /// + /// + /// A task representing the asynchronous operation and containing the exported master key. + /// + /// + /// The object is disposed. + /// + public Task ExportMasterKeyAsync() + { + RejectIfDisposed(); + + if (Secrets.ContainsKey(MasterKeyName)) + { + return ExportSecretAsync(MasterKeyName); + } + + using var controlToken = StateControl.Enter(); + RejectIfDisposed(); + + using var masterKey = CascadingSymmetricKey.New(); + var masterKeySecret = CascadingSymmetricKeySecret.FromValue(MasterKeyName, masterKey); + AddOrUpdate(MasterKeyName, masterKeySecret, controlToken); + return Task.FromResult(new ExportedSecret(masterKeySecret)); + } + /// /// Asynchronously exports the specified secret in plaintext form. /// @@ -362,12 +452,12 @@ public Task ExportSecretAsync(String name) => Task.Factory.Start }); /// - /// Decrypts the specified secret using the vault's master key and imports it. + /// Decrypts the specified secret using the master key of the current and imports it. /// /// - /// When using this method, note that the master key of the current must match the key that was - /// used when exporting the secret. If the exporting vault is not the current vault, the master keys will need to have been - /// synchronized beforehand. + /// When using this method, note that the master key of the current must match the key that was + /// used when exporting the secret. If the exporting vault is not the current , the master keys + /// will need to have been synchronized beforehand. /// /// /// The encrypted secret to import. @@ -409,47 +499,95 @@ public Task ExportSecretAsync(String name) => Task.Factory.Start /// public void ImportEncryptedSecret(IEncryptedExportedSecret secret, String keyName) { - using (var controlToken = StateControl.Enter()) + using var controlToken = StateControl.Enter(); + RejectIfDisposed(); + + if (Secrets.ContainsKey(keyName.RejectIf().IsNullOrEmpty(nameof(keyName)))) { - RejectIfDisposed(); + var keySecret = Secrets[keyName]; - if (Secrets.ContainsKey(keyName.RejectIf().IsNullOrEmpty(nameof(keyName)))) + try { - var keySecret = Secrets[keyName]; - - try + if (keySecret.ValueType == typeof(SymmetricKey)) { - if (keySecret.ValueType == typeof(SymmetricKey)) - { - ((SymmetricKeySecret)keySecret).ReadAsync((SymmetricKey key) => - { - ImportSecret(secret.ToPlaintextModel(key), controlToken); - }).Wait(); - return; - } - else if (keySecret.ValueType == typeof(CascadingSymmetricKey)) + ((SymmetricKeySecret)keySecret).ReadAsync((SymmetricKey key) => { - ((CascadingSymmetricKeySecret)keySecret).ReadAsync((CascadingSymmetricKey key) => - { - ImportSecret(secret.ToPlaintextModel(key), controlToken); - }).Wait(); - return; - } - - throw new ArgumentException($"The specified key name, \"{keyName}\" does not reference a valid key.", nameof(keySecret.Name)); + ImportSecret(secret.ToPlaintextModel(key), controlToken); + }).Wait(); + return; } - catch (AggregateException exception) + else if (keySecret.ValueType == typeof(CascadingSymmetricKey)) { - throw new SecretAccessException($"The specified key, \"{keyName}\", could not be used to import the specified secret. Decryption or deserialization failed.", exception); + ((CascadingSymmetricKeySecret)keySecret).ReadAsync((CascadingSymmetricKey key) => + { + ImportSecret(secret.ToPlaintextModel(key), controlToken); + }).Wait(); + return; } + + throw new ArgumentException($"The specified key name, \"{keyName}\" does not reference a valid key.", nameof(keySecret.Name)); } - else if (keyName == MasterKeyName) + catch (AggregateException exception) { - throw new SecretAccessException("The encrypted secret cannot be imported without specifying an explicit key because the secret vault does not have a master key."); + throw new SecretAccessException($"The specified key, \"{keyName}\", could not be used to import the specified secret. Decryption or deserialization failed.", exception); } - - throw new ArgumentException($"The secret vault does not contain a key with the specified name, \"{keyName}\".", nameof(keyName)); } + else if (keyName == MasterKeyName) + { + throw new SecretAccessException("The encrypted secret cannot be imported without specifying an explicit key because the secret vault does not have a master key."); + } + + throw new ArgumentException($"The secret vault does not contain a key with the specified name, \"{keyName}\".", nameof(keyName)); + } + + /// + /// Decrypts the specified secret vault using the master key of the current and imports it. + /// + /// + /// When using this method, note that the master key of the current must match the key that + /// was used when exporting the secret vault. If the exporting vault is not the current , the + /// master keys will need to have been synchronized beforehand. + /// + /// + /// The encrypted secret vault to import. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during decryption or deserialization. + /// + public void ImportEncryptedSecretVault(IEncryptedExportedSecretVault secretVault) => ImportEncryptedSecretVault(secretVault, MasterKeyName); + + /// + /// Decrypts the specified secret vault using the specified key and imports it. + /// + /// + /// The encrypted secret vault to import. + /// + /// + /// The name of the secret associated with a key that was used to encrypt the exported secret vault. + /// + /// + /// is empty. + /// + /// + /// The secret vault does not contain a key with the specified name. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during decryption or deserialization. + /// + public void ImportEncryptedSecretVault(IEncryptedExportedSecretVault secretVault, String keyName) + { } /// @@ -466,11 +604,9 @@ public void ImportEncryptedSecret(IEncryptedExportedSecret secret, String keyNam /// public void ImportSecret(IExportedSecret secret) { - using (var controlToken = StateControl.Enter()) - { - RejectIfDisposed(); - ImportSecret(secret, controlToken); - } + using var controlToken = StateControl.Enter(); + RejectIfDisposed(); + ImportSecret(secret, controlToken); } /// @@ -718,21 +854,19 @@ public void ImportSecret(IExportedSecret secret) /// public Boolean TryRemove(String name) { - using (var controlToken = StateControl.Enter()) - { - RejectIfDisposed(); + using var controlToken = StateControl.Enter(); + RejectIfDisposed(); - if (Secrets.ContainsKey(name.RejectIf().IsNullOrEmpty(nameof(name)))) + if (Secrets.ContainsKey(name.RejectIf().IsNullOrEmpty(nameof(name)))) + { + if (Secrets.Remove(name, out var secret)) { - if (Secrets.Remove(name, out var secret)) - { - secret?.Dispose(); - return true; - } + secret?.Dispose(); + return true; } - - return false; } + + return false; } /// @@ -749,10 +883,8 @@ protected override void Dispose(Boolean disposing) { try { - using (var controlToken = StateControl.Enter()) - { - LazyReferenceManager.Dispose(); - } + using var controlToken = StateControl.Enter(); + LazyReferenceManager.Dispose(); } finally { @@ -788,11 +920,9 @@ protected override void Dispose(Boolean disposing) [DebuggerHidden] private void AddOrUpdate(String name, IReadOnlySecret secret) { - using (var controlToken = StateControl.Enter()) - { - RejectIfDisposed(); - AddOrUpdate(name, secret, controlToken); - } + using var controlToken = StateControl.Enter(); + RejectIfDisposed(); + AddOrUpdate(name, secret, controlToken); } /// @@ -865,7 +995,46 @@ private async Task ExportEncryptedSecretAsync(ExportedS }).ConfigureAwait(false); } - return encryptedExportedSecret ?? throw new ArgumentException($"The specified key name, \"{keyName}\" does not reference a valid key.", nameof(keySecret)); + return encryptedExportedSecret ?? throw new ArgumentException($"The specified key name, \"{keyName}\", does not reference a valid key.", nameof(keySecret)); + } + + /// + /// Asynchronously exports the specified secret vault and encrypts it using the specified key. + /// + /// + /// The secrets to export. + /// + /// + /// The key secret that is used to encrypt the exported secrets. + /// + /// + /// The specified key secret is not a valid key. + /// + /// + /// An exception was raised during encryption or serialization. + /// + [DebuggerHidden] + private async Task ExportEncryptedSecretVaultAsync(ExportedSecretVault exportedSecretVault, IReadOnlySecret keySecret) + { + var encryptedExportedSecretVault = (EncryptedExportedSecretVault)null; + var keyName = keySecret.Name; + + if (keySecret.ValueType == typeof(SymmetricKey)) + { + await ((SymmetricKeySecret)keySecret).ReadAsync((SymmetricKey key) => + { + encryptedExportedSecretVault = new EncryptedExportedSecretVault(exportedSecretVault, key, keyName); + }).ConfigureAwait(false); + } + else if (keySecret.ValueType == typeof(CascadingSymmetricKey)) + { + await ((CascadingSymmetricKeySecret)keySecret).ReadAsync((CascadingSymmetricKey key) => + { + encryptedExportedSecretVault = new EncryptedExportedSecretVault(exportedSecretVault, key, keyName); + }).ConfigureAwait(false); + } + + return encryptedExportedSecretVault ?? throw new ArgumentException($"The specified key name, \"{keyName}\", does not reference a valid key.", nameof(keySecret)); } /// @@ -892,17 +1061,15 @@ private async Task ExportEncryptedSecretAsync(ExportedS [DebuggerHidden] private ExportedSecret ExportSecret(String name) { - using (var controlToken = StateControl.Enter()) - { - RejectIfDisposed(); - - if (Secrets.ContainsKey(name.RejectIf().IsNullOrEmpty(nameof(name)))) - { - return new ExportedSecret(Secrets[name]); - } + using var controlToken = StateControl.Enter(); + RejectIfDisposed(); - throw new ArgumentException($"The secret vault does not contain a secret with the specified name, \"{name}\".", nameof(name)); + if (Secrets.ContainsKey(name.RejectIf().IsNullOrEmpty(nameof(name)))) + { + return new ExportedSecret(Secrets[name]); } + + throw new ArgumentException($"The secret vault does not contain a secret with the specified name, \"{name}\".", nameof(name)); } /// @@ -920,6 +1087,22 @@ private ExportedSecret ExportSecret(String name) [DebuggerHidden] private void ImportSecret(IExportedSecret secret, IConcurrencyControlToken controlToken) => AddOrUpdate(secret.RejectIf().IsNull(nameof(secret)).TargetArgument.Name, secret.ToSecret(), controlToken); + /// + /// Uses a probabilistic method to randomly regenerate and replace the in-memory keys that are used to secure the secrets + /// stored by the current , with a probability that scales toward 1 as the keys age. + /// + /// + /// The object is disposed. + /// + [DebuggerHidden] + private void ProbabilisticallyRegenerateInMemoryKeys() + { + if (Convert.ToDouble(HardenedRandomNumberGenerator.Instance.GetUInt16()) / Convert.ToDouble(UInt16.MaxValue) < InMemoryKeyRegenerationProbability) + { + RegenerateInMemoryKeys(); + } + } + /// /// Decrypts the specified named secret, pins a copy of it in memory, and performs the specified read operation against it /// as a thread-safe, atomic operation. @@ -952,10 +1135,11 @@ private ExportedSecret ExportSecret(String name) [DebuggerHidden] private void Read(String name, Action readAction) { - using (var controlToken = StateControl.Enter()) - { - RejectIfDisposed(); + using var controlToken = StateControl.Enter(); + RejectIfDisposed(); + try + { if (Secrets.TryGetValue(name.RejectIf().IsNullOrEmpty(nameof(name)), out var secret)) { var typedSecret = secret as Secret; @@ -971,6 +1155,10 @@ private void Read(String name, Action readAction) throw new ArgumentException($"The secret vault does not contain a secret with the specified name, \"{name}\".", nameof(name)); } + finally + { + controlToken.AttachTask(ProbabilisticallyRegenerateInMemoryKeys); + } } /// @@ -1016,6 +1204,25 @@ private Task ReadAsync(String name, Action readAction) }); } + /// + /// Regenerates and replaces the in-memory keys that are used to secure the secrets stored by the current + /// . + /// + /// + /// The object is disposed. + /// + [DebuggerHidden] + private void RegenerateInMemoryKeys() + { + using var controlToken = StateControl.Enter(); + RejectIfDisposed(); + + foreach (var secret in Secrets.Values) + { + controlToken.AttachTask(secret.RegenerateInMemoryKey); + } + } + /// /// Gets the number of secrets that are stored by the current . /// @@ -1026,36 +1233,57 @@ public Int32 Count { get { - using (var controlToken = StateControl.Enter()) - { - RejectIfDisposed(); - return Secrets.Count; - } + using var controlToken = StateControl.Enter(); + RejectIfDisposed(); + return Secrets.Count; } } + /// + /// Gets the unique semantic identifier for the current . + /// + public String Identifier => $"{IdentifierPrefix}{SemanticIdentity}"; + /// /// Gets the textual names that uniquely identify the secrets that are stored by the current . /// /// /// The object is disposed. /// - public IEnumerable Names + public IEnumerable SecretNames { get { - using (var controlToken = StateControl.Enter()) - { - RejectIfDisposed(); + using var controlToken = StateControl.Enter(); + RejectIfDisposed(); - foreach (var name in Secrets.Keys) - { - yield return name; - } + foreach (var name in Secrets.Keys) + { + yield return name; } } } + /// + /// Gets the name of the master key stored within the current instance. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal String MasterKeyName => $"{MasterKeyNamePrefix}{SemanticIdentity}"; + + /// + /// Gets the length of time since the oldest in-memory key in the current was generated, or + /// if the vault does not contain any secrets. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private TimeSpan AgeOfOldestInMemoryKey => Secrets.Any() ? TimeStamp.Current - Secrets.Values.Min(secret => secret?.InMemoryKeyTimeStamp ?? DateTime.MaxValue) : TimeSpan.Zero; + + /// + /// Gets a conditional, per-read-operation probability which governs the frequency at which the vault's in-memory keys are + /// regenerated and replaced. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private Double InMemoryKeyRegenerationProbability => Math.Min(AgeOfOldestInMemoryKey.TotalSeconds / InMemoryKeyAgeRegenerationThresholdInSeconds, 1d); + /// /// Gets a utility that disposes of the secrets that are managed by the current . /// @@ -1063,10 +1291,23 @@ public IEnumerable Names private IReferenceManager ReferenceManager => LazyReferenceManager.Value; /// - /// Represents the name of the master key stored within every instance. + /// Represents the textual prefix for the semantic identifier of every instance. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal const String IdentifierPrefix = "__SecretVault-"; + + /// + /// Represents the textual prefix for the name of the master key stored within every instance. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - internal const String MasterKeyName = "__MasterKey"; + internal const String MasterKeyNamePrefix = "__MasterKey-"; + + /// + /// Gets the length of time, in seconds, for expiration of in-memory keys at which the probability of a key regeneration and + /// replacement event becomes 1 (100%). + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Int32 InMemoryKeyAgeRegenerationThresholdInSeconds = 180; /// /// Represents the lazily-initialized utility that disposes of the secrets that are managed by the current @@ -1080,5 +1321,11 @@ public IEnumerable Names /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly IDictionary Secrets; + + /// + /// Represents the unique portion of the semantic identifier for the current . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly String SemanticIdentity; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/StringSecret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/StringSecret.cs index 04abe4db..929b8c59 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/StringSecret.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/StringSecret.cs @@ -7,6 +7,7 @@ using RapidField.SolidInstruments.Core.ArgumentValidation; using RapidField.SolidInstruments.Core.Concurrency; using System; +using System.Diagnostics; using System.Text; namespace RapidField.SolidInstruments.Cryptography.Secrets @@ -14,7 +15,7 @@ namespace RapidField.SolidInstruments.Cryptography.Secrets /// /// Represents a named secret value that is pinned in memory and encrypted at rest. /// - public sealed class StringSecret : Secret + public class StringSecret : Secret { /// /// Initializes a new instance of the class. @@ -29,11 +30,33 @@ public sealed class StringSecret : Secret /// is . /// public StringSecret(String name) - : base(name) + : this(name, DefaultEncoding) { return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A textual name that uniquely identifies the secret. + /// + /// + /// The encoding that is use when converting the secret string to and from bytes. The default value is + /// . + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + protected StringSecret(String name, Encoding encoding) + : base(name) + { + Encoding = encoding.RejectIf().IsNull(nameof(encoding)); + } + /// /// Creates a new using the specified name and value. /// @@ -72,7 +95,7 @@ public static StringSecret FromValue(String name, String value) /// /// The resulting . /// - protected sealed override String ConvertBytesToValue(IReadOnlyPinnedMemory bytes, IConcurrencyControlToken controlToken) => Encoding.Unicode.GetString(bytes.ReadOnlySpan); + protected sealed override String ConvertBytesToValue(IReadOnlyPinnedMemory bytes, IConcurrencyControlToken controlToken) => Encoding.GetString(bytes.ReadOnlySpan); /// /// Gets the bytes of , pins them in memory and returns the resulting @@ -87,7 +110,20 @@ public static StringSecret FromValue(String name, String value) /// /// as pinned memory. /// - protected sealed override IReadOnlyPinnedMemory ConvertValueToBytes(String value, IConcurrencyControlToken controlToken) => new PinnedMemory(Encoding.Unicode.GetBytes(value), true); + /// + /// does not conform to the specified encoding. + /// + protected sealed override IReadOnlyPinnedMemory ConvertValueToBytes(String value, IConcurrencyControlToken controlToken) + { + try + { + return new PinnedMemory(Encoding.GetBytes(value), true); + } + catch (EncoderFallbackException exception) + { + throw new FormatException($"The specified secret string value contains characters that are invalid for the encoding format: {Encoding.EncodingName}", exception); + } + } /// /// Releases all resources consumed by the current . @@ -96,5 +132,19 @@ public static StringSecret FromValue(String name, String value) /// A value indicating whether or not managed resources should be released. /// protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Gets the encoding that is use when converting the secret string to and from bytes. + /// + protected Encoding Encoding + { + get; + } + + /// + /// Gets the default encoding that is use when converting the secret string to and from bytes. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static Encoding DefaultEncoding => Encoding.Unicode; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/SecureMemory.cs b/src/RapidField.SolidInstruments.Cryptography/SecureMemory.cs index d601edd9..70989259 100644 --- a/src/RapidField.SolidInstruments.Cryptography/SecureMemory.cs +++ b/src/RapidField.SolidInstruments.Cryptography/SecureMemory.cs @@ -37,23 +37,19 @@ public SecureMemory(Int32 lengthInBytes) { Cipher = new Aes128CbcCipher(RandomnessProvider); LengthInBytes = lengthInBytes.RejectIf().IsLessThanOrEqualTo(0, nameof(lengthInBytes)); - PrivateKeySource = new PinnedMemory(PrivateKeySourceLengthInBytes, true); + PrivateKeySourceField = new InflatedField(PrivateKeySourceLengthInBytes, PrivateKeySourceFieldMultiplier, RandomnessProvider); PrivateKeySourceBitShiftDirection = RandomnessProvider.GetBoolean() ? BitShiftDirection.Left : BitShiftDirection.Right; PrivateKeySourceBitShiftCount = BitConverter.GetBytes(RandomnessProvider.GetUInt16()).Max(); - RandomnessProvider.GetBytes(PrivateKeySource); + PrivateKeyVersionValue = 0; - using (var initializationVector = new PinnedMemory(Cipher.BlockSizeInBytes, true)) - { - RandomnessProvider.GetBytes(initializationVector); + using var initializationVector = new PinnedMemory(Cipher.BlockSizeInBytes, true); + RandomnessProvider.GetBytes(initializationVector); - using (var plaintext = new PinnedMemory(lengthInBytes)) - { - using (var privateKey = DerivePrivateKey(PrivateKeySource, PrivateKeySourceBitShiftDirection, PrivateKeySourceBitShiftCount, Cipher.KeySizeInBytes)) - { - Ciphertext = Cipher.Encrypt(plaintext, privateKey, initializationVector); - } - } - } + using var plaintext = new PinnedMemory(lengthInBytes); + using var privateKey = DerivePrivateKey(PrivateKeySource, PrivateKeySourceBitShiftDirection, PrivateKeySourceBitShiftCount, Cipher.KeySizeInBytes); + using var ciphertext = Cipher.Encrypt(plaintext, privateKey, initializationVector); + CiphertextField = new InflatedField(ciphertext.Length, CiphertextFieldMultiplier, RandomnessProvider); + ciphertext.ReadOnlySpan.CopyTo(Ciphertext); } /// @@ -71,6 +67,7 @@ public SecureMemory(Int32 lengthInBytes) public static ISecureMemory GenerateHardenedRandomBytes(Int32 lengthInBytes) { var hardenedRandomBytes = new SecureMemory(lengthInBytes); + hardenedRandomBytes.Access((memory) => { RandomnessProvider.GetBytes(memory); @@ -96,23 +93,19 @@ public void Access(Action action) { action = action.RejectIf().IsNull(nameof(action)); - using (var controlToken = StateControl.Enter()) - { - RejectIfDisposed(); + using var controlToken = StateControl.Enter(); + RejectIfDisposed(); - using (var plaintext = new PinnedMemory(LengthInBytes, true)) - { - DecryptField(plaintext); + using var plaintext = new PinnedMemory(LengthInBytes, true); + DecryptField(plaintext); - try - { - action(plaintext); - } - finally - { - EncryptField(plaintext); - } - } + try + { + action(plaintext); + } + finally + { + EncryptField(plaintext); } } @@ -149,7 +142,7 @@ public void Access(Action action) /// /// The object is disposed. /// - public void RegeneratePrivateKey() + void ISecureMemory.RegeneratePrivateKey() { using (var controlToken = StateControl.Enter()) { @@ -161,10 +154,12 @@ public void RegeneratePrivateKey() try { - RandomnessProvider.GetBytes(PrivateKeySource); + CiphertextField.Scramble(); + PrivateKeySourceField.Scramble(); } finally { + PrivateKeyVersionValue = PrivateKeyVersionValue == UInt32.MaxValue ? 0 : PrivateKeyVersionValue + 1; EncryptField(plaintext); } } @@ -186,7 +181,7 @@ public void RegeneratePrivateKey() /// The resulting private key. /// [DebuggerHidden] - internal PinnedMemory DerivePrivateKey() => DerivePrivateKey(PrivateKeySource, PrivateKeySourceBitShiftDirection, PrivateKeySourceBitShiftCount, Cipher.BlockSizeInBytes); + internal PinnedMemory DerivePrivateKey() => DerivePrivateKey(PrivateKeySource, PrivateKeySourceBitShiftDirection, PrivateKeySourceBitShiftCount, Cipher.KeySizeInBytes); /// /// Releases all resources consumed by the current . @@ -201,8 +196,8 @@ protected override void Dispose(Boolean disposing) if (disposing) { Cipher.Dispose(); - Ciphertext.Dispose(); - PrivateKeySource.Dispose(); + CiphertextField.Dispose(); + PrivateKeySourceField.Dispose(); } } finally @@ -245,13 +240,9 @@ protected override void Dispose(Boolean disposing) [DebuggerHidden] private void DecryptField(PinnedMemory plaintext) { - using (var privateKey = DerivePrivateKey()) - { - using (var buffer = Cipher.Decrypt(Ciphertext, privateKey)) - { - buffer.Span.CopyTo(plaintext); - } - } + using var privateKey = DerivePrivateKey(); + using var buffer = Cipher.Decrypt(Ciphertext, privateKey); + buffer.Span.CopyTo(plaintext); } /// @@ -263,18 +254,12 @@ private void DecryptField(PinnedMemory plaintext) [DebuggerHidden] private void EncryptField(PinnedMemory plaintext) { - using (var initializationVector = new PinnedMemory(Cipher.BlockSizeInBytes, true)) - { - RandomnessProvider.GetBytes(initializationVector); + using var initializationVector = new PinnedMemory(Cipher.BlockSizeInBytes, true); + RandomnessProvider.GetBytes(initializationVector); - using (var privateKey = DerivePrivateKey()) - { - using (var buffer = Cipher.Encrypt(plaintext, privateKey, initializationVector)) - { - buffer.Span.CopyTo(Ciphertext); - } - } - } + using var privateKey = DerivePrivateKey(); + using var buffer = Cipher.Encrypt(plaintext, privateKey, initializationVector); + buffer.Span.CopyTo(Ciphertext); } /// @@ -291,6 +276,24 @@ public Int32 LengthInBytes get; } + /// + /// Gets the current zero-based ordinal version of the bits comprising the private key that is used to secure the current + /// . + /// + UInt32 ISecureMemory.PrivateKeyVersion => PrivateKeyVersionValue; + + /// + /// Gets the ciphertext bits for the bit field. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private PinnedMemory Ciphertext => CiphertextField.TargetField; + + /// + /// Gets a field of random bits from which a private key is derived to encrypt and decrypt the secure bit field. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private PinnedMemory PrivateKeySource => PrivateKeySourceField.TargetField; + /// /// Gets the length, in bytes, of . /// @@ -298,22 +301,28 @@ public Int32 LengthInBytes private Int32 PrivateKeySourceLengthInBytes => Cipher.KeySizeInBytes + 16; /// - /// Represents a cipher that is used to encrypt and decrypt the bit field. + /// Represents the length multiplier for . /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly SymmetricKeyCipher Cipher; + private const Int32 CiphertextFieldMultiplier = 2; /// - /// Represents the ciphertext bits for the bit field. + /// Represents the length multiplier for . /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly PinnedMemory Ciphertext; + private const Int32 PrivateKeySourceFieldMultiplier = 4; /// - /// Represents a field of random bits from which a private key is derived to encrypt and decrypt the secure bit field. + /// Represents a cipher that is used to encrypt and decrypt the bit field. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly PinnedMemory PrivateKeySource; + private readonly SymmetricKeyCipher Cipher; + + /// + /// Represents the ciphertext bits for the bit field. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly InflatedField CiphertextField; /// /// Represents the circular bit shift count to use when deriving a private key from . @@ -326,5 +335,150 @@ public Int32 LengthInBytes /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly BitShiftDirection PrivateKeySourceBitShiftDirection; + + /// + /// Represents a field of random bits from which a private key is derived to encrypt and decrypt the secure bit field. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly InflatedField PrivateKeySourceField; + + /// + /// Represents the current zero-based ordinal version of the bits comprising . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private UInt32 PrivateKeyVersionValue; + + /// + /// Represents a pinned memory field, the length of which is inflated by an integer multiplier. + /// + /// + /// exposes diversionary bit fields in memory and increases the computational expense of memory + /// attacks upon instances. + /// + private sealed class InflatedField : Instrument + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The length, in bytes, of the target field. + /// + /// + /// The total number of fields to allocate. + /// + /// + /// A random number generator that is used to scramble the inflated field. + /// + /// + /// is . + /// + /// + /// is less than or equal to zero -or- is less than or equal + /// to one. + /// + [DebuggerHidden] + internal InflatedField(Int32 length, Int32 multiplier, RandomNumberGenerator randomnessProvider) + : base() + { + Fields = new PinnedMemory[multiplier.RejectIf().IsLessThanOrEqualTo(1, nameof(multiplier))]; + RandomnessProvider = randomnessProvider.RejectIf().IsNull(nameof(randomnessProvider)); + ReferenceKey = RandomnessProvider.GetUInt64(); + + for (var i = 0; i < multiplier; i++) + { + var field = new PinnedMemory(length, true); + RandomnessProvider.GetBytes(field); + Fields[i] = field; + } + } + + /// + /// Fills the current with random bits and generates a new, random reference key. + /// + /// + /// The object is disposed. + /// + [DebuggerHidden] + internal void Scramble() + { + using var controlToken = StateControl.Enter(); + RejectIfDisposed(); + ReferenceKey = RandomnessProvider.GetUInt64(); + + foreach (var field in Fields) + { + RandomnessProvider.GetBytes(field); + } + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) + { + try + { + if (disposing) + { + ReferenceKey = default; + + foreach (var field in Fields) + { + field?.Dispose(); + } + } + } + finally + { + base.Dispose(disposing); + } + } + + /// + /// Gets the target field. + /// + /// + /// The object is disposed. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal PinnedMemory TargetField + { + [DebuggerHidden] + get + { + using var controlToken = StateControl.Enter(); + RejectIfDisposed(); + return Fields[Convert.ToInt32(ReferenceKey / (UInt64.MaxValue / Convert.ToUInt64(Multiplier)))]; + } + } + + /// + /// Represents the total number of fields allocated by the current . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private Int32 Multiplier => Fields.Length; + + /// + /// Represents the underlying collection of pinned memory fields. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly PinnedMemory[] Fields; + + /// + /// Represents a random number generator that is used to scramble the inflated field. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly RandomNumberGenerator RandomnessProvider; + + /// + /// Represents a 64-bit key that defines the location of the target field within the current + /// . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private UInt64 ReferenceKey; + } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/CascadingSymmetricKey.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/CascadingSymmetricKey.cs index a0959863..da9d51fc 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Symmetric/CascadingSymmetricKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Symmetric/CascadingSymmetricKey.cs @@ -6,6 +6,7 @@ using RapidField.SolidInstruments.Core; using RapidField.SolidInstruments.Core.ArgumentValidation; using RapidField.SolidInstruments.Core.Concurrency; +using RapidField.SolidInstruments.Cryptography.Secrets; using System; using System.Collections.Generic; using System.Diagnostics; @@ -72,16 +73,16 @@ private CascadingSymmetricKey(ISymmetricKey[] keys) /// /// A new . /// - /// - /// is empty. - /// /// /// is shorter than thirteen characters. /// /// /// is . /// - public static CascadingSymmetricKey FromPassword(String password) => FromPassword(password, DefaultDerivationMode, DefaultFirstLayerAlgorithm, DefaultSecondLayerAlgorithm); + /// + /// is disposed. + /// + public static CascadingSymmetricKey FromPassword(IPassword password) => FromPassword(password, DefaultDerivationMode, DefaultFirstLayerAlgorithm, DefaultSecondLayerAlgorithm); /// /// Derives a new from the specified password. @@ -103,9 +104,6 @@ private CascadingSymmetricKey(ISymmetricKey[] keys) /// /// A new . /// - /// - /// is empty. - /// /// /// is shorter than thirteen characters. /// @@ -116,7 +114,10 @@ private CascadingSymmetricKey(ISymmetricKey[] keys) /// is equal to -or- one or more /// algorithm layers are equal to . /// - public static CascadingSymmetricKey FromPassword(String password, SymmetricKeyDerivationMode derivationMode, SymmetricAlgorithmSpecification firstLayerAlgorithm, SymmetricAlgorithmSpecification secondLayerAlgorithm) => FromPassword(password, derivationMode, new SymmetricAlgorithmSpecification[] { firstLayerAlgorithm, secondLayerAlgorithm }); + /// + /// is disposed. + /// + public static CascadingSymmetricKey FromPassword(IPassword password, SymmetricKeyDerivationMode derivationMode, SymmetricAlgorithmSpecification firstLayerAlgorithm, SymmetricAlgorithmSpecification secondLayerAlgorithm) => FromPassword(password, derivationMode, new SymmetricAlgorithmSpecification[] { firstLayerAlgorithm, secondLayerAlgorithm }); /// /// Derives a new from the specified password. @@ -139,9 +140,6 @@ private CascadingSymmetricKey(ISymmetricKey[] keys) /// /// A new . /// - /// - /// is empty. - /// /// /// is shorter than thirteen characters. /// @@ -152,7 +150,10 @@ private CascadingSymmetricKey(ISymmetricKey[] keys) /// is equal to -or- one or more /// algorithm layers are equal to . /// - public static CascadingSymmetricKey FromPassword(String password, SymmetricKeyDerivationMode derivationMode, SymmetricAlgorithmSpecification firstLayerAlgorithm, SymmetricAlgorithmSpecification secondLayerAlgorithm, SymmetricAlgorithmSpecification thirdLayerAlgorithm) => FromPassword(password, derivationMode, new SymmetricAlgorithmSpecification[] { firstLayerAlgorithm, secondLayerAlgorithm, thirdLayerAlgorithm }); + /// + /// is disposed. + /// + public static CascadingSymmetricKey FromPassword(IPassword password, SymmetricKeyDerivationMode derivationMode, SymmetricAlgorithmSpecification firstLayerAlgorithm, SymmetricAlgorithmSpecification secondLayerAlgorithm, SymmetricAlgorithmSpecification thirdLayerAlgorithm) => FromPassword(password, derivationMode, new SymmetricAlgorithmSpecification[] { firstLayerAlgorithm, secondLayerAlgorithm, thirdLayerAlgorithm }); /// /// Derives a new from the specified password. @@ -178,9 +179,6 @@ private CascadingSymmetricKey(ISymmetricKey[] keys) /// /// A new . /// - /// - /// is empty. - /// /// /// is shorter than thirteen characters. /// @@ -191,7 +189,10 @@ private CascadingSymmetricKey(ISymmetricKey[] keys) /// is equal to -or- one or more /// algorithm layers are equal to . /// - public static CascadingSymmetricKey FromPassword(String password, SymmetricKeyDerivationMode derivationMode, SymmetricAlgorithmSpecification firstLayerAlgorithm, SymmetricAlgorithmSpecification secondLayerAlgorithm, SymmetricAlgorithmSpecification thirdLayerAlgorithm, SymmetricAlgorithmSpecification fourthLayerAlgorithm) => FromPassword(password, derivationMode, new SymmetricAlgorithmSpecification[] { firstLayerAlgorithm, secondLayerAlgorithm, thirdLayerAlgorithm, fourthLayerAlgorithm }); + /// + /// is disposed. + /// + public static CascadingSymmetricKey FromPassword(IPassword password, SymmetricKeyDerivationMode derivationMode, SymmetricAlgorithmSpecification firstLayerAlgorithm, SymmetricAlgorithmSpecification secondLayerAlgorithm, SymmetricAlgorithmSpecification thirdLayerAlgorithm, SymmetricAlgorithmSpecification fourthLayerAlgorithm) => FromPassword(password, derivationMode, new SymmetricAlgorithmSpecification[] { firstLayerAlgorithm, secondLayerAlgorithm, thirdLayerAlgorithm, fourthLayerAlgorithm }); /// /// Creates a new instance of a using the specified secure bit field. @@ -426,9 +427,6 @@ protected override void Dispose(Boolean disposing) /// /// A new . /// - /// - /// is empty -or- is empty. - /// /// /// is shorter than thirteen characters. /// @@ -439,8 +437,11 @@ protected override void Dispose(Boolean disposing) /// is equal to -or- one or more /// algorithm layers are equal to . /// + /// + /// is disposed. + /// [DebuggerHidden] - private static CascadingSymmetricKey FromPassword(String password, SymmetricKeyDerivationMode derivationMode, SymmetricAlgorithmSpecification[] algorithms) + private static CascadingSymmetricKey FromPassword(IPassword password, SymmetricKeyDerivationMode derivationMode, SymmetricAlgorithmSpecification[] algorithms) { var keyDepth = algorithms.RejectIf().IsNullOrEmpty(nameof(algorithms)).TargetArgument.Length; var singleKeySourceLengthInBytes = SymmetricKey.KeySourceLengthInBytes; diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKey.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKey.cs index 16816c21..39bae26a 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKey.cs @@ -9,7 +9,9 @@ using RapidField.SolidInstruments.Core.Extensions; using RapidField.SolidInstruments.Cryptography.Extensions; using RapidField.SolidInstruments.Cryptography.Hashing; +using RapidField.SolidInstruments.Cryptography.Secrets; using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Security; @@ -72,16 +74,16 @@ private SymmetricKey(SymmetricAlgorithmSpecification algorithm, SymmetricKeyDeri /// /// A new . /// - /// - /// is empty. - /// /// /// is shorter than thirteen characters. /// /// /// is . /// - public static SymmetricKey FromPassword(String password) => FromPassword(password, DefaultAlgorithm); + /// + /// is disposed. + /// + public static SymmetricKey FromPassword(IPassword password) => FromPassword(password, DefaultAlgorithm); /// /// Derives a new from the specified password. @@ -96,9 +98,6 @@ private SymmetricKey(SymmetricAlgorithmSpecification algorithm, SymmetricKeyDeri /// /// A new . /// - /// - /// is empty. - /// /// /// is shorter than thirteen characters. /// @@ -108,7 +107,10 @@ private SymmetricKey(SymmetricAlgorithmSpecification algorithm, SymmetricKeyDeri /// /// is equal to . /// - public static SymmetricKey FromPassword(String password, SymmetricAlgorithmSpecification algorithm) => FromPassword(password, algorithm, DefaultDerivationMode); + /// + /// is disposed. + /// + public static SymmetricKey FromPassword(IPassword password, SymmetricAlgorithmSpecification algorithm) => FromPassword(password, algorithm, DefaultDerivationMode); /// /// Derives a new from the specified password. @@ -126,9 +128,6 @@ private SymmetricKey(SymmetricAlgorithmSpecification algorithm, SymmetricKeyDeri /// /// A new . /// - /// - /// is empty. - /// /// /// is shorter than thirteen characters. /// @@ -139,7 +138,10 @@ private SymmetricKey(SymmetricAlgorithmSpecification algorithm, SymmetricKeyDeri /// is equal to -or- /// is equal to . /// - public static SymmetricKey FromPassword(String password, SymmetricAlgorithmSpecification algorithm, SymmetricKeyDerivationMode derivationMode) + /// + /// is disposed. + /// + public static SymmetricKey FromPassword(IPassword password, SymmetricAlgorithmSpecification algorithm, SymmetricKeyDerivationMode derivationMode) { using (var keySource = DeriveKeySourceBytesFromPassword(password, KeySourceLengthInBytes)) { @@ -275,7 +277,7 @@ public ISecureMemory ToDerivedKeyBytes() { result.Access(memory => { - // Perform PBKDF2 key-derivation. + // Perform PBKDF2 key derivation. var keyBytes = new Span(Pbkdf2Provider.GetBytes(DerivedKeyLength)); keyBytes.CopyTo(memory); keyBytes.Clear(); @@ -428,9 +430,6 @@ public ISecureMemory ToSecureMemory() /// /// The key source bytes. /// - /// - /// is empty. - /// /// /// is shorter than thirteen characters. /// @@ -440,12 +439,16 @@ public ISecureMemory ToSecureMemory() /// /// is less than or equal to zero. /// + /// + /// is disposed. + /// [DebuggerHidden] - internal static PinnedMemory DeriveKeySourceBytesFromPassword(String password, Int32 keySourceLengthInBytes) + internal static PinnedMemory DeriveKeySourceBytesFromPassword(IPassword password, Int32 keySourceLengthInBytes) { - using (var passwordBytes = new ReadOnlyPinnedMemory(PasswordEncoding.GetBytes(password.RejectIf().IsNullOrEmpty(nameof(password)).OrIf(argument => argument.Length < MinimumPasswordLength, nameof(password), $"The specified password is shorter than {MinimumPasswordLength} characters.")))) + password.RejectIf().IsNull(nameof(password)).OrIf(argument => argument.GetCharacterLength() < MinimumPasswordLength, nameof(password), $"The specified password is shorter than {MinimumPasswordLength} characters."); + using (var passwordBytes = new ReadOnlyPinnedMemory(Password.GetPasswordPlaintextBytes(password))) { - var hashingProcessor = new HashingBinaryProcessor(RandomnessProvider); + var hashingProcessor = new HashingProcessor(RandomnessProvider); using (var saltBytes = new ReadOnlyPinnedMemory(hashingProcessor.CalculateHash(passwordBytes, PasswordSaltHashingAlgorithm))) { @@ -564,6 +567,16 @@ public SymmetricAlgorithmSpecification Algorithm get; } + /// + /// Gets the substitution bytes that are used to fulfill key derivation operations when is + /// equal to . + /// + /// + /// This property is exposed for testing. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal static IEnumerable SubstitutionBoxBytes => SubstitutionBox; + /// /// Gets the PBKDF2 algorithm provider for the current . /// @@ -718,8 +731,8 @@ public SymmetricAlgorithmSpecification Algorithm private static readonly StaticMemberFinalizer StaticMemberFinalizer = new StaticMemberFinalizer(FinalizeStaticMembers); /// - /// Represents substitution bytes that are used to fulfill key derivation operations when is - /// equal to . + /// Represents the substitution bytes that are used to fulfill key derivation operations when + /// is equal to . /// /// /// This sequence is deliberately and carefully balanced. Modifications can introduce severe security flaws and break the diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricProcessor.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricProcessor.cs index 9c84fa68..8fd0d257 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricProcessor.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricProcessor.cs @@ -68,15 +68,6 @@ public static ISymmetricProcessor ForType() public class SymmetricProcessor : ISymmetricProcessor where T : class { - /// - /// Initializes a new instance of the class. - /// - public SymmetricProcessor() - : this(HardenedRandomNumberGenerator.Instance) - { - return; - } - /// /// Initializes a new instance of the class. /// @@ -111,6 +102,16 @@ public SymmetricProcessor(RandomNumberGenerator randomnessProvider, ISerializer< RandomnessProvider = randomnessProvider.RejectIf().IsNull(nameof(randomnessProvider)); } + /// + /// Initializes a new instance of the class. + /// + [DebuggerHidden] + internal SymmetricProcessor() + : this(HardenedRandomNumberGenerator.Instance) + { + return; + } + /// /// Decrypts the specified ciphertext. /// diff --git a/test/RapidField.SolidInstruments.Core.UnitTests/Extensions/ByteCollectionExtensionsTests.cs b/test/RapidField.SolidInstruments.Core.UnitTests/Extensions/ByteCollectionExtensionsTests.cs index 330fcec6..5effd4be 100644 --- a/test/RapidField.SolidInstruments.Core.UnitTests/Extensions/ByteCollectionExtensionsTests.cs +++ b/test/RapidField.SolidInstruments.Core.UnitTests/Extensions/ByteCollectionExtensionsTests.cs @@ -219,5 +219,40 @@ public void PerformCircularShift_ShouldReturnValidResult() resultFive[i].Should().Be(targetFive[i]); } } + + [TestMethod] + public void ToBase64String_ShouldReturnValidResult() + { + // Arrange. + var targetOne = Array.Empty(); + var targetTwo = new Byte[1] { 0x3f }; + var targetThree = new Byte[2] { 0x7b, 0x80 }; + var targetFour = new Byte[2] { 0xe1, 0x93 }; + var targetFive = new Byte[4] { 0x68, 0xc5, 0x8a, 0x13 }; + var expectedResultOne = Convert.ToBase64String(targetOne); + var expectedResultTwo = Convert.ToBase64String(targetTwo); + var expectedResultThree = Convert.ToBase64String(targetThree); + var expectedResultFour = Convert.ToBase64String(targetFour); + var expectedResultFive = Convert.ToBase64String(targetFive); + + // Act. + var resultOne = targetOne.ToBase64String(); + var resultTwo = targetTwo.ToBase64String(); + var resultThree = targetThree.ToBase64String(); + var resultFour = targetFour.ToBase64String(); + var resultFive = targetFive.ToBase64String(); + + // Assert. + resultOne.Should().NotBeNull(); + resultTwo.Should().NotBeNull(); + resultThree.Should().NotBeNull(); + resultFour.Should().NotBeNull(); + resultFive.Should().NotBeNull(); + resultOne.Should().Be(expectedResultOne); + resultTwo.Should().Be(expectedResultTwo); + resultThree.Should().Be(expectedResultThree); + resultFour.Should().Be(expectedResultFour); + resultFive.Should().Be(expectedResultFive); + } } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Hashing/HashingProcessorTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Hashing/HashingProcessorTests.cs index 92e1ac5d..c0b86095 100644 --- a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Hashing/HashingProcessorTests.cs +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Hashing/HashingProcessorTests.cs @@ -6,7 +6,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using RapidField.SolidInstruments.Core.Extensions; using RapidField.SolidInstruments.Cryptography.Hashing; -using RapidField.SolidInstruments.Serialization; using System; using System.Linq; using System.Security.Cryptography; @@ -17,40 +16,64 @@ namespace RapidField.SolidInstruments.Cryptography.UnitTests.Hashing public class HashingProcessorTests { [TestMethod] - public void CalculateHash_ShouldBeDeterministic_ForUnsalted_Sha256() => CalculateHash_ShouldBeDeterministic_ForUnsaltedHashing(HashingAlgorithmSpecification.ShaTwo256); + public void CalculateHash_ShouldBeDeterministic_ForUnsalted_Md5() => CalculateHash_ShouldBeDeterministic_ForUnsaltedHashing(HashingAlgorithmSpecification.Md5); [TestMethod] - public void CalculateHash_ShouldBeDeterministic_ForUnsalted_Sha384() => CalculateHash_ShouldBeDeterministic_ForUnsaltedHashing(HashingAlgorithmSpecification.ShaTwo384); + public void CalculateHash_ShouldBeDeterministic_ForUnsalted_Pbkdf2() => CalculateHash_ShouldBeDeterministic_ForUnsaltedHashing(HashingAlgorithmSpecification.Pbkdf2); [TestMethod] - public void CalculateHash_ShouldBeDeterministic_ForUnsalted_Sha512() => CalculateHash_ShouldBeDeterministic_ForUnsaltedHashing(HashingAlgorithmSpecification.ShaTwo512); + public void CalculateHash_ShouldBeDeterministic_ForUnsalted_ShaTwo256() => CalculateHash_ShouldBeDeterministic_ForUnsaltedHashing(HashingAlgorithmSpecification.ShaTwo256); [TestMethod] - public void CalculateHash_ShouldNotBeDeterministic_ForSalted_Sha256() => CalculateHash_ShouldNotBeDeterministic_ForSaltedHashing(HashingAlgorithmSpecification.ShaTwo256); + public void CalculateHash_ShouldBeDeterministic_ForUnsalted_ShaTwo384() => CalculateHash_ShouldBeDeterministic_ForUnsaltedHashing(HashingAlgorithmSpecification.ShaTwo384); [TestMethod] - public void CalculateHash_ShouldNotBeDeterministic_ForSalted_Sha384() => CalculateHash_ShouldNotBeDeterministic_ForSaltedHashing(HashingAlgorithmSpecification.ShaTwo384); + public void CalculateHash_ShouldBeDeterministic_ForUnsalted_ShaTwo512() => CalculateHash_ShouldBeDeterministic_ForUnsaltedHashing(HashingAlgorithmSpecification.ShaTwo512); [TestMethod] - public void CalculateHash_ShouldNotBeDeterministic_ForSalted_Sha512() => CalculateHash_ShouldNotBeDeterministic_ForSaltedHashing(HashingAlgorithmSpecification.ShaTwo512); + public void CalculateHash_ShouldNotBeDeterministic_ForSalted_Md5() => CalculateHash_ShouldNotBeDeterministic_ForSaltedHashing(HashingAlgorithmSpecification.Md5); [TestMethod] - public void EvaluateHash_ShouldProduceDesiredResults_ForSalted_Sha256() => EvaluateHash_ShouldProduceDesiredResults_ForSaltedHashing(HashingAlgorithmSpecification.ShaTwo256); + public void CalculateHash_ShouldNotBeDeterministic_ForSalted_Pbkdf2() => CalculateHash_ShouldNotBeDeterministic_ForSaltedHashing(HashingAlgorithmSpecification.Pbkdf2); [TestMethod] - public void EvaluateHash_ShouldProduceDesiredResults_ForSalted_Sha384() => EvaluateHash_ShouldProduceDesiredResults_ForSaltedHashing(HashingAlgorithmSpecification.ShaTwo384); + public void CalculateHash_ShouldNotBeDeterministic_ForSalted_ShaTwo256() => CalculateHash_ShouldNotBeDeterministic_ForSaltedHashing(HashingAlgorithmSpecification.ShaTwo256); [TestMethod] - public void EvaluateHash_ShouldProduceDesiredResults_ForSalted_Sha512() => EvaluateHash_ShouldProduceDesiredResults_ForSaltedHashing(HashingAlgorithmSpecification.ShaTwo512); + public void CalculateHash_ShouldNotBeDeterministic_ForSalted_ShaTwo384() => CalculateHash_ShouldNotBeDeterministic_ForSaltedHashing(HashingAlgorithmSpecification.ShaTwo384); [TestMethod] - public void EvaluateHash_ShouldProduceDesiredResults_ForUnsalted_Sha256() => EvaluateHash_ShouldProduceDesiredResults_ForUnsaltedHashing(HashingAlgorithmSpecification.ShaTwo256); + public void CalculateHash_ShouldNotBeDeterministic_ForSalted_ShaTwo512() => CalculateHash_ShouldNotBeDeterministic_ForSaltedHashing(HashingAlgorithmSpecification.ShaTwo512); [TestMethod] - public void EvaluateHash_ShouldProduceDesiredResults_ForUnsalted_Sha384() => EvaluateHash_ShouldProduceDesiredResults_ForUnsaltedHashing(HashingAlgorithmSpecification.ShaTwo384); + public void EvaluateHash_ShouldProduceDesiredResults_ForSalted_Md5() => EvaluateHash_ShouldProduceDesiredResults_ForSaltedHashing(HashingAlgorithmSpecification.Md5); [TestMethod] - public void EvaluateHash_ShouldProduceDesiredResults_ForUnsalted_Sha512() => EvaluateHash_ShouldProduceDesiredResults_ForUnsaltedHashing(HashingAlgorithmSpecification.ShaTwo512); + public void EvaluateHash_ShouldProduceDesiredResults_ForSalted_Pbkdf2() => EvaluateHash_ShouldProduceDesiredResults_ForSaltedHashing(HashingAlgorithmSpecification.Pbkdf2); + + [TestMethod] + public void EvaluateHash_ShouldProduceDesiredResults_ForSalted_ShaTwo256() => EvaluateHash_ShouldProduceDesiredResults_ForSaltedHashing(HashingAlgorithmSpecification.ShaTwo256); + + [TestMethod] + public void EvaluateHash_ShouldProduceDesiredResults_ForSalted_ShaTwo384() => EvaluateHash_ShouldProduceDesiredResults_ForSaltedHashing(HashingAlgorithmSpecification.ShaTwo384); + + [TestMethod] + public void EvaluateHash_ShouldProduceDesiredResults_ForSalted_ShaTwo512() => EvaluateHash_ShouldProduceDesiredResults_ForSaltedHashing(HashingAlgorithmSpecification.ShaTwo512); + + [TestMethod] + public void EvaluateHash_ShouldProduceDesiredResults_ForUnsalted_Md5() => EvaluateHash_ShouldProduceDesiredResults_ForUnsaltedHashing(HashingAlgorithmSpecification.Md5); + + [TestMethod] + public void EvaluateHash_ShouldProduceDesiredResults_ForUnsalted_Pbkdf2() => EvaluateHash_ShouldProduceDesiredResults_ForUnsaltedHashing(HashingAlgorithmSpecification.Pbkdf2); + + [TestMethod] + public void EvaluateHash_ShouldProduceDesiredResults_ForUnsalted_ShaTwo256() => EvaluateHash_ShouldProduceDesiredResults_ForUnsaltedHashing(HashingAlgorithmSpecification.ShaTwo256); + + [TestMethod] + public void EvaluateHash_ShouldProduceDesiredResults_ForUnsalted_ShaTwo384() => EvaluateHash_ShouldProduceDesiredResults_ForUnsaltedHashing(HashingAlgorithmSpecification.ShaTwo384); + + [TestMethod] + public void EvaluateHash_ShouldProduceDesiredResults_ForUnsalted_ShaTwo512() => EvaluateHash_ShouldProduceDesiredResults_ForUnsaltedHashing(HashingAlgorithmSpecification.ShaTwo512); private static void CalculateHash_ShouldBeDeterministic(HashingAlgorithmSpecification algorithm, SaltingMode saltingMode) => CalculateHash_ValidateDeterminism(algorithm, saltingMode, true); @@ -65,8 +88,7 @@ private static void CalculateHash_ValidateDeterminism(HashingAlgorithmSpecificat using (var randomnessProvider = RandomNumberGenerator.Create()) { // Arrange. - var serializer = new PassThroughSerializer(); - var target = new HashingProcessor(randomnessProvider, serializer); + var target = new HashingProcessor(randomnessProvider); var plaintextObject = new Byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 }; // Act. @@ -88,12 +110,12 @@ private static void CalculateHash_ValidateDeterminism(HashingAlgorithmSpecificat if (shouldBeDeterministic) { // Assert. - firstHashValue.ComputeThirtyTwoBitHash().Should().Be(secondHashValue.ComputeThirtyTwoBitHash()); + firstHashValue.Should().BeEquivalentTo(secondHashValue); } else { // Assert. - firstHashValue.ComputeThirtyTwoBitHash().Should().NotBe(secondHashValue.ComputeThirtyTwoBitHash()); + firstHashValue.Should().NotBeEquivalentTo(secondHashValue); } } } @@ -103,8 +125,7 @@ private static void EvaluateHash_ShouldProduceDesiredResults(HashingAlgorithmSpe using (var randomnessProvider = RandomNumberGenerator.Create()) { // Arrange. - var serializer = new PassThroughSerializer(); - var target = new HashingProcessor(randomnessProvider, serializer); + var target = new HashingProcessor(randomnessProvider); var plaintextObject = new Byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 }; var matchingHashValue = target.CalculateHash(plaintextObject, algorithm, saltingMode); var nonMatchingHashValue = matchingHashValue.PerformCircularBitShift(Core.BitShiftDirection.Left, 16); @@ -133,6 +154,11 @@ private static void ValidateDigestLength(Byte[] hashValue, HashingAlgorithmSpeci hashValue.Length.Should().Be(16 + (saltingMode == SaltingMode.Salted ? saltLengthInBytes : 0)); break; + case HashingAlgorithmSpecification.Pbkdf2: + + hashValue.Length.Should().Be(32 + (saltingMode == SaltingMode.Salted ? saltLengthInBytes : 0)); + break; + case HashingAlgorithmSpecification.ShaTwo256: hashValue.Length.Should().Be(32 + (saltingMode == SaltingMode.Salted ? saltLengthInBytes : 0)); diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/PasswordTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/PasswordTests.cs new file mode 100644 index 00000000..89f48994 --- /dev/null +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/PasswordTests.cs @@ -0,0 +1,303 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RapidField.SolidInstruments.Collections; +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Cryptography.Secrets; +using System; +using System.Linq; + +namespace RapidField.SolidInstruments.Cryptography.UnitTests.Secrets +{ + [TestClass] + public class PasswordTests + { + [TestMethod] + public void EvaluateSecureHashString_ShouldProduceDesiredResults() + { + // Arrange. + using (var referenceManager = new ReferenceManager()) + { + var passwords = new IPassword[] + { + Password.FromAsciiString("0"), + Password.FromAsciiString("1"), + Password.FromAsciiString("2"), + Password.FromAsciiString(" "), + Password.FromAsciiString("foo"), + Password.FromAsciiString("bar"), + Password.FromAsciiString("baz"), + Password.NewRandomStrongPassword(), + Password.NewRandomStrongPassword(), + Password.NewRandomStrongPassword() + }; + var passwordHashStringsOne = new String[] + { + passwords[0].CalculateSecureHashString(), + passwords[1].CalculateSecureHashString(), + passwords[2].CalculateSecureHashString(), + passwords[3].CalculateSecureHashString(), + passwords[4].CalculateSecureHashString(), + passwords[5].CalculateSecureHashString(), + passwords[6].CalculateSecureHashString(), + passwords[7].CalculateSecureHashString(), + passwords[8].CalculateSecureHashString(), + passwords[9].CalculateSecureHashString() + }; + var passwordHashStringsTwo = new String[] + { + passwords[0].CalculateSecureHashString(), + passwords[1].CalculateSecureHashString(), + passwords[2].CalculateSecureHashString(), + passwords[3].CalculateSecureHashString(), + passwords[4].CalculateSecureHashString(), + passwords[5].CalculateSecureHashString(), + passwords[6].CalculateSecureHashString(), + passwords[7].CalculateSecureHashString(), + passwords[8].CalculateSecureHashString(), + passwords[9].CalculateSecureHashString() + }; + + // Assert. + passwordHashStringsOne.Should().NotContainNulls(); + passwordHashStringsTwo.Should().NotContainNulls(); + passwordHashStringsOne.Should().NotIntersectWith(passwordHashStringsTwo); + + for (var i = 0; i < passwords.Length; i++) + { + // Act. + var password = passwords[i]; + referenceManager.AddObject(password); + var passwordHashStringOne = passwordHashStringsOne[i]; + var passwordHashStringTwo = passwordHashStringsTwo[i]; + + // Assert. + passwordHashStringOne.Should().NotBeNullOrEmpty(); + passwordHashStringTwo.Should().NotBeNullOrEmpty(); + + // Act. + var passwordHashStringOneMathcesPassword = password.EvaluateSecureHashString(passwordHashStringOne); + var passwordHashStringTwoMathcesPassword = password.EvaluateSecureHashString(passwordHashStringTwo); + + // Assert. + passwordHashStringOne.Should().NotBe(passwordHashStringTwo); + passwordHashStringOneMathcesPassword.Should().BeTrue(); + passwordHashStringTwoMathcesPassword.Should().BeTrue(); + } + + // Assert. + passwords[0].EvaluateSecureHashString(passwordHashStringsOne[1]).Should().BeFalse(); + passwords[1].EvaluateSecureHashString(passwordHashStringsOne[2]).Should().BeFalse(); + passwords[2].EvaluateSecureHashString(passwordHashStringsOne[3]).Should().BeFalse(); + passwords[3].EvaluateSecureHashString(passwordHashStringsOne[4]).Should().BeFalse(); + passwords[4].EvaluateSecureHashString(passwordHashStringsOne[5]).Should().BeFalse(); + passwords[5].EvaluateSecureHashString(passwordHashStringsOne[6]).Should().BeFalse(); + passwords[6].EvaluateSecureHashString(passwordHashStringsOne[7]).Should().BeFalse(); + passwords[7].EvaluateSecureHashString(passwordHashStringsOne[8]).Should().BeFalse(); + passwords[8].EvaluateSecureHashString(passwordHashStringsOne[9]).Should().BeFalse(); + } + } + + [TestMethod] + public void EvaluateSecureHashValue_ShouldProduceDesiredResults() + { + // Arrange. + using (var referenceManager = new ReferenceManager()) + { + var passwords = new IPassword[] + { + Password.FromAsciiString("0"), + Password.FromAsciiString("1"), + Password.FromAsciiString("2"), + Password.FromAsciiString(" "), + Password.FromAsciiString("foo"), + Password.FromAsciiString("bar"), + Password.FromAsciiString("baz"), + Password.NewRandomStrongPassword(), + Password.NewRandomStrongPassword(), + Password.NewRandomStrongPassword() + }; + var passwordHashValuesOne = new IReadOnlyPinnedMemory[] + { + passwords[0].CalculateSecureHashValue(), + passwords[1].CalculateSecureHashValue(), + passwords[2].CalculateSecureHashValue(), + passwords[3].CalculateSecureHashValue(), + passwords[4].CalculateSecureHashValue(), + passwords[5].CalculateSecureHashValue(), + passwords[6].CalculateSecureHashValue(), + passwords[7].CalculateSecureHashValue(), + passwords[8].CalculateSecureHashValue(), + passwords[9].CalculateSecureHashValue() + }; + var passwordHashValuesTwo = new IReadOnlyPinnedMemory[] + { + passwords[0].CalculateSecureHashValue(), + passwords[1].CalculateSecureHashValue(), + passwords[2].CalculateSecureHashValue(), + passwords[3].CalculateSecureHashValue(), + passwords[4].CalculateSecureHashValue(), + passwords[5].CalculateSecureHashValue(), + passwords[6].CalculateSecureHashValue(), + passwords[7].CalculateSecureHashValue(), + passwords[8].CalculateSecureHashValue(), + passwords[9].CalculateSecureHashValue() + }; + + for (var i = 0; i < passwords.Length; i++) + { + // Act. + var password = passwords[i]; + referenceManager.AddObject(password); + var passwordHashValueOne = passwordHashValuesOne[i]; + var passwordHashValueTwo = passwordHashValuesTwo[i]; + + // Assert. + passwordHashValueOne.Should().NotBeNullOrEmpty(); + passwordHashValueTwo.Should().NotBeNullOrEmpty(); + + // Act. + var passwordHashStringOneMathcesPassword = password.EvaluateSecureHashValue(passwordHashValueOne.ToArray()); + var passwordHashStringTwoMathcesPassword = password.EvaluateSecureHashValue(passwordHashValueTwo.ToArray()); + + // Assert. + passwordHashValueOne.Should().NotBeEquivalentTo(passwordHashValueTwo); + passwordHashStringOneMathcesPassword.Should().BeTrue(); + passwordHashStringTwoMathcesPassword.Should().BeTrue(); + } + + // Assert. + passwords[0].EvaluateSecureHashValue(passwordHashValuesOne[1].ToArray()).Should().BeFalse(); + passwords[1].EvaluateSecureHashValue(passwordHashValuesOne[2].ToArray()).Should().BeFalse(); + passwords[2].EvaluateSecureHashValue(passwordHashValuesOne[3].ToArray()).Should().BeFalse(); + passwords[3].EvaluateSecureHashValue(passwordHashValuesOne[4].ToArray()).Should().BeFalse(); + passwords[4].EvaluateSecureHashValue(passwordHashValuesOne[5].ToArray()).Should().BeFalse(); + passwords[5].EvaluateSecureHashValue(passwordHashValuesOne[6].ToArray()).Should().BeFalse(); + passwords[6].EvaluateSecureHashValue(passwordHashValuesOne[7].ToArray()).Should().BeFalse(); + passwords[7].EvaluateSecureHashValue(passwordHashValuesOne[8].ToArray()).Should().BeFalse(); + passwords[8].EvaluateSecureHashValue(passwordHashValuesOne[9].ToArray()).Should().BeFalse(); + } + } + + [TestMethod] + public void FromAsciiString_ShouldProduceDesiredResults_ForValidPasswordString() + { + // Arrange. + var password = "foo BAR 123 !@# "; + + // Act. + using var target = Password.FromAsciiString(password); + + // Assert. + target.Should().NotBeNull(); + target.ReadAsync((String plaintext) => + { + plaintext.Should().NotBeNullOrEmpty(); + plaintext.Should().Be(password); + }).Wait(); + } + + [TestMethod] + public void FromUnicodeString_ShouldProduceDesiredResults_ForValidPasswordString() + { + // Arrange. + var password = "foo BAR 123 !@# "; + + // Act. + using var target = Password.FromUnicodeString(password); + + // Assert. + target.Should().NotBeNull(); + target.ReadAsync((String plaintext) => + { + plaintext.Should().NotBeNullOrEmpty(); + plaintext.Should().Be(password); + }).Wait(); + } + + [TestMethod] + public void FromUtf8String_ShouldProduceDesiredResults_ForValidPasswordString() + { + // Arrange. + var password = "foo BAR 123 !@# "; + + // Act. + using var target = Password.FromUtf8String(password); + + // Assert. + target.Should().NotBeNull(); + target.ReadAsync((String plaintext) => + { + plaintext.Should().NotBeNullOrEmpty(); + plaintext.Should().Be(password); + }).Wait(); + } + + [TestMethod] + public void MeetsRequirements_ShouldProduceDesiredResults() + { + // Arrange. + var requirementsNone = PasswordCompositionRequirements.None; + var requirementsNist = PasswordCompositionRequirements.Nist800633; + var requirementsStrict = PasswordCompositionRequirements.Strict; + using var passwordOne = Password.FromAsciiString("0"); + using var passwordTwo = Password.FromAsciiString("foo123!"); + using var passwordThree = Password.FromAsciiString("FooBar 123!"); + using var passwordFour = Password.FromAsciiString("1Q2w3e4r5t"); + using var passwordFive = Password.FromAsciiString("FooBarBazBuzz 123!"); + + // Act. + var passwordOneMeetsRequirementsNone = passwordOne.MeetsRequirements(requirementsNone); + var passwordOneMeetsRequirementsNist = passwordOne.MeetsRequirements(requirementsNist); + var passwordOneMeetsRequirementsStrict = passwordOne.MeetsRequirements(requirementsStrict); + var passwordTwoMeetsRequirementsNone = passwordTwo.MeetsRequirements(requirementsNone); + var passwordTwoMeetsRequirementsNist = passwordTwo.MeetsRequirements(requirementsNist); + var passwordTwoMeetsRequirementsStrict = passwordTwo.MeetsRequirements(requirementsStrict); + var passwordThreeMeetsRequirementsNone = passwordThree.MeetsRequirements(requirementsNone); + var passwordThreeMeetsRequirementsNist = passwordThree.MeetsRequirements(requirementsNist); + var passwordThreeMeetsRequirementsStrict = passwordThree.MeetsRequirements(requirementsStrict); + var passwordFourMeetsRequirementsNone = passwordFour.MeetsRequirements(requirementsNone); + var passwordFourMeetsRequirementsNist = passwordFour.MeetsRequirements(requirementsNist); + var passwordFourMeetsRequirementsStrict = passwordFour.MeetsRequirements(requirementsStrict); + var passwordFiveMeetsRequirementsNone = passwordFive.MeetsRequirements(requirementsNone); + var passwordFiveMeetsRequirementsNist = passwordFive.MeetsRequirements(requirementsNist); + var passwordFiveMeetsRequirementsStrict = passwordFive.MeetsRequirements(requirementsStrict); + + // Assert. + passwordOneMeetsRequirementsNone.Should().BeTrue(); + passwordOneMeetsRequirementsNist.Should().BeFalse(); + passwordOneMeetsRequirementsStrict.Should().BeFalse(); + passwordTwoMeetsRequirementsNone.Should().BeTrue(); + passwordTwoMeetsRequirementsNist.Should().BeFalse(); + passwordTwoMeetsRequirementsStrict.Should().BeFalse(); + passwordThreeMeetsRequirementsNone.Should().BeTrue(); + passwordThreeMeetsRequirementsNist.Should().BeTrue(); + passwordThreeMeetsRequirementsStrict.Should().BeFalse(); + passwordFourMeetsRequirementsNone.Should().BeTrue(); + passwordFourMeetsRequirementsNist.Should().BeFalse(); + passwordFourMeetsRequirementsStrict.Should().BeFalse(); + passwordFiveMeetsRequirementsNone.Should().BeTrue(); + passwordFiveMeetsRequirementsNist.Should().BeTrue(); + passwordFiveMeetsRequirementsStrict.Should().BeTrue(); + } + + [TestMethod] + public void NewRandomStrongPassword_ShouldProduceDesiredResults() + { + // Arrange. + var iterationCount = 30; + + for (var i = 0; i < iterationCount; i++) + { + // Act. + using var password = Password.NewRandomStrongPassword(); + + // Assert. + password.MeetsRequirements(PasswordCompositionRequirements.Strict).Should().BeTrue(); + } + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SecretTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SecretTests.cs index 2adee611..9dc3d0b2 100644 --- a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SecretTests.cs +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SecretTests.cs @@ -22,6 +22,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults() var valueTwo = Array.Empty(); var valueThree = new Byte[] { 0xcc, 0xff }; var hashCode = 0; + var derivedIdentity = Guid.Empty; using (var target = new Secret(name)) { @@ -32,6 +33,8 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults() target.Name.Should().Be(name); target.HasValue.Should().BeFalse(); target.ValueType.Should().Be(typeof(IReadOnlyPinnedMemory)); + derivedIdentity = target.DerivedIdentity; + derivedIdentity.Should().NotBe(Guid.Empty); // Act. target.Write(() => new PinnedMemory(valueOne)); @@ -41,6 +44,8 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults() hashCode = target.GetHashCode(); hashCode.Should().Be(target.GetHashCode()); target.HasValue.Should().BeTrue(); + target.DerivedIdentity.Should().NotBe(derivedIdentity); + derivedIdentity = target.DerivedIdentity; target.Read(secret => { secret.ReadOnlySpan.ToArray().Should().BeEquivalentTo(valueOne); @@ -53,6 +58,8 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults() hashCode.Should().NotBe(target.GetHashCode()); hashCode = target.GetHashCode(); hashCode.Should().Be(target.GetHashCode()); + target.DerivedIdentity.Should().NotBe(derivedIdentity); + derivedIdentity = target.DerivedIdentity; target.ReadAsync(secret => { secret.ReadOnlySpan.ToArray().Should().BeEquivalentTo(valueTwo); @@ -65,6 +72,8 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults() hashCode.Should().NotBe(target.GetHashCode()); hashCode = target.GetHashCode(); hashCode.Should().Be(target.GetHashCode()); + target.DerivedIdentity.Should().NotBe(derivedIdentity); + derivedIdentity = target.DerivedIdentity; target.Read(secret => { secret.ReadOnlySpan.ToArray().Should().BeEquivalentTo(valueThree); diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SecretVaultTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SecretVaultTests.cs index c9581113..b0cbfc14 100644 --- a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SecretVaultTests.cs +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SecretVaultTests.cs @@ -17,6 +17,24 @@ namespace RapidField.SolidInstruments.Cryptography.UnitTests.Secrets [TestClass] public class SecretVaultTests { + [TestMethod] + public void ExportAsync_ShouldBeReversible_UsingExplicitCascadingSymmetricKey() + { + // TODO Write this test. + } + + [TestMethod] + public void ExportAsync_ShouldBeReversible_UsingExplicitSymmetricKey() + { + // TODO Write this test. + } + + [TestMethod] + public void ExportAsync_ShouldBeReversible_UsingMasterKey() + { + // TODO Write this test. + } + [TestMethod] public void ExportEncryptedSecret_ShouldBeReversible_ForCascadingSymmetricKeySecrets_AsCiphertext_UsingExplicitKey() { @@ -55,11 +73,10 @@ public void ExportEncryptedSecret_ShouldBeReversible_ForCascadingSymmetricKeySec { // Arrange. var secretName = "foo"; - var password = "12345 bar BAZ !"; using var secret = CascadingSymmetricKey.New(); - using var key = SymmetricKey.New(); - using var targetOne = new SecretVault(password); - using var targetTwo = new SecretVault(password); + using var masterPassword = Password.FromUnicodeString("12345 bar BAZ !"); + using var targetOne = new SecretVault(masterPassword); + using var targetTwo = new SecretVault(masterPassword); // Act. targetOne.AddOrUpdate(secretName, secret); @@ -118,12 +135,11 @@ public void ExportEncryptedSecret_ShouldBeReversible_ForGuidSecrets_AsCiphertext public void ExportEncryptedSecret_ShouldBeReversible_ForGuidSecrets_AsCiphertext_UsingMasterKey() { // Arrange. - var password = "12345 bar BAZ !"; var secretName = "foo"; var secret = Guid.NewGuid(); - using var key = SymmetricKey.New(); - using var targetOne = new SecretVault(password); - using var targetTwo = new SecretVault(password); + using var masterPassword = Password.FromUnicodeString("12345 bar BAZ !"); + using var targetOne = new SecretVault(masterPassword); + using var targetTwo = new SecretVault(masterPassword); // Act. targetOne.AddOrUpdate(secretName, secret); @@ -182,12 +198,11 @@ public void ExportEncryptedSecret_ShouldBeReversible_ForNumericSecrets_AsCiphert public void ExportEncryptedSecret_ShouldBeReversible_ForNumericSecrets_AsCiphertext_UsingMasterKey() { // Arrange. - var password = "12345 bar BAZ !"; var secretName = "foo"; var secret = 1234.56789d; - using var key = SymmetricKey.New(); - using var targetOne = new SecretVault(password); - using var targetTwo = new SecretVault(password); + using var masterPassword = Password.FromUnicodeString("12345 bar BAZ !"); + using var targetOne = new SecretVault(masterPassword); + using var targetTwo = new SecretVault(masterPassword); // Act. targetOne.AddOrUpdate(secretName, secret); @@ -248,12 +263,11 @@ public void ExportEncryptedSecret_ShouldBeReversible_ForStringSecrets_AsCipherte public void ExportEncryptedSecret_ShouldBeReversible_ForStringSecrets_AsCiphertext_UsingMasterKey() { // Arrange. - var password = "12345 bar BAZ !"; var secretName = "foo"; var secret = "baz"; - using var key = SymmetricKey.New(); - using var targetOne = new SecretVault(password); - using var targetTwo = new SecretVault(password); + using var masterPassword = Password.FromUnicodeString("12345 bar BAZ !"); + using var targetOne = new SecretVault(masterPassword); + using var targetTwo = new SecretVault(masterPassword); // Act. targetOne.AddOrUpdate(secretName, secret); @@ -314,12 +328,11 @@ public void ExportEncryptedSecret_ShouldBeReversible_ForSymmetricKeySecrets_AsCi public void ExportEncryptedSecret_ShouldBeReversible_ForSymmetricKeySecrets_AsCiphertext_UsingMasterKey() { // Arrange. - var password = "12345 bar BAZ !"; var secretName = "foo"; using var secret = SymmetricKey.New(); - using var key = SymmetricKey.New(); - using var targetOne = new SecretVault(password); - using var targetTwo = new SecretVault(password); + using var masterPassword = Password.FromUnicodeString("12345 bar BAZ !"); + using var targetOne = new SecretVault(masterPassword); + using var targetTwo = new SecretVault(masterPassword); // Act. targetOne.AddOrUpdate(secretName, secret); @@ -382,14 +395,13 @@ public void ExportEncryptedSecret_ShouldBeReversible_ForX509CertificateSecrets_A public void ExportEncryptedSecret_ShouldBeReversible_ForX509CertificateSecrets_AsCiphertext_UsingMasterKey() { // Arrange. - var password = "12345 bar BAZ !"; var secretName = "foo"; var fileName = "TestRootOne.testcert"; var subject = "CN=TestRootOne"; var secret = new X509Certificate2(fileName); - using var key = SymmetricKey.New(); - using var targetOne = new SecretVault(password); - using var targetTwo = new SecretVault(password); + using var masterPassword = Password.FromUnicodeString("12345 bar BAZ !"); + using var targetOne = new SecretVault(masterPassword); + using var targetTwo = new SecretVault(masterPassword); // Act. targetOne.AddOrUpdate(secretName, secret); @@ -413,6 +425,38 @@ public void ExportEncryptedSecret_ShouldBeReversible_ForX509CertificateSecrets_A }).Wait(); } + [TestMethod] + public void ExportMasterKey_ShouldBeReversible() + { + // Arrange. + using var masterPassword = Password.FromUnicodeString("12345 bar BAZ !"); + using var targetOne = new SecretVault(masterPassword); + using var targetTwo = new SecretVault(); + + // Act. + using var exportMasterKeyTaskOne = targetOne.ExportMasterKeyAsync(); + exportMasterKeyTaskOne.Wait(); + targetTwo.ImportSecret(exportMasterKeyTaskOne.Result); + using var exportMasterKeyTaskTwo = targetTwo.ExportSecretAsync(exportMasterKeyTaskOne.Result.Name); + exportMasterKeyTaskTwo.Wait(); + targetOne.ImportSecret(exportMasterKeyTaskTwo.Result); + + // Arrange. + using var masterKeySecretOne = exportMasterKeyTaskOne.Result.ToSecret(); + using var masterKeySecretTwo = exportMasterKeyTaskTwo.Result.ToSecret(); + + masterKeySecretOne.Read(masterKeyOne => + { + masterKeySecretTwo.Read(masterKeyTwo => + { + // Assert. + masterKeyOne.Should().NotBeNullOrEmpty(); + masterKeyTwo.Should().NotBeNullOrEmpty(); + masterKeyOne.Should().BeEquivalentTo(masterKeyTwo); + }); + }); + } + [TestMethod] public void ExportSecret_ShouldBeReversible_ForCascadingSymmetricKeySecrets_AsPlaintext() { @@ -638,14 +682,14 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForCascadingSymme { // Assert. target.Count.Should().Be(0); - target.Names.Should().BeEmpty(); + target.SecretNames.Should().BeEmpty(); // Act. target.AddOrUpdate(secretOneName, secretOne); // Assert. target.Count.Should().Be(1); - target.Names.Should().Contain(secretOneName); + target.SecretNames.Should().Contain(secretOneName); target.ReadAsync(secretOneName, (CascadingSymmetricKey value) => { value.Should().NotBeNull(); @@ -656,8 +700,8 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForCascadingSymme // Assert. target.Count.Should().Be(2); - target.Names.Should().Contain(secretOneName); - target.Names.Should().Contain(secretTwoName); + target.SecretNames.Should().Contain(secretOneName); + target.SecretNames.Should().Contain(secretTwoName); target.ReadAsync(secretTwoName, (CascadingSymmetricKey value) => { value.Should().NotBeNull(); @@ -668,7 +712,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForCascadingSymme // Assert. target.Count.Should().Be(2); - target.Names.Should().Contain(secretOneName); + target.SecretNames.Should().Contain(secretOneName); target.ReadAsync(secretOneName, (CascadingSymmetricKey value) => { value.Should().NotBeNull(); @@ -680,15 +724,15 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForCascadingSymme // Assert. target.Count.Should().Be(1); - target.Names.Should().NotContain(secretOneName); - target.Names.Should().Contain(secretTwoName); + target.SecretNames.Should().NotContain(secretOneName); + target.SecretNames.Should().Contain(secretTwoName); // Act. target.Clear(); // Assert. target.Count.Should().Be(0); - target.Names.Should().BeEmpty(); + target.SecretNames.Should().BeEmpty(); } } @@ -708,14 +752,14 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForGuidSecrets() { // Assert. target.Count.Should().Be(0); - target.Names.Should().BeEmpty(); + target.SecretNames.Should().BeEmpty(); // Act. target.AddOrUpdate(secretOneName, secretOne); // Assert. target.Count.Should().Be(1); - target.Names.Should().Contain(secretOneName); + target.SecretNames.Should().Contain(secretOneName); target.ReadAsync(secretOneName, (Guid value) => { value.Should().Be(secretOne); @@ -726,8 +770,8 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForGuidSecrets() // Assert. target.Count.Should().Be(2); - target.Names.Should().Contain(secretOneName); - target.Names.Should().Contain(secretTwoName); + target.SecretNames.Should().Contain(secretOneName); + target.SecretNames.Should().Contain(secretTwoName); target.ReadAsync(secretTwoName, (Guid value) => { value.Should().Be(secretTwo); @@ -738,7 +782,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForGuidSecrets() // Assert. target.Count.Should().Be(2); - target.Names.Should().Contain(secretOneName); + target.SecretNames.Should().Contain(secretOneName); target.ReadAsync(secretOneName, (Guid value) => { value.Should().Be(secretThree); @@ -750,15 +794,15 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForGuidSecrets() // Assert. target.Count.Should().Be(1); - target.Names.Should().NotContain(secretOneName); - target.Names.Should().Contain(secretTwoName); + target.SecretNames.Should().NotContain(secretOneName); + target.SecretNames.Should().Contain(secretTwoName); // Act. target.Clear(); // Assert. target.Count.Should().Be(0); - target.Names.Should().BeEmpty(); + target.SecretNames.Should().BeEmpty(); } } } @@ -779,14 +823,14 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForNumericSecrets { // Assert. target.Count.Should().Be(0); - target.Names.Should().BeEmpty(); + target.SecretNames.Should().BeEmpty(); // Act. target.AddOrUpdate(secretOneName, secretOne); // Assert. target.Count.Should().Be(1); - target.Names.Should().Contain(secretOneName); + target.SecretNames.Should().Contain(secretOneName); target.ReadAsync(secretOneName, (Double value) => { value.Should().Be(secretOne); @@ -797,8 +841,8 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForNumericSecrets // Assert. target.Count.Should().Be(2); - target.Names.Should().Contain(secretOneName); - target.Names.Should().Contain(secretTwoName); + target.SecretNames.Should().Contain(secretOneName); + target.SecretNames.Should().Contain(secretTwoName); target.ReadAsync(secretTwoName, (Double value) => { value.Should().Be(secretTwo); @@ -809,7 +853,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForNumericSecrets // Assert. target.Count.Should().Be(2); - target.Names.Should().Contain(secretOneName); + target.SecretNames.Should().Contain(secretOneName); target.ReadAsync(secretOneName, (Double value) => { value.Should().Be(secretThree); @@ -821,15 +865,15 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForNumericSecrets // Assert. target.Count.Should().Be(1); - target.Names.Should().NotContain(secretOneName); - target.Names.Should().Contain(secretTwoName); + target.SecretNames.Should().NotContain(secretOneName); + target.SecretNames.Should().Contain(secretTwoName); // Act. target.Clear(); // Assert. target.Count.Should().Be(0); - target.Names.Should().BeEmpty(); + target.SecretNames.Should().BeEmpty(); } } } @@ -853,14 +897,14 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForStandardSecret { // Assert. target.Count.Should().Be(0); - target.Names.Should().BeEmpty(); + target.SecretNames.Should().BeEmpty(); // Act. target.AddOrUpdate(secretOneName, secretOne); // Assert. target.Count.Should().Be(1); - target.Names.Should().Contain(secretOneName); + target.SecretNames.Should().Contain(secretOneName); target.ReadAsync(secretOneName, (IReadOnlyPinnedMemory value) => { value.Should().BeEquivalentTo(secretOne); @@ -871,8 +915,8 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForStandardSecret // Assert. target.Count.Should().Be(2); - target.Names.Should().Contain(secretOneName); - target.Names.Should().Contain(secretTwoName); + target.SecretNames.Should().Contain(secretOneName); + target.SecretNames.Should().Contain(secretTwoName); target.ReadAsync(secretTwoName, (IReadOnlyPinnedMemory value) => { value.Should().BeEquivalentTo(secretTwo); @@ -883,7 +927,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForStandardSecret // Assert. target.Count.Should().Be(2); - target.Names.Should().Contain(secretOneName); + target.SecretNames.Should().Contain(secretOneName); target.ReadAsync(secretOneName, (IReadOnlyPinnedMemory value) => { value.Should().BeEquivalentTo(secretThree); @@ -895,15 +939,15 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForStandardSecret // Assert. target.Count.Should().Be(1); - target.Names.Should().NotContain(secretOneName); - target.Names.Should().Contain(secretTwoName); + target.SecretNames.Should().NotContain(secretOneName); + target.SecretNames.Should().Contain(secretTwoName); // Act. target.Clear(); // Assert. target.Count.Should().Be(0); - target.Names.Should().BeEmpty(); + target.SecretNames.Should().BeEmpty(); } } } @@ -924,14 +968,14 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForStringSecrets( { // Assert. target.Count.Should().Be(0); - target.Names.Should().BeEmpty(); + target.SecretNames.Should().BeEmpty(); // Act. target.AddOrUpdate(secretOneName, secretOne); // Assert. target.Count.Should().Be(1); - target.Names.Should().Contain(secretOneName); + target.SecretNames.Should().Contain(secretOneName); target.ReadAsync(secretOneName, (String value) => { value.Should().Be(secretOne); @@ -942,8 +986,8 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForStringSecrets( // Assert. target.Count.Should().Be(2); - target.Names.Should().Contain(secretOneName); - target.Names.Should().Contain(secretTwoName); + target.SecretNames.Should().Contain(secretOneName); + target.SecretNames.Should().Contain(secretTwoName); target.ReadAsync(secretTwoName, (String value) => { value.Should().Be(secretTwo); @@ -954,7 +998,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForStringSecrets( // Assert. target.Count.Should().Be(2); - target.Names.Should().Contain(secretOneName); + target.SecretNames.Should().Contain(secretOneName); target.ReadAsync(secretOneName, (String value) => { value.Should().Be(secretThree); @@ -966,15 +1010,15 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForStringSecrets( // Assert. target.Count.Should().Be(1); - target.Names.Should().NotContain(secretOneName); - target.Names.Should().Contain(secretTwoName); + target.SecretNames.Should().NotContain(secretOneName); + target.SecretNames.Should().Contain(secretTwoName); // Act. target.Clear(); // Assert. target.Count.Should().Be(0); - target.Names.Should().BeEmpty(); + target.SecretNames.Should().BeEmpty(); } } } @@ -993,14 +1037,14 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForSymmetricKeySe { // Assert. target.Count.Should().Be(0); - target.Names.Should().BeEmpty(); + target.SecretNames.Should().BeEmpty(); // Act. target.AddOrUpdate(secretOneName, secretOne); // Assert. target.Count.Should().Be(1); - target.Names.Should().Contain(secretOneName); + target.SecretNames.Should().Contain(secretOneName); target.ReadAsync(secretOneName, (SymmetricKey value) => { value.Should().NotBeNull(); @@ -1011,8 +1055,8 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForSymmetricKeySe // Assert. target.Count.Should().Be(2); - target.Names.Should().Contain(secretOneName); - target.Names.Should().Contain(secretTwoName); + target.SecretNames.Should().Contain(secretOneName); + target.SecretNames.Should().Contain(secretTwoName); target.ReadAsync(secretTwoName, (SymmetricKey value) => { value.Should().NotBeNull(); @@ -1023,7 +1067,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForSymmetricKeySe // Assert. target.Count.Should().Be(2); - target.Names.Should().Contain(secretOneName); + target.SecretNames.Should().Contain(secretOneName); target.ReadAsync(secretOneName, (SymmetricKey value) => { value.Should().NotBeNull(); @@ -1035,15 +1079,15 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForSymmetricKeySe // Assert. target.Count.Should().Be(1); - target.Names.Should().NotContain(secretOneName); - target.Names.Should().Contain(secretTwoName); + target.SecretNames.Should().NotContain(secretOneName); + target.SecretNames.Should().Contain(secretTwoName); // Act. target.Clear(); // Assert. target.Count.Should().Be(0); - target.Names.Should().BeEmpty(); + target.SecretNames.Should().BeEmpty(); } } @@ -1067,14 +1111,14 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForX509Certificat { // Assert. target.Count.Should().Be(0); - target.Names.Should().BeEmpty(); + target.SecretNames.Should().BeEmpty(); // Act. target.AddOrUpdate(secretOneName, secretOne); // Assert. target.Count.Should().Be(1); - target.Names.Should().Contain(secretOneName); + target.SecretNames.Should().Contain(secretOneName); target.ReadAsync(secretOneName, (X509Certificate2 value) => { value.Should().NotBeNull(); @@ -1086,8 +1130,8 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForX509Certificat // Assert. target.Count.Should().Be(2); - target.Names.Should().Contain(secretOneName); - target.Names.Should().Contain(secretTwoName); + target.SecretNames.Should().Contain(secretOneName); + target.SecretNames.Should().Contain(secretTwoName); target.ReadAsync(secretTwoName, (X509Certificate2 value) => { value.Should().NotBeNull(); @@ -1099,7 +1143,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForX509Certificat // Assert. target.Count.Should().Be(2); - target.Names.Should().Contain(secretOneName); + target.SecretNames.Should().Contain(secretOneName); target.ReadAsync(secretOneName, (X509Certificate2 value) => { value.Should().NotBeNull(); @@ -1112,15 +1156,15 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForX509Certificat // Assert. target.Count.Should().Be(1); - target.Names.Should().NotContain(secretOneName); - target.Names.Should().Contain(secretTwoName); + target.SecretNames.Should().NotContain(secretOneName); + target.SecretNames.Should().Contain(secretTwoName); // Act. target.Clear(); // Assert. target.Count.Should().Be(0); - target.Names.Should().BeEmpty(); + target.SecretNames.Should().BeEmpty(); } } } diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/SecureMemoryTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/SecureMemoryTests.cs index 964f3d4e..1cf8958a 100644 --- a/test/RapidField.SolidInstruments.Cryptography.UnitTests/SecureMemoryTests.cs +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/SecureMemoryTests.cs @@ -177,8 +177,14 @@ public void RegeneratePrivateKey_ShouldProduceDesiredResults() memory[referenceBytePosition] = referenceByte; }); + // Assert. + ((ISecureMemory)target).PrivateKeyVersion.Should().Be(0); + // Act. - target.RegeneratePrivateKey(); + ((ISecureMemory)target).RegeneratePrivateKey(); + + // Assert. + ((ISecureMemory)target).PrivateKeyVersion.Should().Be(1); using (var privateKeyTwo = target.DerivePrivateKey()) { @@ -191,7 +197,10 @@ public void RegeneratePrivateKey_ShouldProduceDesiredResults() }); // Act. - target.RegeneratePrivateKey(); + ((ISecureMemory)target).RegeneratePrivateKey(); + + // Assert. + ((ISecureMemory)target).PrivateKeyVersion.Should().Be(2); using (var privateKeyThree = target.DerivePrivateKey()) { diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/CascadingSymmetricKeyTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/CascadingSymmetricKeyTests.cs index 58a87b50..ad58c859 100644 --- a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/CascadingSymmetricKeyTests.cs +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/CascadingSymmetricKeyTests.cs @@ -6,6 +6,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using RapidField.SolidInstruments.Core.Concurrency; using RapidField.SolidInstruments.Cryptography.Extensions; +using RapidField.SolidInstruments.Cryptography.Secrets; using RapidField.SolidInstruments.Cryptography.Symmetric; using System; using System.Linq; @@ -24,7 +25,7 @@ public void FromPassword_ShouldBeRepeatable_UsingBasicMethod() // Arrange. var processor = new SymmetricStringProcessor(randomnessProvider); var plaintextObject = "䆟`ಮ䷆ʘ‣⦸⏹ⰄͶa✰ṁ亡Zᨖ0༂⽔9㗰"; - var password = randomnessProvider.GetString(SymmetricKey.MinimumPasswordLength, true, true, true, true, true, true, false); + using var password = Password.FromUnicodeString(randomnessProvider.GetString(SymmetricKey.MinimumPasswordLength, true, true, true, true, true, true, false)); // Act. using (var targetOne = CascadingSymmetricKey.FromPassword(password)) @@ -59,7 +60,7 @@ public void FromPassword_ShouldBeRepeatable_UsingFourAlgorithms() var fourthLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Ecb; var derivationMode = SymmetricKeyDerivationMode.XorLayeringWithSubstitution; var plaintextObject = "䆟`ಮ䷆ʘ‣⦸⏹ⰄͶa✰ṁ亡Zᨖ0༂⽔9㗰"; - var password = randomnessProvider.GetString(SymmetricKey.MinimumPasswordLength, true, true, true, true, true, true, false); + using var password = Password.FromUnicodeString(randomnessProvider.GetString(SymmetricKey.MinimumPasswordLength, true, true, true, true, true, true, false)); // Act. using (var targetOne = CascadingSymmetricKey.FromPassword(password, derivationMode, firstLayerAlgorithm, secondLayerAlgorithm, thirdLayerAlgorithm, fourthLayerAlgorithm)) @@ -93,7 +94,7 @@ public void FromPassword_ShouldBeRepeatable_UsingThreeAlgorithms() var thirdLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Cbc; var derivationMode = SymmetricKeyDerivationMode.XorLayeringWithSubstitution; var plaintextObject = "䆟`ಮ䷆ʘ‣⦸⏹ⰄͶa✰ṁ亡Zᨖ0༂⽔9㗰"; - var password = randomnessProvider.GetString(SymmetricKey.MinimumPasswordLength, true, true, true, true, true, true, false); + using var password = Password.FromUnicodeString(randomnessProvider.GetString(SymmetricKey.MinimumPasswordLength, true, true, true, true, true, true, false)); // Act. using (var targetOne = CascadingSymmetricKey.FromPassword(password, derivationMode, firstLayerAlgorithm, secondLayerAlgorithm, thirdLayerAlgorithm)) @@ -126,7 +127,7 @@ public void FromPassword_ShouldBeRepeatable_UsingTwoAlgorithms() var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Ecb; var derivationMode = SymmetricKeyDerivationMode.XorLayeringWithSubstitution; var plaintextObject = "䆟`ಮ䷆ʘ‣⦸⏹ⰄͶa✰ṁ亡Zᨖ0༂⽔9㗰"; - var password = randomnessProvider.GetString(SymmetricKey.MinimumPasswordLength, true, true, true, true, true, true, false); + using var password = Password.FromUnicodeString(randomnessProvider.GetString(SymmetricKey.MinimumPasswordLength, true, true, true, true, true, true, false)); // Act. using (var targetOne = CascadingSymmetricKey.FromPassword(password, derivationMode, firstLayerAlgorithm, secondLayerAlgorithm)) diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/SymmetricKeyTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/SymmetricKeyTests.cs index b17c2468..af5170b1 100644 --- a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/SymmetricKeyTests.cs +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/SymmetricKeyTests.cs @@ -5,8 +5,8 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using RapidField.SolidInstruments.Collections; -using RapidField.SolidInstruments.Core; using RapidField.SolidInstruments.Cryptography.Extensions; +using RapidField.SolidInstruments.Cryptography.Secrets; using RapidField.SolidInstruments.Cryptography.Symmetric; using System; using System.Linq; @@ -27,7 +27,7 @@ public void FromPassword_ShouldBeRepeatable() var algorithm = SymmetricAlgorithmSpecification.Aes128Cbc; var derivationMode = SymmetricKeyDerivationMode.XorLayering; var plaintextObject = "䆟`ಮ䷆ʘ‣⦸⏹ⰄͶa✰ṁ亡Zᨖ0༂⽔9㗰"; - var password = randomnessProvider.GetString(SymmetricKey.MinimumPasswordLength, true, true, true, true, true, true, false); + using var password = Password.FromUnicodeString(randomnessProvider.GetString(SymmetricKey.MinimumPasswordLength, true, true, true, true, true, true, false)); // Act. using (var targetOne = SymmetricKey.FromPassword(password, algorithm, derivationMode)) @@ -49,35 +49,13 @@ public void FromPassword_ShouldBeRepeatable() } } - [TestMethod] - public void FromPassword_ShouldRaiseArgumentEmptyException_ForEmptyPassword() - { - using (var randomnessProvider = RandomNumberGenerator.Create()) - { - // Arrange. - var password = String.Empty; - - // Act. - var action = new Action(() => - { - using (var target = SymmetricKey.FromPassword(password)) - { - return; - } - }); - - // Assert. - action.Should().Throw(); - } - } - [TestMethod] public void FromPassword_ShouldRaiseArgumentException_ForInvalidPasswordLength() { using (var randomnessProvider = RandomNumberGenerator.Create()) { // Arrange. - var password = randomnessProvider.GetString((SymmetricKey.MinimumPasswordLength - 1), true, true, true, true, true, true, false); + using var password = Password.FromUnicodeString(randomnessProvider.GetString(12, true, true, true, true, true, true, false)); // Act. var action = new Action(() => @@ -99,7 +77,7 @@ public void FromPassword_ShouldRaiseArgumentNullException_ForNullPassword() using (var randomnessProvider = RandomNumberGenerator.Create()) { // Arrange. - var password = (String)null; + var password = (Password)null; // Act. var action = new Action(() => @@ -120,7 +98,7 @@ public void FromPassword_ShouldReturnValidResult_ForAes128Cbc() { // Arrange. var algorithm = SymmetricAlgorithmSpecification.Aes128Cbc; - var password = "zmDsQ5b58pE7p"; + using var password = Password.FromAsciiString("zmDsQ5b58pE7p"); // Act. using (var target = SymmetricKey.FromPassword(password, algorithm)) @@ -135,7 +113,7 @@ public void FromPassword_ShouldReturnValidResult_ForAes128Ecb() { // Arrange. var algorithm = SymmetricAlgorithmSpecification.Aes128Ecb; - var password = "zmDsQ5b58pE7p"; + using var password = Password.FromAsciiString("zmDsQ5b58pE7p"); // Act. using (var target = SymmetricKey.FromPassword(password, algorithm)) @@ -150,7 +128,7 @@ public void FromPassword_ShouldReturnValidResult_ForAes256Cbc() { // Arrange. var algorithm = SymmetricAlgorithmSpecification.Aes256Cbc; - var password = "zmDsQ5b58pE7p"; + using var password = Password.FromAsciiString("zmDsQ5b58pE7p"); // Act. using (var target = SymmetricKey.FromPassword(password, algorithm)) @@ -165,7 +143,7 @@ public void FromPassword_ShouldReturnValidResult_ForAes256Ecb() { // Arrange. var algorithm = SymmetricAlgorithmSpecification.Aes256Ecb; - var password = "zmDsQ5b58pE7p"; + using var password = Password.FromAsciiString("zmDsQ5b58pE7p"); // Act. using (var target = SymmetricKey.FromPassword(password, algorithm)) @@ -250,6 +228,18 @@ public void New_ShouldReturnValidKey_ForValidArguments() } } + [TestMethod] + public void SubstitutionBoxBytes_ShouldBeValid() + { + // Arrange. + var substitutionBoxBytes = SymmetricKey.SubstitutionBoxBytes; + + // Assert. + substitutionBoxBytes.Should().NotBeNullOrEmpty(); + substitutionBoxBytes.Count().Should().Be(256); + substitutionBoxBytes.Should().OnlyHaveUniqueItems(); + } + [TestMethod] public void ToDerivedKeyBytes_ShouldReturnValidResult_ForAes128Cbc() { From 62ee632feaeea7db04d8900c4a0d61035b49dfeb Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Thu, 11 Jun 2020 09:36:38 -0500 Subject: [PATCH 34/55] Fixing a test defect. --- .../ReadOnlyPinnedMemory.cs | 3 +- .../ISoftwareSecurityModule.cs | 15 + .../Secrets/Secret.cs | 44 ++- .../Secrets/SecretVault.cs | 357 ++++++++++++------ .../SecureMemory.cs | 69 ++-- .../Secrets/SecretVaultTests.cs | 145 ++++++- 6 files changed, 471 insertions(+), 162 deletions(-) create mode 100644 src/RapidField.SolidInstruments.Cryptography/ISoftwareSecurityModule.cs diff --git a/src/RapidField.SolidInstruments.Collections/ReadOnlyPinnedMemory.cs b/src/RapidField.SolidInstruments.Collections/ReadOnlyPinnedMemory.cs index b4d7e361..5bf9f23a 100644 --- a/src/RapidField.SolidInstruments.Collections/ReadOnlyPinnedMemory.cs +++ b/src/RapidField.SolidInstruments.Collections/ReadOnlyPinnedMemory.cs @@ -4,7 +4,6 @@ using RapidField.SolidInstruments.Core; using RapidField.SolidInstruments.Core.ArgumentValidation; -using RapidField.SolidInstruments.Core.Concurrency; using RapidField.SolidInstruments.Core.Extensions; using System; using System.Collections; @@ -99,7 +98,7 @@ public ReadOnlyPinnedMemory(Int32 length) /// is . /// public ReadOnlyPinnedMemory(T[] field) - : base(ConcurrencyControlMode.SingleThreadLock) + : base() { Handle = GCHandle.Alloc(field.RejectIf().IsNull(nameof(field)).TargetArgument, GCHandleType.Pinned); Field = (T[])Handle.Target; diff --git a/src/RapidField.SolidInstruments.Cryptography/ISoftwareSecurityModule.cs b/src/RapidField.SolidInstruments.Cryptography/ISoftwareSecurityModule.cs new file mode 100644 index 00000000..9af9fa8a --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/ISoftwareSecurityModule.cs @@ -0,0 +1,15 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; + +namespace RapidField.SolidInstruments.Cryptography +{ + /// + /// Represents a centralized facility for safeguarding digital secrets and performing cryptographic operations. + /// + public interface ISoftwareSecurityModule : IInstrument + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/Secret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/Secret.cs index 0ff7e166..be1c6ed4 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/Secret.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/Secret.cs @@ -165,9 +165,11 @@ protected Secret(String name) /// public void Read(Action> readAction) { - using var controlToken = StateControl.Enter(); - RejectIfDisposed(); - Read(readAction.RejectIf().IsNull(nameof(readAction)), controlToken); + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + Read(readAction.RejectIf().IsNull(nameof(readAction)), controlToken); + } } /// @@ -192,9 +194,11 @@ public void Read(Action> readAction) /// public void Read(Action readAction) { - using var controlToken = StateControl.Enter(); - RejectIfDisposed(); - Read(readAction.RejectIf().IsNull(nameof(readAction)), controlToken); + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + Read(readAction.RejectIf().IsNull(nameof(readAction)), controlToken); + } } /// @@ -256,16 +260,18 @@ public void Read(Action readAction) [DebuggerHidden] void IReadOnlySecret.RegenerateInMemoryKey() { - using var controlToken = StateControl.Enter(); - RejectIfDisposed(); - - try - { - SecureValueMemory.RegeneratePrivateKey(); - } - finally + using (var controlToken = StateControl.Enter()) { - InMemoryKeyTimeStampValue = TimeStamp.Current; + RejectIfDisposed(); + + try + { + SecureValueMemory.RegeneratePrivateKey(); + } + finally + { + InMemoryKeyTimeStampValue = TimeStamp.Current; + } } } @@ -294,9 +300,11 @@ void IReadOnlySecret.RegenerateInMemoryKey() /// public void Write(Func writeFunction) { - using var controlToken = StateControl.Enter(); - RejectIfDisposed(); - Write(writeFunction.RejectIf().IsNull(nameof(writeFunction)), controlToken); + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + Write(writeFunction.RejectIf().IsNull(nameof(writeFunction)), controlToken); + } } /// diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretVault.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretVault.cs index 774eae1c..04a34189 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretVault.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretVault.cs @@ -31,6 +31,7 @@ public sealed class SecretVault : Instrument, ISecretVault /// Initializes a new instance of the class. /// public SecretVault() + : base() { LazyReferenceManager = new Lazy(() => new ReferenceManager(), LazyThreadSafetyMode.ExecutionAndPublication); Secrets = new Dictionary(); @@ -41,11 +42,12 @@ public SecretVault() /// Initializes a new instance of the class. /// /// - /// A 13+ character password from which a master key is derived, which is used as the default encryption key for exported - /// secrets. A random master key is generated on demand if the parameterless constructor is used. + /// A compliant password from which to derive the master key, which is + /// used as the default encryption key for exported secrets. A master key is generated on demand if the parameterless + /// constructor is used. /// /// - /// contains a string value that is shorter than thirteen characters. + /// does not comply with . /// /// /// is . @@ -56,10 +58,7 @@ public SecretVault() public SecretVault(IPassword masterPassword) : this() { - using var masterKey = CascadingSymmetricKey.FromPassword(masterPassword); - var masterKeySecret = CascadingSymmetricKeySecret.FromValue(MasterKeyName, masterKey); - ReferenceManager.AddObject(masterKeySecret); - Secrets.Add(masterKeySecret.Name, masterKeySecret); + _ = CreateMasterKey(masterPassword); } /// @@ -217,26 +216,28 @@ public SecretVault(IPassword masterPassword) /// public void Clear() { - using var controlToken = StateControl.Enter(); - RejectIfDisposed(); - - try + using (var controlToken = StateControl.Enter()) { - foreach (var secret in Secrets.Values) + RejectIfDisposed(); + + try { - try + foreach (var secret in Secrets.Values) { - secret?.Dispose(); - } - catch - { - continue; + try + { + secret?.Dispose(); + } + catch + { + continue; + } } } - } - finally - { - Secrets.Clear(); + finally + { + Secrets.Clear(); + } } } @@ -290,22 +291,22 @@ public async Task ExportAsync(String keyName) } var exportedSecretVault = new ExportedSecretVault(Identifier, secrets); - using var controlToken = StateControl.Enter(); - RejectIfDisposed(); - if (Secrets.ContainsKey(keyName.RejectIf().IsNullOrEmpty(nameof(keyName)))) - { - return await ExportEncryptedSecretVaultAsync(exportedSecretVault, Secrets[keyName]).ConfigureAwait(false); - } - else if (keyName == MasterKeyName) + using (var controlToken = StateControl.Enter()) { - using var masterKey = CascadingSymmetricKey.New(); - var masterKeySecret = CascadingSymmetricKeySecret.FromValue(keyName, masterKey); - AddOrUpdate(MasterKeyName, masterKeySecret, controlToken); - return await ExportEncryptedSecretVaultAsync(exportedSecretVault, masterKeySecret).ConfigureAwait(false); - } + RejectIfDisposed(); + + if (Secrets.ContainsKey(keyName.RejectIf().IsNullOrEmpty(nameof(keyName)))) + { + return await ExportEncryptedSecretVaultAsync(exportedSecretVault, Secrets[keyName]).ConfigureAwait(false); + } + else if (keyName == MasterKeyName) + { + return await ExportEncryptedSecretVaultAsync(exportedSecretVault, CreateMasterKey()).ConfigureAwait(false); + } - throw new ArgumentException($"The secret vault does not contain a key with the specified name, \"{keyName}\".", nameof(keyName)); + throw new ArgumentException($"The secret vault does not contain a key with the specified name, \"{keyName}\".", nameof(keyName)); + } } /// @@ -372,22 +373,21 @@ public async Task ExportEncryptedSecretAsync(String nam try { - using var controlToken = StateControl.Enter(); - RejectIfDisposed(); - - if (Secrets.ContainsKey(keyName.RejectIf().IsNullOrEmpty(nameof(keyName)))) + using (var controlToken = StateControl.Enter()) { - return await ExportEncryptedSecretAsync(exportedSecret, Secrets[keyName]).ConfigureAwait(false); - } - else if (keyName == MasterKeyName) - { - using var masterKey = CascadingSymmetricKey.New(); - var masterKeySecret = CascadingSymmetricKeySecret.FromValue(keyName, masterKey); - AddOrUpdate(MasterKeyName, masterKeySecret, controlToken); - return await ExportEncryptedSecretAsync(exportedSecret, masterKeySecret).ConfigureAwait(false); - } + RejectIfDisposed(); + + if (Secrets.ContainsKey(keyName.RejectIf().IsNullOrEmpty(nameof(keyName)))) + { + return await ExportEncryptedSecretAsync(exportedSecret, Secrets[keyName]).ConfigureAwait(false); + } + else if (keyName == MasterKeyName) + { + return await ExportEncryptedSecretAsync(exportedSecret, CreateMasterKey()).ConfigureAwait(false); + } - throw new ArgumentException($"The secret vault does not contain a key with the specified name, \"{keyName}\".", nameof(keyName)); + throw new ArgumentException($"The secret vault does not contain a key with the specified name, \"{keyName}\".", nameof(keyName)); + } } finally { @@ -416,13 +416,14 @@ public Task ExportMasterKeyAsync() return ExportSecretAsync(MasterKeyName); } - using var controlToken = StateControl.Enter(); - RejectIfDisposed(); - - using var masterKey = CascadingSymmetricKey.New(); - var masterKeySecret = CascadingSymmetricKeySecret.FromValue(MasterKeyName, masterKey); - AddOrUpdate(MasterKeyName, masterKeySecret, controlToken); - return Task.FromResult(new ExportedSecret(masterKeySecret)); + return Task.Factory.StartNew(() => + { + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + return new ExportedSecret(CreateMasterKey()); + } + }); } /// @@ -499,45 +500,53 @@ public Task ExportSecretAsync(String name) => Task.Factory.Start /// public void ImportEncryptedSecret(IEncryptedExportedSecret secret, String keyName) { - using var controlToken = StateControl.Enter(); - RejectIfDisposed(); + _ = secret.RejectIf().IsNull(nameof(secret)); - if (Secrets.ContainsKey(keyName.RejectIf().IsNullOrEmpty(nameof(keyName)))) + using (var controlToken = StateControl.Enter()) { - var keySecret = Secrets[keyName]; + RejectIfDisposed(); - try + if (Secrets.ContainsKey(keyName.RejectIf().IsNullOrEmpty(nameof(keyName)))) { - if (keySecret.ValueType == typeof(SymmetricKey)) + var exportedSecret = (IExportedSecret)null; + var keySecret = Secrets[keyName]; + + try { - ((SymmetricKeySecret)keySecret).ReadAsync((SymmetricKey key) => + if (keySecret.ValueType == typeof(SymmetricKey)) { - ImportSecret(secret.ToPlaintextModel(key), controlToken); - }).Wait(); - return; + ((SymmetricKeySecret)keySecret).ReadAsync((SymmetricKey key) => + { + exportedSecret = secret.ToPlaintextModel(key); + }).Wait(); + } + else if (keySecret.ValueType == typeof(CascadingSymmetricKey)) + { + ((CascadingSymmetricKeySecret)keySecret).ReadAsync((CascadingSymmetricKey key) => + { + exportedSecret = secret.ToPlaintextModel(key); + }).Wait(); + } + else + { + throw new ArgumentException($"The specified key name, \"{keyName}\" does not reference a valid key.", nameof(keySecret.Name)); + } } - else if (keySecret.ValueType == typeof(CascadingSymmetricKey)) + catch (AggregateException exception) { - ((CascadingSymmetricKeySecret)keySecret).ReadAsync((CascadingSymmetricKey key) => - { - ImportSecret(secret.ToPlaintextModel(key), controlToken); - }).Wait(); - return; + throw new SecretAccessException($"The specified key, \"{keyName}\", could not be used to import the specified secret. Decryption or deserialization failed.", exception); } - throw new ArgumentException($"The specified key name, \"{keyName}\" does not reference a valid key.", nameof(keySecret.Name)); + ImportSecret(exportedSecret, controlToken); + return; } - catch (AggregateException exception) + else if (keyName == MasterKeyName) { - throw new SecretAccessException($"The specified key, \"{keyName}\", could not be used to import the specified secret. Decryption or deserialization failed.", exception); + throw new SecretAccessException("The encrypted secret cannot be imported without specifying an explicit key because the secret vault does not have a master key."); } - } - else if (keyName == MasterKeyName) - { - throw new SecretAccessException("The encrypted secret cannot be imported without specifying an explicit key because the secret vault does not have a master key."); - } - throw new ArgumentException($"The secret vault does not contain a key with the specified name, \"{keyName}\".", nameof(keyName)); + throw new ArgumentException($"The secret vault does not contain a key with the specified name, \"{keyName}\".", nameof(keyName)); + } } /// @@ -588,6 +597,59 @@ public void ImportEncryptedSecret(IEncryptedExportedSecret secret, String keyNam /// public void ImportEncryptedSecretVault(IEncryptedExportedSecretVault secretVault, String keyName) { + _ = secretVault.RejectIf().IsNull(nameof(secretVault)); + + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + + if (Secrets.ContainsKey(keyName.RejectIf().IsNullOrEmpty(nameof(keyName)))) + { + var keySecret = Secrets[keyName]; + var exportedVault = (IExportedSecretVault)null; + + try + { + if (keySecret.ValueType == typeof(SymmetricKey)) + { + ((SymmetricKeySecret)keySecret).ReadAsync((SymmetricKey key) => + { + exportedVault = secretVault.ToPlaintextModel(key); + }).Wait(); + } + else if (keySecret.ValueType == typeof(CascadingSymmetricKey)) + { + ((CascadingSymmetricKeySecret)keySecret).ReadAsync((CascadingSymmetricKey key) => + { + exportedVault = secretVault.ToPlaintextModel(key); + }).Wait(); + } + else + { + throw new ArgumentException($"The specified key name, \"{keyName}\" does not reference a valid key.", nameof(keySecret.Name)); + } + } + catch (AggregateException exception) + { + throw new SecretAccessException($"The specified key, \"{keyName}\", could not be used to import the specified secret vault. Decryption or deserialization failed.", exception); + } + + var exportedSecrets = exportedVault.GetExportedSecrets(); + + foreach (var exportedSecret in exportedSecrets) + { + ImportSecret(exportedSecret, controlToken); + } + + return; + } + else if (keyName == MasterKeyName) + { + throw new SecretAccessException("The encrypted secret cannot be imported without specifying an explicit key because the secret vault does not have a master key."); + } + + throw new ArgumentException($"The secret vault does not contain a key with the specified name, \"{keyName}\".", nameof(keyName)); + } } /// @@ -604,9 +666,11 @@ public void ImportEncryptedSecretVault(IEncryptedExportedSecretVault secretVault /// public void ImportSecret(IExportedSecret secret) { - using var controlToken = StateControl.Enter(); - RejectIfDisposed(); - ImportSecret(secret, controlToken); + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + ImportSecret(secret, controlToken); + } } /// @@ -854,19 +918,21 @@ public void ImportSecret(IExportedSecret secret) /// public Boolean TryRemove(String name) { - using var controlToken = StateControl.Enter(); - RejectIfDisposed(); - - if (Secrets.ContainsKey(name.RejectIf().IsNullOrEmpty(nameof(name)))) + using (var controlToken = StateControl.Enter()) { - if (Secrets.Remove(name, out var secret)) + RejectIfDisposed(); + + if (Secrets.ContainsKey(name.RejectIf().IsNullOrEmpty(nameof(name)))) { - secret?.Dispose(); - return true; + if (Secrets.Remove(name, out var secret)) + { + secret?.Dispose(); + return true; + } } - } - return false; + return false; + } } /// @@ -883,7 +949,6 @@ protected override void Dispose(Boolean disposing) { try { - using var controlToken = StateControl.Enter(); LazyReferenceManager.Dispose(); } finally @@ -920,9 +985,11 @@ protected override void Dispose(Boolean disposing) [DebuggerHidden] private void AddOrUpdate(String name, IReadOnlySecret secret) { - using var controlToken = StateControl.Enter(); - RejectIfDisposed(); - AddOrUpdate(name, secret, controlToken); + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + AddOrUpdate(name, secret, controlToken); + } } /// @@ -959,6 +1026,57 @@ private void AddOrUpdate(String name, IReadOnlySecret secret, IConcurrencyContro Secrets.Add(name, secret.RejectIf().IsNull(nameof(secret)).TargetArgument); } + /// + /// Creates and stores a new master key for the current . + /// + /// + /// The resulting master key secret. + /// + /// + /// The object is disposed. + /// + [DebuggerHidden] + private IReadOnlySecret CreateMasterKey() + { + using var masterPassword = Password.NewRandomStrongPassword(); + return CreateMasterKey(masterPassword); + } + + /// + /// Creates and stores a new master key for the current . + /// + /// + /// A compliant password from which to derive the master key. + /// + /// + /// The resulting master key secret. + /// + /// + /// does not comply with . + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + [DebuggerHidden] + private IReadOnlySecret CreateMasterKey(IPassword masterPassword) + { + RejectIfDisposed(); + + if (masterPassword.MeetsRequirements(MasterKeyPasswordCompositionRequirements)) + { + using var masterKey = CascadingSymmetricKey.FromPassword(masterPassword); + var masterKeySecret = CascadingSymmetricKeySecret.FromValue(MasterKeyName, masterKey); + ReferenceManager.AddObject(masterKeySecret); + Secrets.Add(masterKeySecret.Name, masterKeySecret); + return masterKeySecret; + } + + throw new ArgumentException("The specified password does not comply with the composition requirements for secret vault master keys.", nameof(masterPassword)); + } + /// /// Asynchronously exports the specified secret and encrypts it using the specified key. /// @@ -1061,15 +1179,17 @@ private async Task ExportEncryptedSecretVaultAsync [DebuggerHidden] private ExportedSecret ExportSecret(String name) { - using var controlToken = StateControl.Enter(); - RejectIfDisposed(); - - if (Secrets.ContainsKey(name.RejectIf().IsNullOrEmpty(nameof(name)))) + using (var controlToken = StateControl.Enter()) { - return new ExportedSecret(Secrets[name]); - } + RejectIfDisposed(); - throw new ArgumentException($"The secret vault does not contain a secret with the specified name, \"{name}\".", nameof(name)); + if (Secrets.ContainsKey(name.RejectIf().IsNullOrEmpty(nameof(name)))) + { + return new ExportedSecret(Secrets[name]); + } + + throw new ArgumentException($"The secret vault does not contain a secret with the specified name, \"{name}\".", nameof(name)); + } } /// @@ -1091,15 +1211,18 @@ private ExportedSecret ExportSecret(String name) /// Uses a probabilistic method to randomly regenerate and replace the in-memory keys that are used to secure the secrets /// stored by the current , with a probability that scales toward 1 as the keys age. /// + /// + /// A token that represents and manages contextual thread safety. + /// /// /// The object is disposed. /// [DebuggerHidden] - private void ProbabilisticallyRegenerateInMemoryKeys() + private void ProbabilisticallyRegenerateInMemoryKeys(IConcurrencyControlToken controlToken) { if (Convert.ToDouble(HardenedRandomNumberGenerator.Instance.GetUInt16()) / Convert.ToDouble(UInt16.MaxValue) < InMemoryKeyRegenerationProbability) { - RegenerateInMemoryKeys(); + RegenerateInMemoryKeys(controlToken); } } @@ -1135,7 +1258,6 @@ private void ProbabilisticallyRegenerateInMemoryKeys() [DebuggerHidden] private void Read(String name, Action readAction) { - using var controlToken = StateControl.Enter(); RejectIfDisposed(); try @@ -1157,7 +1279,10 @@ private void Read(String name, Action readAction) } finally { - controlToken.AttachTask(ProbabilisticallyRegenerateInMemoryKeys); + using (var controlToken = StateControl.Enter()) + { + ProbabilisticallyRegenerateInMemoryKeys(controlToken); + } } } @@ -1208,15 +1333,15 @@ private Task ReadAsync(String name, Action readAction) /// Regenerates and replaces the in-memory keys that are used to secure the secrets stored by the current /// . /// + /// + /// A token that represents and manages contextual thread safety. + /// /// /// The object is disposed. /// [DebuggerHidden] - private void RegenerateInMemoryKeys() + private void RegenerateInMemoryKeys(IConcurrencyControlToken controlToken) { - using var controlToken = StateControl.Enter(); - RejectIfDisposed(); - foreach (var secret in Secrets.Values) { controlToken.AttachTask(secret.RegenerateInMemoryKey); @@ -1233,7 +1358,6 @@ public Int32 Count { get { - using var controlToken = StateControl.Enter(); RejectIfDisposed(); return Secrets.Count; } @@ -1254,7 +1378,6 @@ public IEnumerable SecretNames { get { - using var controlToken = StateControl.Enter(); RejectIfDisposed(); foreach (var name in Secrets.Keys) @@ -1302,6 +1425,12 @@ public IEnumerable SecretNames [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal const String MasterKeyNamePrefix = "__MasterKey-"; + /// + /// Represents the composition requirements for password-derived master keys. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal static readonly IPasswordCompositionRequirements MasterKeyPasswordCompositionRequirements = PasswordCompositionRequirements.Strict; + /// /// Gets the length of time, in seconds, for expiration of in-memory keys at which the probability of a key regeneration and /// replacement event becomes 1 (100%). diff --git a/src/RapidField.SolidInstruments.Cryptography/SecureMemory.cs b/src/RapidField.SolidInstruments.Cryptography/SecureMemory.cs index 70989259..7f4750d8 100644 --- a/src/RapidField.SolidInstruments.Cryptography/SecureMemory.cs +++ b/src/RapidField.SolidInstruments.Cryptography/SecureMemory.cs @@ -5,7 +5,6 @@ using RapidField.SolidInstruments.Collections; using RapidField.SolidInstruments.Core; using RapidField.SolidInstruments.Core.ArgumentValidation; -using RapidField.SolidInstruments.Core.Concurrency; using RapidField.SolidInstruments.Core.Extensions; using RapidField.SolidInstruments.Cryptography.Extensions; using RapidField.SolidInstruments.Cryptography.Symmetric; @@ -33,7 +32,7 @@ public class SecureMemory : Instrument, ISecureMemory /// is less than or equal to zero. /// public SecureMemory(Int32 lengthInBytes) - : base(ConcurrencyControlMode.SingleThreadLock) + : base() { Cipher = new Aes128CbcCipher(RandomnessProvider); LengthInBytes = lengthInBytes.RejectIf().IsLessThanOrEqualTo(0, nameof(lengthInBytes)); @@ -93,19 +92,21 @@ public void Access(Action action) { action = action.RejectIf().IsNull(nameof(action)); - using var controlToken = StateControl.Enter(); - RejectIfDisposed(); + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); - using var plaintext = new PinnedMemory(LengthInBytes, true); - DecryptField(plaintext); + using var plaintext = new PinnedMemory(LengthInBytes, true); + DecryptField(plaintext); - try - { - action(plaintext); - } - finally - { - EncryptField(plaintext); + try + { + action(plaintext); + } + finally + { + EncryptField(plaintext); + } } } @@ -401,13 +402,15 @@ internal InflatedField(Int32 length, Int32 multiplier, RandomNumberGenerator ran [DebuggerHidden] internal void Scramble() { - using var controlToken = StateControl.Enter(); - RejectIfDisposed(); - ReferenceKey = RandomnessProvider.GetUInt64(); - - foreach (var field in Fields) + using (var controlToken = StateControl.Enter()) { - RandomnessProvider.GetBytes(field); + RejectIfDisposed(); + ReferenceKey = RandomnessProvider.GetUInt64(); + + foreach (var field in Fields) + { + RandomnessProvider.GetBytes(field); + } } } @@ -440,27 +443,43 @@ protected override void Dispose(Boolean disposing) /// /// Gets the target field. /// + /// + /// The target field. + /// /// /// The object is disposed. /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - internal PinnedMemory TargetField + [DebuggerHidden] + private PinnedMemory GetTargetField() { - [DebuggerHidden] - get + using (var controlToken = StateControl.Enter()) { - using var controlToken = StateControl.Enter(); RejectIfDisposed(); - return Fields[Convert.ToInt32(ReferenceKey / (UInt64.MaxValue / Convert.ToUInt64(Multiplier)))]; + return Fields[TargetFieldIndex]; } } + /// + /// Gets the target field. + /// + /// + /// The object is disposed. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal PinnedMemory TargetField => GetTargetField(); + /// /// Represents the total number of fields allocated by the current . /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] private Int32 Multiplier => Fields.Length; + /// + /// Gets the zero-based index of the target field within . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private Int32 TargetFieldIndex => Convert.ToInt32(ReferenceKey / (UInt64.MaxValue / Convert.ToUInt64(Multiplier))); + /// /// Represents the underlying collection of pinned memory fields. /// diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SecretVaultTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SecretVaultTests.cs index b0cbfc14..81c0da38 100644 --- a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SecretVaultTests.cs +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SecretVaultTests.cs @@ -20,19 +20,158 @@ public class SecretVaultTests [TestMethod] public void ExportAsync_ShouldBeReversible_UsingExplicitCascadingSymmetricKey() { - // TODO Write this test. + // Arrange. + var exportKeyName = "ExportKey"; + var secretOneName = "SecretOne"; + var secretTwoName = "SecretTwo"; + var secretNames = new String[] { exportKeyName, secretOneName, secretTwoName }; + var secretOneValue = "foo"; + var secretTwoValue = 123.456d; + using var exportKey = CascadingSymmetricKey.New(); + using var targetOne = new SecretVault(); + using var targetTwo = new SecretVault(); + targetOne.AddOrUpdate(exportKeyName, exportKey); + targetOne.AddOrUpdate(secretOneName, secretOneValue); + targetOne.AddOrUpdate(secretTwoName, secretTwoValue); + targetTwo.AddOrUpdate(exportKeyName, exportKey); + + // Act. + targetOne.ExportAsync(exportKeyName).ContinueWith(exportTask => + { + targetTwo.ImportEncryptedSecretVault(exportTask.Result, exportKeyName); + }).Wait(); + + // Assert. + targetOne.Count.Should().Be(3); + targetOne.SecretNames.Should().Contain(secretNames); + targetTwo.Count.Should().Be(3); + targetTwo.SecretNames.Should().Contain(secretNames); + targetTwo.ReadAsync(secretOneName, (String value) => { value.Should().Be(secretOneValue); }).Wait(); + targetTwo.ReadAsync(secretTwoName, (Double value) => { value.Should().Be(secretTwoValue); }).Wait(); + + // Arrange. + secretOneValue = "bar"; + secretTwoValue = 654.321d; + targetTwo.AddOrUpdate(secretOneName, secretOneValue); + targetTwo.AddOrUpdate(secretTwoName, secretTwoValue); + + // Act. + targetTwo.ExportAsync(exportKeyName).ContinueWith(exportTask => + { + targetOne.ImportEncryptedSecretVault(exportTask.Result, exportKeyName); + }).Wait(); + + // Assert. + targetTwo.Count.Should().Be(3); + targetTwo.SecretNames.Should().Contain(secretNames); + targetOne.Count.Should().Be(3); + targetOne.SecretNames.Should().Contain(secretNames); + targetOne.ReadAsync(secretOneName, (String value) => { value.Should().Be(secretOneValue); }).Wait(); + targetOne.ReadAsync(secretTwoName, (Double value) => { value.Should().Be(secretTwoValue); }).Wait(); } [TestMethod] public void ExportAsync_ShouldBeReversible_UsingExplicitSymmetricKey() { - // TODO Write this test. + // Arrange. + var exportKeyName = "ExportKey"; + var secretOneName = "SecretOne"; + var secretTwoName = "SecretTwo"; + var secretNames = new String[] { exportKeyName, secretOneName, secretTwoName }; + var secretOneValue = "foo"; + var secretTwoValue = 123.456d; + using var exportKey = SymmetricKey.New(); + using var targetOne = new SecretVault(); + using var targetTwo = new SecretVault(); + targetOne.AddOrUpdate(exportKeyName, exportKey); + targetOne.AddOrUpdate(secretOneName, secretOneValue); + targetOne.AddOrUpdate(secretTwoName, secretTwoValue); + targetTwo.AddOrUpdate(exportKeyName, exportKey); + + // Act. + targetOne.ExportAsync(exportKeyName).ContinueWith(exportTask => + { + targetTwo.ImportEncryptedSecretVault(exportTask.Result, exportKeyName); + }).Wait(); + + // Assert. + targetOne.Count.Should().Be(3); + targetOne.SecretNames.Should().Contain(secretNames); + targetTwo.Count.Should().Be(3); + targetTwo.SecretNames.Should().Contain(secretNames); + targetTwo.ReadAsync(secretOneName, (String value) => { value.Should().Be(secretOneValue); }).Wait(); + targetTwo.ReadAsync(secretTwoName, (Double value) => { value.Should().Be(secretTwoValue); }).Wait(); + + // Arrange. + secretOneValue = "bar"; + secretTwoValue = 654.321d; + targetTwo.AddOrUpdate(secretOneName, secretOneValue); + targetTwo.AddOrUpdate(secretTwoName, secretTwoValue); + + // Act. + targetTwo.ExportAsync(exportKeyName).ContinueWith(exportTask => + { + targetOne.ImportEncryptedSecretVault(exportTask.Result, exportKeyName); + }).Wait(); + + // Assert. + targetTwo.Count.Should().Be(3); + targetTwo.SecretNames.Should().Contain(secretNames); + targetOne.Count.Should().Be(3); + targetOne.SecretNames.Should().Contain(secretNames); + targetOne.ReadAsync(secretOneName, (String value) => { value.Should().Be(secretOneValue); }).Wait(); + targetOne.ReadAsync(secretTwoName, (Double value) => { value.Should().Be(secretTwoValue); }).Wait(); } [TestMethod] public void ExportAsync_ShouldBeReversible_UsingMasterKey() { - // TODO Write this test. + // Arrange. + var secretOneName = "SecretOne"; + var secretTwoName = "SecretTwo"; + var secretNames = new String[] { secretOneName, secretTwoName }; + var secretOneValue = "foo"; + var secretTwoValue = 123.456d; + using var masterPassword = Password.NewRandomStrongPassword(); + using var exportKey = SymmetricKey.New(); + using var targetOne = new SecretVault(masterPassword); + using var targetTwo = new SecretVault(masterPassword); + targetOne.AddOrUpdate(secretOneName, secretOneValue); + targetOne.AddOrUpdate(secretTwoName, secretTwoValue); + + // Act. + targetOne.ExportAsync().ContinueWith(exportTask => + { + targetTwo.ImportEncryptedSecretVault(exportTask.Result); + }).Wait(); + + // Assert. + targetOne.Count.Should().Be(3); + targetOne.SecretNames.Should().Contain(secretNames); + targetTwo.Count.Should().Be(4); + targetTwo.SecretNames.Should().Contain(secretNames); + targetTwo.ReadAsync(secretOneName, (String value) => { value.Should().Be(secretOneValue); }).Wait(); + targetTwo.ReadAsync(secretTwoName, (Double value) => { value.Should().Be(secretTwoValue); }).Wait(); + + // Arrange. + secretOneValue = "bar"; + secretTwoValue = 654.321d; + targetTwo.AddOrUpdate(secretOneName, secretOneValue); + targetTwo.AddOrUpdate(secretTwoName, secretTwoValue); + + // Act. + targetTwo.ExportAsync().ContinueWith(exportTask => + { + targetOne.ImportEncryptedSecretVault(exportTask.Result); + }).Wait(); + + // Assert. + targetTwo.Count.Should().Be(4); + targetTwo.SecretNames.Should().Contain(secretNames); + targetOne.Count.Should().Be(4); + targetOne.SecretNames.Should().Contain(secretNames); + targetOne.ReadAsync(secretOneName, (String value) => { value.Should().Be(secretOneValue); }).Wait(); + targetOne.ReadAsync(secretTwoName, (Double value) => { value.Should().Be(secretTwoValue); }).Wait(); } [TestMethod] From 9c7e6b8e19b954d0f791c684b6b9a07e9f6a03ec Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Thu, 11 Jun 2020 22:23:25 -0500 Subject: [PATCH 35/55] Extending secret vault features. --- .../Secrets/CascadingSymmetricKeySecret.cs | 25 ++ .../Secrets/ISecretManager.cs | 114 ++++++++ .../Secrets/ISecretReader.cs | 159 +---------- .../Secrets/ISecretScalarValueReader.cs | 78 ++++++ .../Secrets/ISecretSymmetricKeyReader.cs | 104 +++++++ .../Secrets/ISecretVaultBasicInformation.cs | 14 +- .../Secrets/ISecretX509CertificateReader.cs | 72 +++++ .../Secrets/Password.cs | 10 +- .../Secrets/Secret.cs | 22 ++ .../Secrets/SecretVault.cs | 253 ++++++++++++++++-- .../Secrets/SymmetricKeySecret.cs | 25 ++ .../Secrets/X509CertificateSecret.cs | 25 ++ .../Secrets/PasswordTests.cs | 16 +- .../Secrets/SecretVaultTests.cs | 225 ++++++++++++---- 14 files changed, 893 insertions(+), 249 deletions(-) create mode 100644 src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretScalarValueReader.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretSymmetricKeyReader.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretX509CertificateReader.cs diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/CascadingSymmetricKeySecret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/CascadingSymmetricKeySecret.cs index ddeb6375..4589a1c8 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/CascadingSymmetricKeySecret.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/CascadingSymmetricKeySecret.cs @@ -8,6 +8,7 @@ using RapidField.SolidInstruments.Core.Concurrency; using RapidField.SolidInstruments.Cryptography.Symmetric; using System; +using System.Diagnostics; using System.Linq; namespace RapidField.SolidInstruments.Cryptography.Secrets @@ -61,6 +62,15 @@ public static CascadingSymmetricKeySecret FromValue(String name, CascadingSymmet return secret; } + /// + /// Generates a new named . + /// + /// + /// A new named . + /// + [DebuggerHidden] + internal static CascadingSymmetricKeySecret New() => FromValue(NewDefaultCascadingSymmetricKeySecretName(), CascadingSymmetricKey.New()); + /// /// Creates a using the provided bytes. /// @@ -125,5 +135,20 @@ protected sealed override IReadOnlyPinnedMemory ConvertValueToBytes(Cascad /// A value indicating whether or not managed resources should be released. /// protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Generates a new, unique default cascading symmetric key name. + /// + /// + /// A new, unique default cascading symmetric key name. + /// + [DebuggerHidden] + private static String NewDefaultCascadingSymmetricKeySecretName() => Secret.GetPrefixedSemanticIdentifier(DefaultCascadingSymmetricKeySecretNamePrefix, NewRandomSemanticIdentifier()); + + /// + /// Represents the default textual prefix for names. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal const String DefaultCascadingSymmetricKeySecretNamePrefix = "CascadingSymmetricKey"; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretManager.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretManager.cs index ce2d6dea..305717dc 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretManager.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretManager.cs @@ -4,6 +4,8 @@ using RapidField.SolidInstruments.Core; using System; +using System.Security; +using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; namespace RapidField.SolidInstruments.Cryptography.Secrets @@ -221,6 +223,118 @@ public interface ISecretManager : ISecretVaultBasicInformation /// public void ImportSecret(IExportedSecret secret); + /// + /// Imports all valid certificates from the current user's personal certificate store. + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised while trying to access or read the specified store or one of the certificates contained therein. + /// + public void ImportStoreCertificates(); + + /// + /// Imports all valid certificates from the specified local certificate store. + /// + /// + /// The name of the store from which the certificates are imported. The default value is . + /// + /// + /// is not a valid store name. + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised while trying to access or read the specified store or one of the certificates contained therein. + /// + public void ImportStoreCertificates(StoreName storeName); + + /// + /// Imports all valid certificates from the specified local certificate store. + /// + /// + /// The name of the store from which the certificates are imported. The default value is . + /// + /// + /// The location of the store from which the certificates are imported. The default value is + /// . + /// + /// + /// is not a valid store name -or- is not a valid store + /// location. + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised while trying to access or read the specified store or one of the certificates contained therein. + /// + public void ImportStoreCertificates(StoreName storeName, StoreLocation storeLocation); + + /// + /// Generates a new and returns its assigned name. + /// + /// + /// The textual name assigned to the new secret. + /// + /// + /// The object is disposed. + /// + public String NewCascadingSymmetricKey(); + + /// + /// Generates a new with the specified name. + /// + /// + /// The textual name to assign to the new secret. + /// + /// + /// is empty. + /// + /// + /// The secret vault already contains a secret with the specified name. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public void NewCascadingSymmetricKey(String name); + + /// + /// Generates a new and returns its assigned name. + /// + /// + /// The textual name assigned to the new secret. + /// + /// + /// The object is disposed. + /// + public String NewSymmetricKey(); + + /// + /// Generates a new with the specified name. + /// + /// + /// The textual name to assign to the new secret. + /// + /// + /// is empty. + /// + /// + /// The secret vault already contains a secret with the specified name. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public void NewSymmetricKey(String name); + /// /// Attempts to remove a secret with the specified name. /// diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretReader.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretReader.cs index 672f5a13..70013fd0 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretReader.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretReader.cs @@ -4,9 +4,7 @@ using RapidField.SolidInstruments.Collections; using RapidField.SolidInstruments.Core; -using RapidField.SolidInstruments.Cryptography.Symmetric; using System; -using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; namespace RapidField.SolidInstruments.Cryptography.Secrets @@ -14,7 +12,7 @@ namespace RapidField.SolidInstruments.Cryptography.Secrets /// /// Represents a read facility for named secret values which are encrypted and pinned in memory at rest. /// - public interface ISecretReader : ISecretVaultBasicInformation + public interface ISecretReader : ISecretScalarValueReader, ISecretSymmetricKeyReader, ISecretVaultBasicInformation, ISecretX509CertificateReader { /// /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read @@ -77,160 +75,5 @@ public interface ISecretReader : ISecretVaultBasicInformation /// specified type. /// public Task ReadAsync(String name, Action readAction); - - /// - /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read - /// operation against it as a thread-safe, atomic operation. - /// - /// - /// A textual name that uniquely identifies the target secret. - /// - /// - /// The read operation to perform. - /// - /// - /// A task representing the asynchronous operation. - /// - /// - /// is empty. - /// - /// - /// The secret vault does not contain a secret with the specified name. - /// - /// - /// is -or- is . - /// - /// - /// The object is disposed. - /// - /// - /// raised an exception -or- the secret vault does not contain a valid secret of the - /// specified type. - /// - public Task ReadAsync(String name, Action readAction); - - /// - /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read - /// operation against it as a thread-safe, atomic operation. - /// - /// - /// A textual name that uniquely identifies the target secret. - /// - /// - /// The read operation to perform. - /// - /// - /// A task representing the asynchronous operation. - /// - /// - /// is empty. - /// - /// - /// The secret vault does not contain a secret with the specified name. - /// - /// - /// is -or- is . - /// - /// - /// The object is disposed. - /// - /// - /// raised an exception -or- the secret vault does not contain a valid secret of the - /// specified type. - /// - public Task ReadAsync(String name, Action readAction); - - /// - /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read - /// operation against it as a thread-safe, atomic operation. - /// - /// - /// A textual name that uniquely identifies the target secret. - /// - /// - /// The read operation to perform. - /// - /// - /// A task representing the asynchronous operation. - /// - /// - /// is empty. - /// - /// - /// The secret vault does not contain a secret with the specified name. - /// - /// - /// is -or- is . - /// - /// - /// The object is disposed. - /// - /// - /// raised an exception -or- the secret vault does not contain a valid secret of the - /// specified type. - /// - public Task ReadAsync(String name, Action readAction); - - /// - /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read - /// operation against it as a thread-safe, atomic operation. - /// - /// - /// A textual name that uniquely identifies the target secret. - /// - /// - /// The read operation to perform. - /// - /// - /// A task representing the asynchronous operation. - /// - /// - /// is empty. - /// - /// - /// The secret vault does not contain a secret with the specified name. - /// - /// - /// is -or- is . - /// - /// - /// The object is disposed. - /// - /// - /// raised an exception -or- the secret vault does not contain a valid secret of the - /// specified type. - /// - public Task ReadAsync(String name, Action readAction); - - /// - /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read - /// operation against it as a thread-safe, atomic operation. - /// - /// - /// A textual name that uniquely identifies the target secret. - /// - /// - /// The read operation to perform. - /// - /// - /// A task representing the asynchronous operation. - /// - /// - /// is empty. - /// - /// - /// The secret vault does not contain a secret with the specified name. - /// - /// - /// is -or- is . - /// - /// - /// The object is disposed. - /// - /// - /// raised an exception -or- the secret vault does not contain a valid secret of the - /// specified type. - /// - public Task ReadAsync(String name, Action readAction); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretScalarValueReader.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretScalarValueReader.cs new file mode 100644 index 00000000..9193014c --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretScalarValueReader.cs @@ -0,0 +1,78 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using System; +using System.Threading.Tasks; + +namespace RapidField.SolidInstruments.Cryptography.Secrets +{ + /// + /// Represents a read facility for named scalar values which are encrypted and pinned in memory at rest. + /// + public interface ISecretScalarValueReader : IAsyncDisposable, IDisposable + { + /// + /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read + /// operation against it as a thread-safe, atomic operation. + /// + /// + /// A textual name that uniquely identifies the target secret. + /// + /// + /// The read operation to perform. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is empty. + /// + /// + /// The secret vault does not contain a secret with the specified name. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// raised an exception -or- the secret vault does not contain a valid secret of the + /// specified type. + /// + public Task ReadAsync(String name, Action readAction); + + /// + /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read + /// operation against it as a thread-safe, atomic operation. + /// + /// + /// A textual name that uniquely identifies the target secret. + /// + /// + /// The read operation to perform. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is empty. + /// + /// + /// The secret vault does not contain a secret with the specified name. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// raised an exception -or- the secret vault does not contain a valid secret of the + /// specified type. + /// + public Task ReadAsync(String name, Action readAction); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretSymmetricKeyReader.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretSymmetricKeyReader.cs new file mode 100644 index 00000000..7b69b149 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretSymmetricKeyReader.cs @@ -0,0 +1,104 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Cryptography.Symmetric; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace RapidField.SolidInstruments.Cryptography.Secrets +{ + /// + /// Represents a read facility for named symmetric keys which are encrypted and pinned in memory at rest. + /// + public interface ISecretSymmetricKeyReader : IAsyncDisposable, IDisposable + { + /// + /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read + /// operation against it as a thread-safe, atomic operation. + /// + /// + /// A textual name that uniquely identifies the target secret. + /// + /// + /// The read operation to perform. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is empty. + /// + /// + /// The secret vault does not contain a secret with the specified name. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// raised an exception -or- the secret vault does not contain a valid secret of the + /// specified type. + /// + public Task ReadAsync(String name, Action readAction); + + /// + /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read + /// operation against it as a thread-safe, atomic operation. + /// + /// + /// A textual name that uniquely identifies the target secret. + /// + /// + /// The read operation to perform. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is empty. + /// + /// + /// The secret vault does not contain a secret with the specified name. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// raised an exception -or- the secret vault does not contain a valid secret of the + /// specified type. + /// + public Task ReadAsync(String name, Action readAction); + + /// + /// Gets the number of and objects that are + /// stored by the . + /// + /// + /// The object is disposed. + /// + public Int32 SymmetricKeySecretCount + { + get; + } + + /// + /// Gets the textual names that uniquely identify the and + /// objects that are stored by the . + /// + /// + /// The object is disposed. + /// + public IEnumerable SymmetricKeySecretNames + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretVaultBasicInformation.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretVaultBasicInformation.cs index aa1a64fb..e7845ad3 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretVaultBasicInformation.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretVaultBasicInformation.cs @@ -14,20 +14,20 @@ namespace RapidField.SolidInstruments.Cryptography.Secrets public interface ISecretVaultBasicInformation : IAsyncDisposable, IDisposable { /// - /// Gets the number of secrets that are stored by the . + /// Gets the unique semantic identifier for the . /// - /// - /// The object is disposed. - /// - public Int32 Count + public String Identifier { get; } /// - /// Gets the unique semantic identifier for the . + /// Gets the number of secrets that are stored by the . /// - public String Identifier + /// + /// The object is disposed. + /// + public Int32 SecretCount { get; } diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretX509CertificateReader.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretX509CertificateReader.cs new file mode 100644 index 00000000..e42ce7c0 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretX509CertificateReader.cs @@ -0,0 +1,72 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using System; +using System.Collections.Generic; +using System.Security.Cryptography.X509Certificates; +using System.Threading.Tasks; + +namespace RapidField.SolidInstruments.Cryptography.Secrets +{ + /// + /// Represents a read facility for named X.509 certificates which are encrypted and pinned in memory at rest. + /// + public interface ISecretX509CertificateReader : IAsyncDisposable, IDisposable + { + /// + /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read + /// operation against it as a thread-safe, atomic operation. + /// + /// + /// A textual name that uniquely identifies the target secret. + /// + /// + /// The read operation to perform. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is empty. + /// + /// + /// The secret vault does not contain a secret with the specified name. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// raised an exception -or- the secret vault does not contain a valid secret of the + /// specified type. + /// + public Task ReadAsync(String name, Action readAction); + + /// + /// Gets the number of objects that are stored by the . + /// + /// + /// The object is disposed. + /// + public Int32 X509CertificateSecretCount + { + get; + } + + /// + /// Gets the textual names that uniquely identify the objects that are stored by the + /// . + /// + /// + /// The object is disposed. + /// + public IEnumerable X509CertificateSecretNames + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/Password.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/Password.cs index 44c7891a..8b753ab2 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/Password.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/Password.cs @@ -176,12 +176,12 @@ private Password(String name, Encoding encoding) public static Password FromUtf8String(String password, String name) => FromString(password, name, Encoding.UTF8); /// - /// Generates a random, strong password that complies with . + /// Generates a strong password that complies with . /// /// - /// A new random, strong password. + /// A new strong password. /// - public static Password NewRandomStrongPassword() + public static Password NewStrongPassword() { while (true) { @@ -450,13 +450,13 @@ private static Password FromString(String password, String name, Encoding encodi /// A new, unique default password name. /// [DebuggerHidden] - private static String NewDefaultPasswordName() => $"{DefaultPasswordNamePrefix}{NewRandomSemanticIdentifier()}"; + private static String NewDefaultPasswordName() => Secret.GetPrefixedSemanticIdentifier(DefaultPasswordNamePrefix, NewRandomSemanticIdentifier()); /// /// Represents the default textual prefix for names. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - internal const String DefaultPasswordNamePrefix = "__Password-"; + internal const String DefaultPasswordNamePrefix = "Password"; /// /// Represents the hashing algorithm that is used to produce cryptographically secure digests for diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/Secret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/Secret.cs index be1c6ed4..99062eb0 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/Secret.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/Secret.cs @@ -65,6 +65,28 @@ public static Secret FromValue(String name, Byte[] value) return secret; } + /// + /// Returns a standard-format, prefixed, unique textual identifier. + /// + /// + /// An alphanumeric prefix for the textual identifier. + /// + /// + /// The unique portion of the textual identifier. + /// + /// + /// A standard-format, prefixed, unique textual identifier. + /// + /// + /// is empty -or- is empty. + /// + /// + /// is -or- is + /// . + /// + [DebuggerHidden] + internal static String GetPrefixedSemanticIdentifier(String prefix, String uniqueSemanticIdentity) => $"__{prefix.RejectIf().IsNullOrEmpty(nameof(prefix)).TargetArgument}-{uniqueSemanticIdentity.RejectIf().IsNullOrEmpty(nameof(uniqueSemanticIdentity)).TargetArgument}"; + /// /// Creates a using the provided bytes. /// diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretVault.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretVault.cs index 04a34189..9e053439 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretVault.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretVault.cs @@ -13,6 +13,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Security; +using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; @@ -673,6 +675,180 @@ public void ImportSecret(IExportedSecret secret) } } + /// + /// Imports all valid certificates from the current user's personal certificate store. + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised while trying to access or read the specified store or one of the certificates contained therein. + /// + public void ImportStoreCertificates() => ImportStoreCertificates(StoreName.My); + + /// + /// Imports all valid certificates from the specified local certificate store. + /// + /// + /// The name of the store from which the certificates are imported. The default value is . + /// + /// + /// is not a valid store name. + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised while trying to access or read the specified store or one of the certificates contained therein. + /// + public void ImportStoreCertificates(StoreName storeName) => ImportStoreCertificates(storeName, StoreLocation.CurrentUser); + + /// + /// Imports all valid certificates from the specified local certificate store. + /// + /// + /// The name of the store from which the certificates are imported. The default value is . + /// + /// + /// The location of the store from which the certificates are imported. The default value is + /// . + /// + /// + /// is not a valid store name -or- is not a valid store + /// location. + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised while trying to access or read the specified store or one of the certificates contained therein. + /// + public void ImportStoreCertificates(StoreName storeName, StoreLocation storeLocation) + { + RejectIfDisposed(); + using var certificateStore = new X509Store(storeName, storeLocation); + + try + { + certificateStore.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly); + } + catch (CryptographicException exception) + { + throw new SecurityException("Failed to import store certificates. The specified store is unreadable.", exception); + } + catch (SecurityException exception) + { + throw new SecurityException("Failed to import store certificates. The user does not have permission to access the specified store.", exception); + } + + var certificates = certificateStore.Certificates.Find(X509FindType.FindByTimeValid, DateTime.Now, true); + + foreach (var certificate in certificates) + { + try + { + if (certificate.Verify()) + { + AddOrUpdate(X509CertificateSecret.NewX509CertificateSecretName(certificate.Thumbprint), certificate); + } + } + catch (CryptographicException exception) + { + throw new SecurityException($"Failed to import store certificates. Certificate \"{certificate.Thumbprint}\" is unreadable.", exception); + } + } + } + + /// + /// Generates a new and returns its assigned name. + /// + /// + /// The textual name assigned to the new secret. + /// + /// + /// The object is disposed. + /// + public String NewCascadingSymmetricKey() + { + var secret = CascadingSymmetricKeySecret.New(); + AddOrUpdate(secret.Name, secret); + return secret.Name; + } + + /// + /// Generates a new with the specified name. + /// + /// + /// The textual name to assign to the new secret. + /// + /// + /// is empty. + /// + /// + /// The secret vault already contains a secret with the specified name. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public void NewCascadingSymmetricKey(String name) + { + if (SecretNames.Contains(name.RejectIf().IsNullOrEmpty(name))) + { + throw new ArgumentException($"Failed to generate a new cascading symmetric key. The secret vault already contains a secret with the name \"{name}\".", nameof(name)); + } + + using var value = CascadingSymmetricKey.New(); + AddOrUpdate(name, CascadingSymmetricKeySecret.FromValue(name, value)); + } + + /// + /// Generates a new and returns its assigned name. + /// + /// + /// The textual name assigned to the new secret. + /// + /// + /// The object is disposed. + /// + public String NewSymmetricKey() + { + var secret = SymmetricKeySecret.New(); + AddOrUpdate(secret.Name, secret); + return secret.Name; + } + + /// + /// Generates a new with the specified name. + /// + /// + /// The textual name to assign to the new secret. + /// + /// + /// is empty. + /// + /// + /// The secret vault already contains a secret with the specified name. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public void NewSymmetricKey(String name) + { + if (SecretNames.Contains(name.RejectIf().IsNullOrEmpty(name))) + { + throw new ArgumentException($"Failed to generate a new symmetric key. The secret vault already contains a secret with the name \"{name}\".", nameof(name)); + } + + using var value = SymmetricKey.New(); + AddOrUpdate(name, SymmetricKeySecret.FromValue(name, value)); + } + /// /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read /// operation against it as a thread-safe, atomic operation. @@ -896,7 +1072,7 @@ public void ImportSecret(IExportedSecret secret) /// /// A string representation of the current . /// - public override String ToString() => $"{{ \"{nameof(Count)}\": {Count} }}"; + public override String ToString() => $"{{ \"{nameof(SecretCount)}\": {SecretCount} }}"; /// /// Attempts to remove a secret with the specified name. @@ -1038,7 +1214,7 @@ private void AddOrUpdate(String name, IReadOnlySecret secret, IConcurrencyContro [DebuggerHidden] private IReadOnlySecret CreateMasterKey() { - using var masterPassword = Password.NewRandomStrongPassword(); + using var masterPassword = Password.NewStrongPassword(); return CreateMasterKey(masterPassword); } @@ -1348,13 +1524,18 @@ private void RegenerateInMemoryKeys(IConcurrencyControlToken controlToken) } } + /// + /// Gets the unique semantic identifier for the current . + /// + public String Identifier => Secret.GetPrefixedSemanticIdentifier(SecretVaultIdentifierPrefix, SemanticIdentity); + /// /// Gets the number of secrets that are stored by the current . /// /// /// The object is disposed. /// - public Int32 Count + public Int32 SecretCount { get { @@ -1364,26 +1545,66 @@ public Int32 Count } /// - /// Gets the unique semantic identifier for the current . + /// Gets the textual names that uniquely identify the secrets that are stored by the current . /// - public String Identifier => $"{IdentifierPrefix}{SemanticIdentity}"; + /// + /// The object is disposed. + /// + public IEnumerable SecretNames + { + get + { + RejectIfDisposed(); + return Secrets.Keys.ToArray(); + } + } /// - /// Gets the textual names that uniquely identify the secrets that are stored by the current . + /// Gets the number of and objects that are + /// stored by the . /// /// /// The object is disposed. /// - public IEnumerable SecretNames + public Int32 SymmetricKeySecretCount => SymmetricKeySecretNames.Count(); + + /// + /// Gets the textual names that uniquely identify the and + /// objects that are stored by the . + /// + /// + /// The object is disposed. + /// + public IEnumerable SymmetricKeySecretNames { get { RejectIfDisposed(); + return Secrets.Where(secret => secret.Value.ValueType == typeof(SymmetricKey) || secret.Value.ValueType == typeof(CascadingSymmetricKey)).Select(secret => secret.Key).ToArray(); + } + } - foreach (var name in Secrets.Keys) - { - yield return name; - } + /// + /// Gets the number of objects that are stored by the . + /// + /// + /// The object is disposed. + /// + public Int32 X509CertificateSecretCount => X509CertificateSecretNames.Count(); + + /// + /// Gets the textual names that uniquely identify the objects that are stored by the + /// . + /// + /// + /// The object is disposed. + /// + public IEnumerable X509CertificateSecretNames + { + get + { + RejectIfDisposed(); + return Secrets.Where(secret => secret.Value.ValueType == typeof(X509Certificate2)).Select(secret => secret.Key).ToArray(); } } @@ -1391,7 +1612,7 @@ public IEnumerable SecretNames /// Gets the name of the master key stored within the current instance. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - internal String MasterKeyName => $"{MasterKeyNamePrefix}{SemanticIdentity}"; + internal String MasterKeyName => Secret.GetPrefixedSemanticIdentifier(MasterKeyNamePrefix, SemanticIdentity); /// /// Gets the length of time since the oldest in-memory key in the current was generated, or @@ -1414,16 +1635,16 @@ public IEnumerable SecretNames private IReferenceManager ReferenceManager => LazyReferenceManager.Value; /// - /// Represents the textual prefix for the semantic identifier of every instance. + /// Represents the textual prefix for the name of the master key stored within every instance. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - internal const String IdentifierPrefix = "__SecretVault-"; + internal const String MasterKeyNamePrefix = "MasterKey"; /// - /// Represents the textual prefix for the name of the master key stored within every instance. + /// Represents the textual prefix for the semantic identifier of every instance. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - internal const String MasterKeyNamePrefix = "__MasterKey-"; + internal const String SecretVaultIdentifierPrefix = "SecretVault"; /// /// Represents the composition requirements for password-derived master keys. diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/SymmetricKeySecret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/SymmetricKeySecret.cs index 96f08d46..db711541 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/SymmetricKeySecret.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/SymmetricKeySecret.cs @@ -8,6 +8,7 @@ using RapidField.SolidInstruments.Core.Concurrency; using RapidField.SolidInstruments.Cryptography.Symmetric; using System; +using System.Diagnostics; using System.Linq; namespace RapidField.SolidInstruments.Cryptography.Secrets @@ -61,6 +62,15 @@ public static SymmetricKeySecret FromValue(String name, SymmetricKey value) return secret; } + /// + /// Generates a new named . + /// + /// + /// A new named . + /// + [DebuggerHidden] + internal static SymmetricKeySecret New() => FromValue(NewDefaultSymmetricKeySecretName(), SymmetricKey.New()); + /// /// Creates a using the provided bytes. /// @@ -125,5 +135,20 @@ protected sealed override IReadOnlyPinnedMemory ConvertValueToBytes(Symmet /// A value indicating whether or not managed resources should be released. /// protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Generates a new, unique default symmetric key name. + /// + /// + /// A new, unique default symmetric key name. + /// + [DebuggerHidden] + private static String NewDefaultSymmetricKeySecretName() => Secret.GetPrefixedSemanticIdentifier(DefaultSymmetricKeySecretNamePrefix, NewRandomSemanticIdentifier()); + + /// + /// Represents the default textual prefix for names. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal const String DefaultSymmetricKeySecretNamePrefix = "SymmetricKey"; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/X509CertificateSecret.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/X509CertificateSecret.cs index b3338c79..3b8bc937 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/X509CertificateSecret.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/X509CertificateSecret.cs @@ -7,6 +7,7 @@ using RapidField.SolidInstruments.Core.ArgumentValidation; using RapidField.SolidInstruments.Core.Concurrency; using System; +using System.Diagnostics; using System.Linq; using System.Security.Cryptography.X509Certificates; @@ -61,6 +62,24 @@ public static X509CertificateSecret FromValue(String name, X509Certificate2 valu return secret; } + /// + /// Generates a new, unique X.509 certificate name. + /// + /// + /// The certificate thumbprint. + /// + /// + /// A new, unique X.509 certificate name. + /// + /// + /// is empty. + /// + /// + /// is . + /// + [DebuggerHidden] + internal static String NewX509CertificateSecretName(String thumbprint) => Secret.GetPrefixedSemanticIdentifier(X509CertificateSecretNamePrefix, thumbprint); + /// /// Creates a using the provided bytes. /// @@ -97,5 +116,11 @@ public static X509CertificateSecret FromValue(String name, X509Certificate2 valu /// A value indicating whether or not managed resources should be released. /// protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Represents the default textual prefix for names. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal const String X509CertificateSecretNamePrefix = "X509Certificate"; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/PasswordTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/PasswordTests.cs index 89f48994..2ab54423 100644 --- a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/PasswordTests.cs +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/PasswordTests.cs @@ -30,9 +30,9 @@ public void EvaluateSecureHashString_ShouldProduceDesiredResults() Password.FromAsciiString("foo"), Password.FromAsciiString("bar"), Password.FromAsciiString("baz"), - Password.NewRandomStrongPassword(), - Password.NewRandomStrongPassword(), - Password.NewRandomStrongPassword() + Password.NewStrongPassword(), + Password.NewStrongPassword(), + Password.NewStrongPassword() }; var passwordHashStringsOne = new String[] { @@ -116,9 +116,9 @@ public void EvaluateSecureHashValue_ShouldProduceDesiredResults() Password.FromAsciiString("foo"), Password.FromAsciiString("bar"), Password.FromAsciiString("baz"), - Password.NewRandomStrongPassword(), - Password.NewRandomStrongPassword(), - Password.NewRandomStrongPassword() + Password.NewStrongPassword(), + Password.NewStrongPassword(), + Password.NewStrongPassword() }; var passwordHashValuesOne = new IReadOnlyPinnedMemory[] { @@ -285,7 +285,7 @@ public void MeetsRequirements_ShouldProduceDesiredResults() } [TestMethod] - public void NewRandomStrongPassword_ShouldProduceDesiredResults() + public void NewStrongPassword_ShouldProduceDesiredResults() { // Arrange. var iterationCount = 30; @@ -293,7 +293,7 @@ public void NewRandomStrongPassword_ShouldProduceDesiredResults() for (var i = 0; i < iterationCount; i++) { // Act. - using var password = Password.NewRandomStrongPassword(); + using var password = Password.NewStrongPassword(); // Assert. password.MeetsRequirements(PasswordCompositionRequirements.Strict).Should().BeTrue(); diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SecretVaultTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SecretVaultTests.cs index 81c0da38..ce9b170c 100644 --- a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SecretVaultTests.cs +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SecretVaultTests.cs @@ -42,10 +42,12 @@ public void ExportAsync_ShouldBeReversible_UsingExplicitCascadingSymmetricKey() }).Wait(); // Assert. - targetOne.Count.Should().Be(3); + targetOne.SecretCount.Should().Be(3); targetOne.SecretNames.Should().Contain(secretNames); - targetTwo.Count.Should().Be(3); + targetOne.SymmetricKeySecretNames.Should().Contain(exportKeyName); + targetTwo.SecretCount.Should().Be(3); targetTwo.SecretNames.Should().Contain(secretNames); + targetTwo.SymmetricKeySecretNames.Should().Contain(exportKeyName); targetTwo.ReadAsync(secretOneName, (String value) => { value.Should().Be(secretOneValue); }).Wait(); targetTwo.ReadAsync(secretTwoName, (Double value) => { value.Should().Be(secretTwoValue); }).Wait(); @@ -62,9 +64,9 @@ public void ExportAsync_ShouldBeReversible_UsingExplicitCascadingSymmetricKey() }).Wait(); // Assert. - targetTwo.Count.Should().Be(3); + targetTwo.SecretCount.Should().Be(3); targetTwo.SecretNames.Should().Contain(secretNames); - targetOne.Count.Should().Be(3); + targetOne.SecretCount.Should().Be(3); targetOne.SecretNames.Should().Contain(secretNames); targetOne.ReadAsync(secretOneName, (String value) => { value.Should().Be(secretOneValue); }).Wait(); targetOne.ReadAsync(secretTwoName, (Double value) => { value.Should().Be(secretTwoValue); }).Wait(); @@ -95,10 +97,12 @@ public void ExportAsync_ShouldBeReversible_UsingExplicitSymmetricKey() }).Wait(); // Assert. - targetOne.Count.Should().Be(3); + targetOne.SecretCount.Should().Be(3); targetOne.SecretNames.Should().Contain(secretNames); - targetTwo.Count.Should().Be(3); + targetOne.SymmetricKeySecretNames.Should().Contain(exportKeyName); + targetTwo.SecretCount.Should().Be(3); targetTwo.SecretNames.Should().Contain(secretNames); + targetTwo.SymmetricKeySecretNames.Should().Contain(exportKeyName); targetTwo.ReadAsync(secretOneName, (String value) => { value.Should().Be(secretOneValue); }).Wait(); targetTwo.ReadAsync(secretTwoName, (Double value) => { value.Should().Be(secretTwoValue); }).Wait(); @@ -115,9 +119,9 @@ public void ExportAsync_ShouldBeReversible_UsingExplicitSymmetricKey() }).Wait(); // Assert. - targetTwo.Count.Should().Be(3); + targetTwo.SecretCount.Should().Be(3); targetTwo.SecretNames.Should().Contain(secretNames); - targetOne.Count.Should().Be(3); + targetOne.SecretCount.Should().Be(3); targetOne.SecretNames.Should().Contain(secretNames); targetOne.ReadAsync(secretOneName, (String value) => { value.Should().Be(secretOneValue); }).Wait(); targetOne.ReadAsync(secretTwoName, (Double value) => { value.Should().Be(secretTwoValue); }).Wait(); @@ -132,7 +136,7 @@ public void ExportAsync_ShouldBeReversible_UsingMasterKey() var secretNames = new String[] { secretOneName, secretTwoName }; var secretOneValue = "foo"; var secretTwoValue = 123.456d; - using var masterPassword = Password.NewRandomStrongPassword(); + using var masterPassword = Password.NewStrongPassword(); using var exportKey = SymmetricKey.New(); using var targetOne = new SecretVault(masterPassword); using var targetTwo = new SecretVault(masterPassword); @@ -146,9 +150,11 @@ public void ExportAsync_ShouldBeReversible_UsingMasterKey() }).Wait(); // Assert. - targetOne.Count.Should().Be(3); + targetOne.SymmetricKeySecretCount.Should().Be(1); + targetOne.SecretCount.Should().Be(3); targetOne.SecretNames.Should().Contain(secretNames); - targetTwo.Count.Should().Be(4); + targetTwo.SymmetricKeySecretCount.Should().Be(2); + targetTwo.SecretCount.Should().Be(4); targetTwo.SecretNames.Should().Contain(secretNames); targetTwo.ReadAsync(secretOneName, (String value) => { value.Should().Be(secretOneValue); }).Wait(); targetTwo.ReadAsync(secretTwoName, (Double value) => { value.Should().Be(secretTwoValue); }).Wait(); @@ -166,9 +172,11 @@ public void ExportAsync_ShouldBeReversible_UsingMasterKey() }).Wait(); // Assert. - targetTwo.Count.Should().Be(4); + targetOne.SymmetricKeySecretCount.Should().Be(2); + targetTwo.SecretCount.Should().Be(4); targetTwo.SecretNames.Should().Contain(secretNames); - targetOne.Count.Should().Be(4); + targetTwo.SymmetricKeySecretCount.Should().Be(2); + targetOne.SecretCount.Should().Be(4); targetOne.SecretNames.Should().Contain(secretNames); targetOne.ReadAsync(secretOneName, (String value) => { value.Should().Be(secretOneValue); }).Wait(); targetOne.ReadAsync(secretTwoName, (Double value) => { value.Should().Be(secretTwoValue); }).Wait(); @@ -510,9 +518,13 @@ public void ExportEncryptedSecret_ShouldBeReversible_ForX509CertificateSecrets_A // Act. targetOne.AddOrUpdate(secretName, secret); + targetOne.X509CertificateSecretCount.Should().Be(1); + targetOne.X509CertificateSecretNames.Should().Contain(secretName); using var exportSecretTaskOne = targetOne.ExportEncryptedSecretAsync(secretName, keyName); exportSecretTaskOne.Wait(); targetTwo.ImportEncryptedSecret(exportSecretTaskOne.Result, keyName); + targetTwo.X509CertificateSecretCount.Should().Be(1); + targetTwo.X509CertificateSecretNames.Should().Contain(secretName); using var exportSecretTaskTwo = targetTwo.ExportEncryptedSecretAsync(secretName, keyName); exportSecretTaskTwo.Wait(); targetOne.ImportEncryptedSecret(exportSecretTaskTwo.Result, keyName); @@ -820,14 +832,14 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForCascadingSymme using (var target = new SecretVault()) { // Assert. - target.Count.Should().Be(0); + target.SecretCount.Should().Be(0); target.SecretNames.Should().BeEmpty(); // Act. target.AddOrUpdate(secretOneName, secretOne); // Assert. - target.Count.Should().Be(1); + target.SecretCount.Should().Be(1); target.SecretNames.Should().Contain(secretOneName); target.ReadAsync(secretOneName, (CascadingSymmetricKey value) => { @@ -838,7 +850,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForCascadingSymme target.AddOrUpdate(secretTwoName, secretTwo); // Assert. - target.Count.Should().Be(2); + target.SecretCount.Should().Be(2); target.SecretNames.Should().Contain(secretOneName); target.SecretNames.Should().Contain(secretTwoName); target.ReadAsync(secretTwoName, (CascadingSymmetricKey value) => @@ -850,7 +862,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForCascadingSymme target.AddOrUpdate(secretOneName, secretThree); // Assert. - target.Count.Should().Be(2); + target.SecretCount.Should().Be(2); target.SecretNames.Should().Contain(secretOneName); target.ReadAsync(secretOneName, (CascadingSymmetricKey value) => { @@ -862,7 +874,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForCascadingSymme target.TryRemove(secretOneName).Should().BeTrue(); // Assert. - target.Count.Should().Be(1); + target.SecretCount.Should().Be(1); target.SecretNames.Should().NotContain(secretOneName); target.SecretNames.Should().Contain(secretTwoName); @@ -870,7 +882,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForCascadingSymme target.Clear(); // Assert. - target.Count.Should().Be(0); + target.SecretCount.Should().Be(0); target.SecretNames.Should().BeEmpty(); } } @@ -890,14 +902,14 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForGuidSecrets() using (var target = new SecretVault()) { // Assert. - target.Count.Should().Be(0); + target.SecretCount.Should().Be(0); target.SecretNames.Should().BeEmpty(); // Act. target.AddOrUpdate(secretOneName, secretOne); // Assert. - target.Count.Should().Be(1); + target.SecretCount.Should().Be(1); target.SecretNames.Should().Contain(secretOneName); target.ReadAsync(secretOneName, (Guid value) => { @@ -908,7 +920,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForGuidSecrets() target.AddOrUpdate(secretTwoName, secretTwo); // Assert. - target.Count.Should().Be(2); + target.SecretCount.Should().Be(2); target.SecretNames.Should().Contain(secretOneName); target.SecretNames.Should().Contain(secretTwoName); target.ReadAsync(secretTwoName, (Guid value) => @@ -920,7 +932,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForGuidSecrets() target.AddOrUpdate(secretOneName, secretThree); // Assert. - target.Count.Should().Be(2); + target.SecretCount.Should().Be(2); target.SecretNames.Should().Contain(secretOneName); target.ReadAsync(secretOneName, (Guid value) => { @@ -932,7 +944,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForGuidSecrets() target.TryRemove(secretOneName).Should().BeTrue(); // Assert. - target.Count.Should().Be(1); + target.SecretCount.Should().Be(1); target.SecretNames.Should().NotContain(secretOneName); target.SecretNames.Should().Contain(secretTwoName); @@ -940,7 +952,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForGuidSecrets() target.Clear(); // Assert. - target.Count.Should().Be(0); + target.SecretCount.Should().Be(0); target.SecretNames.Should().BeEmpty(); } } @@ -961,14 +973,14 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForNumericSecrets using (var target = new SecretVault()) { // Assert. - target.Count.Should().Be(0); + target.SecretCount.Should().Be(0); target.SecretNames.Should().BeEmpty(); // Act. target.AddOrUpdate(secretOneName, secretOne); // Assert. - target.Count.Should().Be(1); + target.SecretCount.Should().Be(1); target.SecretNames.Should().Contain(secretOneName); target.ReadAsync(secretOneName, (Double value) => { @@ -979,7 +991,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForNumericSecrets target.AddOrUpdate(secretTwoName, secretTwo); // Assert. - target.Count.Should().Be(2); + target.SecretCount.Should().Be(2); target.SecretNames.Should().Contain(secretOneName); target.SecretNames.Should().Contain(secretTwoName); target.ReadAsync(secretTwoName, (Double value) => @@ -991,7 +1003,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForNumericSecrets target.AddOrUpdate(secretOneName, secretThree); // Assert. - target.Count.Should().Be(2); + target.SecretCount.Should().Be(2); target.SecretNames.Should().Contain(secretOneName); target.ReadAsync(secretOneName, (Double value) => { @@ -1003,7 +1015,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForNumericSecrets target.TryRemove(secretOneName).Should().BeTrue(); // Assert. - target.Count.Should().Be(1); + target.SecretCount.Should().Be(1); target.SecretNames.Should().NotContain(secretOneName); target.SecretNames.Should().Contain(secretTwoName); @@ -1011,7 +1023,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForNumericSecrets target.Clear(); // Assert. - target.Count.Should().Be(0); + target.SecretCount.Should().Be(0); target.SecretNames.Should().BeEmpty(); } } @@ -1035,14 +1047,14 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForStandardSecret using (var target = new SecretVault()) { // Assert. - target.Count.Should().Be(0); + target.SecretCount.Should().Be(0); target.SecretNames.Should().BeEmpty(); // Act. target.AddOrUpdate(secretOneName, secretOne); // Assert. - target.Count.Should().Be(1); + target.SecretCount.Should().Be(1); target.SecretNames.Should().Contain(secretOneName); target.ReadAsync(secretOneName, (IReadOnlyPinnedMemory value) => { @@ -1053,7 +1065,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForStandardSecret target.AddOrUpdate(secretTwoName, secretTwo); // Assert. - target.Count.Should().Be(2); + target.SecretCount.Should().Be(2); target.SecretNames.Should().Contain(secretOneName); target.SecretNames.Should().Contain(secretTwoName); target.ReadAsync(secretTwoName, (IReadOnlyPinnedMemory value) => @@ -1065,7 +1077,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForStandardSecret target.AddOrUpdate(secretOneName, secretThree); // Assert. - target.Count.Should().Be(2); + target.SecretCount.Should().Be(2); target.SecretNames.Should().Contain(secretOneName); target.ReadAsync(secretOneName, (IReadOnlyPinnedMemory value) => { @@ -1077,7 +1089,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForStandardSecret target.TryRemove(secretOneName).Should().BeTrue(); // Assert. - target.Count.Should().Be(1); + target.SecretCount.Should().Be(1); target.SecretNames.Should().NotContain(secretOneName); target.SecretNames.Should().Contain(secretTwoName); @@ -1085,7 +1097,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForStandardSecret target.Clear(); // Assert. - target.Count.Should().Be(0); + target.SecretCount.Should().Be(0); target.SecretNames.Should().BeEmpty(); } } @@ -1106,14 +1118,14 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForStringSecrets( using (var target = new SecretVault()) { // Assert. - target.Count.Should().Be(0); + target.SecretCount.Should().Be(0); target.SecretNames.Should().BeEmpty(); // Act. target.AddOrUpdate(secretOneName, secretOne); // Assert. - target.Count.Should().Be(1); + target.SecretCount.Should().Be(1); target.SecretNames.Should().Contain(secretOneName); target.ReadAsync(secretOneName, (String value) => { @@ -1124,7 +1136,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForStringSecrets( target.AddOrUpdate(secretTwoName, secretTwo); // Assert. - target.Count.Should().Be(2); + target.SecretCount.Should().Be(2); target.SecretNames.Should().Contain(secretOneName); target.SecretNames.Should().Contain(secretTwoName); target.ReadAsync(secretTwoName, (String value) => @@ -1136,7 +1148,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForStringSecrets( target.AddOrUpdate(secretOneName, secretThree); // Assert. - target.Count.Should().Be(2); + target.SecretCount.Should().Be(2); target.SecretNames.Should().Contain(secretOneName); target.ReadAsync(secretOneName, (String value) => { @@ -1148,7 +1160,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForStringSecrets( target.TryRemove(secretOneName).Should().BeTrue(); // Assert. - target.Count.Should().Be(1); + target.SecretCount.Should().Be(1); target.SecretNames.Should().NotContain(secretOneName); target.SecretNames.Should().Contain(secretTwoName); @@ -1156,7 +1168,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForStringSecrets( target.Clear(); // Assert. - target.Count.Should().Be(0); + target.SecretCount.Should().Be(0); target.SecretNames.Should().BeEmpty(); } } @@ -1175,14 +1187,14 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForSymmetricKeySe using (var target = new SecretVault()) { // Assert. - target.Count.Should().Be(0); + target.SecretCount.Should().Be(0); target.SecretNames.Should().BeEmpty(); // Act. target.AddOrUpdate(secretOneName, secretOne); // Assert. - target.Count.Should().Be(1); + target.SecretCount.Should().Be(1); target.SecretNames.Should().Contain(secretOneName); target.ReadAsync(secretOneName, (SymmetricKey value) => { @@ -1193,7 +1205,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForSymmetricKeySe target.AddOrUpdate(secretTwoName, secretTwo); // Assert. - target.Count.Should().Be(2); + target.SecretCount.Should().Be(2); target.SecretNames.Should().Contain(secretOneName); target.SecretNames.Should().Contain(secretTwoName); target.ReadAsync(secretTwoName, (SymmetricKey value) => @@ -1205,7 +1217,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForSymmetricKeySe target.AddOrUpdate(secretOneName, secretThree); // Assert. - target.Count.Should().Be(2); + target.SecretCount.Should().Be(2); target.SecretNames.Should().Contain(secretOneName); target.ReadAsync(secretOneName, (SymmetricKey value) => { @@ -1217,7 +1229,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForSymmetricKeySe target.TryRemove(secretOneName).Should().BeTrue(); // Assert. - target.Count.Should().Be(1); + target.SecretCount.Should().Be(1); target.SecretNames.Should().NotContain(secretOneName); target.SecretNames.Should().Contain(secretTwoName); @@ -1225,7 +1237,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForSymmetricKeySe target.Clear(); // Assert. - target.Count.Should().Be(0); + target.SecretCount.Should().Be(0); target.SecretNames.Should().BeEmpty(); } } @@ -1249,14 +1261,18 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForX509Certificat using (var target = new SecretVault()) { // Assert. - target.Count.Should().Be(0); + target.X509CertificateSecretCount.Should().Be(0); + target.X509CertificateSecretNames.Should().BeEmpty(); + target.SecretCount.Should().Be(0); target.SecretNames.Should().BeEmpty(); // Act. target.AddOrUpdate(secretOneName, secretOne); // Assert. - target.Count.Should().Be(1); + target.X509CertificateSecretCount.Should().Be(1); + target.X509CertificateSecretNames.Should().Contain(secretOneName); + target.SecretCount.Should().Be(1); target.SecretNames.Should().Contain(secretOneName); target.ReadAsync(secretOneName, (X509Certificate2 value) => { @@ -1268,7 +1284,10 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForX509Certificat target.AddOrUpdate(secretTwoName, secretTwo); // Assert. - target.Count.Should().Be(2); + target.X509CertificateSecretCount.Should().Be(2); + target.X509CertificateSecretNames.Should().Contain(secretOneName); + target.X509CertificateSecretNames.Should().Contain(secretTwoName); + target.SecretCount.Should().Be(2); target.SecretNames.Should().Contain(secretOneName); target.SecretNames.Should().Contain(secretTwoName); target.ReadAsync(secretTwoName, (X509Certificate2 value) => @@ -1281,7 +1300,7 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForX509Certificat target.AddOrUpdate(secretOneName, secretThree); // Assert. - target.Count.Should().Be(2); + target.SecretCount.Should().Be(2); target.SecretNames.Should().Contain(secretOneName); target.ReadAsync(secretOneName, (X509Certificate2 value) => { @@ -1294,7 +1313,10 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForX509Certificat target.TryRemove(secretOneName).Should().BeTrue(); // Assert. - target.Count.Should().Be(1); + target.X509CertificateSecretCount.Should().Be(1); + target.X509CertificateSecretNames.Should().NotContain(secretOneName); + target.X509CertificateSecretNames.Should().Contain(secretTwoName); + target.SecretCount.Should().Be(1); target.SecretNames.Should().NotContain(secretOneName); target.SecretNames.Should().Contain(secretTwoName); @@ -1302,9 +1324,102 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults_ForX509Certificat target.Clear(); // Assert. - target.Count.Should().Be(0); + target.X509CertificateSecretCount.Should().Be(0); + target.X509CertificateSecretNames.Should().BeEmpty(); + target.SecretCount.Should().Be(0); target.SecretNames.Should().BeEmpty(); } } + + [TestMethod] + public void ImportStoreCertificates_ShouldProduceDesiredResults() + { + // Arrange. + using var x509Store = new X509Store(StoreName.My, StoreLocation.CurrentUser); + x509Store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly); + var certificates = x509Store.Certificates.Find(X509FindType.FindByTimeValid, DateTime.Now, true); + var validCertificateCount = 0; + + foreach (var certificate in certificates) + { + if (certificate.Verify()) + { + validCertificateCount++; + } + } + + using (var target = new SecretVault()) + { + // Act. + target.ImportStoreCertificates(); + + // Assert. + target.X509CertificateSecretCount.Should().Be(validCertificateCount); + } + } + + [TestMethod] + public void NewCascadingSymmetricKey_ShouldProduceDesiredResults_UsingDefaultName() + { + using (var target = new SecretVault()) + { + // Act. + var keyName = target.NewCascadingSymmetricKey(); + + // Assert. + target.SymmetricKeySecretCount.Should().Be(1); + target.ReadAsync(keyName, (CascadingSymmetricKey key) => { key.Should().NotBeNull(); }).Wait(); + } + } + + [TestMethod] + public void NewCascadingSymmetricKey_ShouldProduceDesiredResults_UsingExplicitName() + { + // Arrange. + var keyName = "foo"; + + using (var target = new SecretVault()) + { + // Act. + target.NewCascadingSymmetricKey(keyName); + + // Assert. + target.SymmetricKeySecretCount.Should().Be(1); + target.SymmetricKeySecretNames.Should().Contain(keyName); + target.ReadAsync(keyName, (CascadingSymmetricKey key) => { key.Should().NotBeNull(); }).Wait(); + } + } + + [TestMethod] + public void NewSymmetricKey_ShouldProduceDesiredResults_UsingDefaultName() + { + using (var target = new SecretVault()) + { + // Act. + var keyName = target.NewSymmetricKey(); + + // Assert. + target.SymmetricKeySecretCount.Should().Be(1); + target.ReadAsync(keyName, (SymmetricKey key) => { key.Should().NotBeNull(); }).Wait(); + } + } + + [TestMethod] + public void NewSymmetricKey_ShouldProduceDesiredResults_UsingExplicitName() + { + // Arrange. + var keyName = "foo"; + + using (var target = new SecretVault()) + { + // Act. + target.NewSymmetricKey(keyName); + + // Assert. + target.SymmetricKeySecretCount.Should().Be(1); + target.SymmetricKeySecretNames.Should().Contain(keyName); + target.ReadAsync(keyName, (SymmetricKey key) => { key.Should().NotBeNull(); }).Wait(); + } + } } } \ No newline at end of file From 63aee55b92fbac80f82b9bfd30b0887285362bbc Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Fri, 19 Jun 2020 16:09:49 -0500 Subject: [PATCH 36/55] Stabilizing SoftwareSecurityModule. --- en-US_User.dic | 2 + .../Extensions/CharacterExtensions.cs | 12 + .../ISymmetricEncryptorDecryptorExtensions.cs | 49 + .../ISecurityAppliance.cs | 266 ++++++ .../ISoftwareSecurityModule.cs | 30 +- .../Secrets/IPersistentSecretStore.cs | 55 ++ .../Secrets/ISecretManager.cs | 51 +- .../Secrets/ISecretReader.cs | 14 +- .../Secrets/ISecretScalarValueReader.cs | 14 +- .../Secrets/ISecretStore.cs | 74 ++ .../Secrets/ISecretStorePersistenceVehicle.cs | 102 +++ .../Secrets/ISecretSymmetricKeyReader.cs | 18 +- .../Secrets/ISecretVaultBasicInformation.cs | 46 - .../Secrets/ISecretWriter.cs | 16 +- .../Secrets/ISecretX509CertificateReader.cs | 12 +- .../Secrets/Password.cs | 2 +- .../Secrets/PersistentSecretStore.cs | 858 ++++++++++++++++++ .../SecretStoreFilePersistenceVehicle.cs | 180 ++++ .../SecretStorePersistenceException.cs | 64 ++ .../Secrets/SecretStorePersistenceVehicle.cs | 424 +++++++++ .../Secrets/SecretVault.cs | 110 ++- .../SoftwareSecurityModule.cs | 816 +++++++++++++++++ .../Secrets/SecretVaultTests.cs | 12 +- .../SoftwareSecurityModuleTests.cs | 220 +++++ 24 files changed, 3287 insertions(+), 160 deletions(-) create mode 100644 src/RapidField.SolidInstruments.Cryptography/ISecurityAppliance.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Secrets/IPersistentSecretStore.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretStore.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretStorePersistenceVehicle.cs delete mode 100644 src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretVaultBasicInformation.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Secrets/PersistentSecretStore.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Secrets/SecretStoreFilePersistenceVehicle.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Secrets/SecretStorePersistenceException.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Secrets/SecretStorePersistenceVehicle.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/SoftwareSecurityModule.cs create mode 100644 test/RapidField.SolidInstruments.Cryptography.UnitTests/SoftwareSecurityModuleTests.cs diff --git a/en-US_User.dic b/en-US_User.dic index ca1db95b..6282114c 100644 --- a/en-US_User.dic +++ b/en-US_User.dic @@ -174,6 +174,7 @@ Nwn openssl orm parameterless +passwordless plaintext postfix powershell @@ -260,6 +261,7 @@ wwwroot xml xxxxx yaml +Yixu yy yyyy yyyyyyyy diff --git a/src/RapidField.SolidInstruments.Core/Extensions/CharacterExtensions.cs b/src/RapidField.SolidInstruments.Core/Extensions/CharacterExtensions.cs index caaf2322..d374116c 100644 --- a/src/RapidField.SolidInstruments.Core/Extensions/CharacterExtensions.cs +++ b/src/RapidField.SolidInstruments.Core/Extensions/CharacterExtensions.cs @@ -72,6 +72,18 @@ public static class CharacterExtensions /// public static Boolean IsNumeric(this Char target) => Char.IsDigit(target); + /// + /// Determines whether or not the current represents a Unicode punctuation character. + /// + /// + /// The current instance of the . + /// + /// + /// if the current represents a Unicode punctuation character, otherwise + /// . + /// + public static Boolean IsPunctuation(this Char target) => Char.IsPunctuation(target); + /// /// Determines whether or not the current represents a Unicode symbolic character. /// diff --git a/src/RapidField.SolidInstruments.Cryptography/Extensions/ISymmetricEncryptorDecryptorExtensions.cs b/src/RapidField.SolidInstruments.Cryptography/Extensions/ISymmetricEncryptorDecryptorExtensions.cs index 3e092334..76ff4025 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Extensions/ISymmetricEncryptorDecryptorExtensions.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Extensions/ISymmetricEncryptorDecryptorExtensions.cs @@ -39,6 +39,32 @@ public static class ISymmetricProcessorExtensions /// public static T DecryptFromBase64String(this ISymmetricProcessor target, String ciphertext, SymmetricKey key) => target.Decrypt(Convert.FromBase64String(ciphertext), key); + /// + /// Decrypts the specified Base64 string ciphertext. + /// + /// + /// The type of the object that is decrypted. + /// + /// + /// The current . + /// + /// + /// The Base64 string ciphertext to decrypt. + /// + /// + /// The key derivation bits and algorithm specification used to transform the ciphertext. + /// + /// + /// The resulting plaintext object. + /// + /// + /// is not a valid Base64 string. + /// + /// + /// An exception was raised during decryption or deserialization. + /// + public static T DecryptFromBase64String(this ISymmetricProcessor target, String ciphertext, CascadingSymmetricKey key) => target.Decrypt(Convert.FromBase64String(ciphertext), key); + /// /// Decrypts the specified Base64 string ciphertext. /// @@ -91,6 +117,29 @@ public static class ISymmetricProcessorExtensions /// public static String EncryptToBase64String(this ISymmetricProcessor target, T plaintextObject, SymmetricKey key) => Convert.ToBase64String(target.Encrypt(plaintextObject, key)); + /// + /// Encrypts the specified plaintext object to a Base64 string. + /// + /// + /// The type of the object that is encrypted. + /// + /// + /// The current . + /// + /// + /// The plaintext object to encrypt. + /// + /// + /// The key derivation bits and algorithm specification used to transform the object. + /// + /// + /// The resulting ciphertext as a Base64 string. + /// + /// + /// An exception was raised during encryption or serialization. + /// + public static String EncryptToBase64String(this ISymmetricProcessor target, T plaintextObject, CascadingSymmetricKey key) => Convert.ToBase64String(target.Encrypt(plaintextObject, key)); + /// /// Encrypts the specified plaintext object to a Base64 string. /// diff --git a/src/RapidField.SolidInstruments.Cryptography/ISecurityAppliance.cs b/src/RapidField.SolidInstruments.Cryptography/ISecurityAppliance.cs new file mode 100644 index 00000000..65152518 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/ISecurityAppliance.cs @@ -0,0 +1,266 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Cryptography.Secrets; +using RapidField.SolidInstruments.Cryptography.Symmetric; +using System; +using System.Security; + +namespace RapidField.SolidInstruments.Cryptography +{ + /// + /// Represents a centralized utility for performing cryptographic operations. + /// + public interface ISecurityAppliance : IAsyncDisposable, IDisposable + { + /// + /// Decrypts the specified model using the master key. + /// + /// + /// The type of the model to decrypt. + /// + /// + /// The model to decrypt. + /// + /// + /// The decrypted model. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during decryption or deserialization. + /// + public TModel Decrypt(IEncryptedModel model) + where TModel : class, IModel; + + /// + /// Decrypts the specified model using the specified key. + /// + /// + /// The type of the model to decrypt. + /// + /// + /// The model to decrypt. + /// + /// + /// The name of the key to use when decrypting the model, or to use the master key. + /// + /// + /// The decrypted model. + /// + /// + /// is empty. + /// + /// + /// The secret store does not contain a key with the specified name. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during decryption or deserialization. + /// + public TModel Decrypt(IEncryptedModel model, String keyName) + where TModel : class, IModel; + + /// + /// Decrypts the specified Base-64 encoded ciphertext string using the master key. + /// + /// + /// The Base-64 encoded ciphertext string to decrypt. + /// + /// + /// The resulting plaintext string. + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during decryption. + /// + public String Decrypt(String ciphertext); + + /// + /// Decrypts the specified Base-64 encoded ciphertext string using the specified key. + /// + /// + /// The Base-64 encoded ciphertext string to decrypt. + /// + /// + /// The name of the key to use when decrypting the model, or to use the master key. + /// + /// + /// The resulting plaintext string. + /// + /// + /// is empty -or- is empty. + /// + /// + /// The secret store does not contain a key with the specified name. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during decryption. + /// + public String Decrypt(String ciphertext, String keyName); + + /// + /// Encrypts the specified model using the master key. + /// + /// + /// The type of the model to encrypt. + /// + /// + /// The model to encrypt. + /// + /// + /// The encrypted model. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during encryption or serialization. + /// + public IEncryptedModel Encrypt(TModel model) + where TModel : class, IModel; + + /// + /// Encrypts the specified model using the specified key. + /// + /// + /// The type of the model to encrypt. + /// + /// + /// The model to encrypt. + /// + /// + /// The name of the key to use when encrypting the model, or to use the master key. + /// + /// + /// The encrypted model. + /// + /// + /// is empty. + /// + /// + /// The secret store does not contain a key with the specified name. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during encryption or serialization. + /// + public IEncryptedModel Encrypt(TModel model, String keyName) + where TModel : class, IModel; + + /// + /// Encrypts the specified plaintext string using the master key. + /// + /// + /// The plaintext string to encrypt. + /// + /// + /// The resulting Base-64 encoded, encrypted string. + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during encryption. + /// + public String Encrypt(String plaintext); + + /// + /// Encrypts the specified plaintext string using the specified key. + /// + /// + /// The plaintext string to encrypt. + /// + /// + /// The name of the key to use when encrypting the plaintext string, or to use the master key. + /// + /// + /// The resulting Base-64 encoded, encrypted string. + /// + /// + /// is empty -or- is empty. + /// + /// + /// The secret store does not contain a key with the specified name. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during encryption. + /// + public String Encrypt(String plaintext, String keyName); + + /// + /// Generates a new and returns its assigned name. + /// + /// + /// The textual name assigned to the new secret. + /// + /// + /// The object is disposed. + /// + public String NewCascadingSymmetricKey(); + + /// + /// Generates a new and returns its assigned name. + /// + /// + /// The textual name assigned to the new secret. + /// + /// + /// The object is disposed. + /// + public String NewSymmetricKey(); + + /// + /// Gets the secret reading facility for the current . + /// + public ISecretReader SecretReader + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/ISoftwareSecurityModule.cs b/src/RapidField.SolidInstruments.Cryptography/ISoftwareSecurityModule.cs index 9af9fa8a..1f25d855 100644 --- a/src/RapidField.SolidInstruments.Cryptography/ISoftwareSecurityModule.cs +++ b/src/RapidField.SolidInstruments.Cryptography/ISoftwareSecurityModule.cs @@ -2,14 +2,40 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= -using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Cryptography.Secrets; namespace RapidField.SolidInstruments.Cryptography { /// /// Represents a centralized facility for safeguarding digital secrets and performing cryptographic operations. /// - public interface ISoftwareSecurityModule : IInstrument + /// + /// The type of the provider that facilitates persistence of the in-memory . + /// + public interface ISoftwareSecurityModule : ISoftwareSecurityModule + where TPersistenceVehicle : class, ISecretStorePersistenceVehicle + { + } + + /// + /// Represents a centralized facility for safeguarding digital secrets and performing cryptographic operations. + /// + /// + /// The type of the in-memory . + /// + /// + /// The type of the provider that facilitates persistence of the in-memory . + /// + public interface ISoftwareSecurityModule : IPersistentSecretStore, ISoftwareSecurityModule + where TInMemorySecretStore : class, ISecretStore + where TPersistenceVehicle : class, ISecretStorePersistenceVehicle + { + } + + /// + /// Represents a centralized facility for safeguarding digital secrets and performing cryptographic operations. + /// + public interface ISoftwareSecurityModule : IPersistentSecretStore, ISecurityAppliance { } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/IPersistentSecretStore.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/IPersistentSecretStore.cs new file mode 100644 index 00000000..6dfb2e26 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/IPersistentSecretStore.cs @@ -0,0 +1,55 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +namespace RapidField.SolidInstruments.Cryptography.Secrets +{ + /// + /// Represents a persistent, secure storage facility for named secret values. + /// + /// + /// The type of the provider that facilitates persistence of the in-memory . + /// + public interface IPersistentSecretStore : IPersistentSecretStore + where TPersistenceVehicle : class, ISecretStorePersistenceVehicle + { + } + + /// + /// Represents a persistent, secure storage facility for named secret values. + /// + /// + /// The type of the in-memory . + /// + /// + /// The type of the provider that facilitates persistence of the in-memory . + /// + public interface IPersistentSecretStore : IPersistentSecretStore + where TInMemorySecretStore : class, ISecretStore + where TPersistenceVehicle : class, ISecretStorePersistenceVehicle + { + /// + /// Gets an that represents the in-memory state of the current + /// . + /// + public TInMemorySecretStore InMemoryStore + { + get; + } + + /// + /// Gets a provider that facilitates persistence of . + /// + public TPersistenceVehicle PersistenceVehicle + { + get; + } + } + + /// + /// Represents a persistent, secure storage facility for named secret values. + /// + public interface IPersistentSecretStore : ISecretStore, ISecretWriter + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretManager.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretManager.cs index 305717dc..5362f475 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretManager.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretManager.cs @@ -13,18 +13,11 @@ namespace RapidField.SolidInstruments.Cryptography.Secrets /// /// Represents a management facility for named secret values which are encrypted and pinned in memory at rest. /// - public interface ISecretManager : ISecretVaultBasicInformation + public interface ISecretManager : ISecretStore { /// - /// Removes and safely disposes of all secrets that are stored by the current . - /// - /// - /// The object is disposed. - /// - public void Clear(); - - /// - /// Asynchronously exports the specified secret and encrypts it using the vault's master key. + /// Asynchronously exports the specified secret and encrypts it using the master key for the current + /// . /// /// /// The textual name of the secret to export. @@ -36,7 +29,7 @@ public interface ISecretManager : ISecretVaultBasicInformation /// is empty. /// /// - /// The secret vault does not contain a secret with the specified name. + /// The does not contain a secret with the specified name. /// /// /// is . @@ -65,8 +58,8 @@ public interface ISecretManager : ISecretVaultBasicInformation /// is empty -or- is empty. /// /// - /// The secret vault does not contain a secret with the specified name -or- the secret vault does not contain a key with the - /// specified name. + /// The does not contain a secret with the specified name -or- the + /// does not contain a key with the specified name. /// /// /// is -or- is . @@ -80,7 +73,7 @@ public interface ISecretManager : ISecretVaultBasicInformation public Task ExportEncryptedSecretAsync(String name, String keyName); /// - /// Asynchronously exports the vault's master key in plaintext form. + /// Asynchronously exports the master key for the in plaintext form. /// /// /// A task representing the asynchronous operation and containing the exported master key. @@ -103,7 +96,7 @@ public interface ISecretManager : ISecretVaultBasicInformation /// is empty. /// /// - /// The secret vault does not contain a secret with the specified name. + /// The does not contain a secret with the specified name. /// /// /// is . @@ -148,7 +141,7 @@ public interface ISecretManager : ISecretVaultBasicInformation /// is empty. /// /// - /// The secret vault does not contain a key with the specified name. + /// The does not contain a key with the specified name. /// /// /// is -or- is . @@ -196,7 +189,7 @@ public interface ISecretManager : ISecretVaultBasicInformation /// is empty. /// /// - /// The secret vault does not contain a key with the specified name. + /// The does not contain a key with the specified name. /// /// /// is -or- is . @@ -294,7 +287,7 @@ public interface ISecretManager : ISecretVaultBasicInformation /// is empty. /// /// - /// The secret vault already contains a secret with the specified name. + /// The already contains a secret with the specified name. /// /// /// is . @@ -325,7 +318,7 @@ public interface ISecretManager : ISecretVaultBasicInformation /// is empty. /// /// - /// The secret vault already contains a secret with the specified name. + /// The already contains a secret with the specified name. /// /// /// is . @@ -334,25 +327,5 @@ public interface ISecretManager : ISecretVaultBasicInformation /// The object is disposed. /// public void NewSymmetricKey(String name); - - /// - /// Attempts to remove a secret with the specified name. - /// - /// - /// A textual name that uniquely identifies the target secret. - /// - /// - /// if the secret was removed, otherwise . - /// - /// - /// is empty. - /// - /// - /// is . - /// - /// - /// The object is disposed. - /// - public Boolean TryRemove(String name); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretReader.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretReader.cs index 70013fd0..bb628624 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretReader.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretReader.cs @@ -12,7 +12,7 @@ namespace RapidField.SolidInstruments.Cryptography.Secrets /// /// Represents a read facility for named secret values which are encrypted and pinned in memory at rest. /// - public interface ISecretReader : ISecretScalarValueReader, ISecretSymmetricKeyReader, ISecretVaultBasicInformation, ISecretX509CertificateReader + public interface ISecretReader : ISecretScalarValueReader, ISecretSymmetricKeyReader, ISecretX509CertificateReader { /// /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read @@ -31,7 +31,7 @@ public interface ISecretReader : ISecretScalarValueReader, ISecretSymmetricKeyRe /// is empty. /// /// - /// The secret vault does not contain a secret with the specified name. + /// The does not contain a secret with the specified name. /// /// /// is -or- is . @@ -40,8 +40,8 @@ public interface ISecretReader : ISecretScalarValueReader, ISecretSymmetricKeyRe /// The object is disposed. /// /// - /// raised an exception -or- the secret vault does not contain a valid secret of the - /// specified type. + /// raised an exception -or- the does not contain a valid secret + /// of the specified type. /// public Task ReadAsync(String name, Action> readAction); @@ -62,7 +62,7 @@ public interface ISecretReader : ISecretScalarValueReader, ISecretSymmetricKeyRe /// is empty. /// /// - /// The secret vault does not contain a secret with the specified name. + /// The does not contain a secret with the specified name. /// /// /// is -or- is . @@ -71,8 +71,8 @@ public interface ISecretReader : ISecretScalarValueReader, ISecretSymmetricKeyRe /// The object is disposed. /// /// - /// raised an exception -or- the secret vault does not contain a valid secret of the - /// specified type. + /// raised an exception -or- the does not contain a valid secret + /// of the specified type. /// public Task ReadAsync(String name, Action readAction); } diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretScalarValueReader.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretScalarValueReader.cs index 9193014c..e3ab51d2 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretScalarValueReader.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretScalarValueReader.cs @@ -11,7 +11,7 @@ namespace RapidField.SolidInstruments.Cryptography.Secrets /// /// Represents a read facility for named scalar values which are encrypted and pinned in memory at rest. /// - public interface ISecretScalarValueReader : IAsyncDisposable, IDisposable + public interface ISecretScalarValueReader : ISecretStore { /// /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read @@ -30,7 +30,7 @@ public interface ISecretScalarValueReader : IAsyncDisposable, IDisposable /// is empty. /// /// - /// The secret vault does not contain a secret with the specified name. + /// The does not contain a secret with the specified name. /// /// /// is -or- is . @@ -39,8 +39,8 @@ public interface ISecretScalarValueReader : IAsyncDisposable, IDisposable /// The object is disposed. /// /// - /// raised an exception -or- the secret vault does not contain a valid secret of the - /// specified type. + /// raised an exception -or- the does not contain a valid secret + /// of the specified type. /// public Task ReadAsync(String name, Action readAction); @@ -61,7 +61,7 @@ public interface ISecretScalarValueReader : IAsyncDisposable, IDisposable /// is empty. /// /// - /// The secret vault does not contain a secret with the specified name. + /// The does not contain a secret with the specified name. /// /// /// is -or- is . @@ -70,8 +70,8 @@ public interface ISecretScalarValueReader : IAsyncDisposable, IDisposable /// The object is disposed. /// /// - /// raised an exception -or- the secret vault does not contain a valid secret of the - /// specified type. + /// raised an exception -or- the does not contain a valid secret + /// of the specified type. /// public Task ReadAsync(String name, Action readAction); } diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretStore.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretStore.cs new file mode 100644 index 00000000..06d9392d --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretStore.cs @@ -0,0 +1,74 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using System; +using System.Collections.Generic; + +namespace RapidField.SolidInstruments.Cryptography.Secrets +{ + /// + /// Represents a secure container for named secret values. + /// + public interface ISecretStore : IAsyncDisposable, IDisposable + { + /// + /// Removes and safely disposes of all secrets that are stored by the current . + /// + /// + /// The object is disposed. + /// + public void Clear(); + + /// + /// Attempts to remove a secret with the specified name. + /// + /// + /// A textual name that uniquely identifies the target secret. + /// + /// + /// if the secret was removed, otherwise . + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public Boolean TryRemove(String name); + + /// + /// Gets the unique semantic identifier for the . + /// + public String Identifier + { + get; + } + + /// + /// Gets the number of secrets that are stored by the . + /// + /// + /// The object is disposed. + /// + public Int32 SecretCount + { + get; + } + + /// + /// Gets the textual names that uniquely identify the secrets that are stored by the . + /// + /// + /// The object is disposed. + /// + public IEnumerable SecretNames + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretStorePersistenceVehicle.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretStorePersistenceVehicle.cs new file mode 100644 index 00000000..ee9a23a2 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretStorePersistenceVehicle.cs @@ -0,0 +1,102 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using System; +using System.Threading; +using System.Threading.Tasks; +using SystemTimeoutException = System.TimeoutException; + +namespace RapidField.SolidInstruments.Cryptography.Secrets +{ + /// + /// Represents a utility that provides state persistence for an in-memory . + /// + public interface ISecretStorePersistenceVehicle : ISecretStorePersistenceVehicle + { + } + + /// + /// Represents a utility that provides state persistence for an in-memory . + /// + /// + /// The type of the in-memory that the persistence vehicle providers state persistence for. + /// + public interface ISecretStorePersistenceVehicle : IInstrument + where TInMemorySecretStore : class, ISecretStore + { + /// + /// Requests the persisted state and hydrates using the result. + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised while retrieving persisted state or hydrating . + /// + /// + /// The specified timeout threshold was exceeded while attempting to load persisted state. + /// + public void LoadPersistedState(); + + /// + /// Requests the persisted state and hydrates using the result. + /// + /// + /// The maximum length of time to wait for the state to be hydrated before raising an exception, or + /// to specify an infinite duration. The default value is one minute. + /// + /// + /// is a negative number other than -or- + /// is greater than . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised while retrieving persisted state or hydrating . + /// + /// + /// The specified timeout threshold was exceeded while attempting to load persisted state. + /// + public void LoadPersistedState(TimeSpan timeoutThreshold); + + /// + /// Asynchronously requests the persisted state and hydrates using the result. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised while retrieving persisted state or hydrating . + /// + public Task LoadPersistedStateAsync(); + + /// + /// Asynchronously persists the state of . + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised while attempting to persist . + /// + public Task PersistInMemoryStoreAsync(); + + /// + /// Gets the in-memory that the current + /// provides state persistence for. + /// + public TInMemorySecretStore InMemoryStore + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretSymmetricKeyReader.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretSymmetricKeyReader.cs index 7b69b149..65695461 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretSymmetricKeyReader.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretSymmetricKeyReader.cs @@ -13,7 +13,7 @@ namespace RapidField.SolidInstruments.Cryptography.Secrets /// /// Represents a read facility for named symmetric keys which are encrypted and pinned in memory at rest. /// - public interface ISecretSymmetricKeyReader : IAsyncDisposable, IDisposable + public interface ISecretSymmetricKeyReader : ISecretStore { /// /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read @@ -32,7 +32,7 @@ public interface ISecretSymmetricKeyReader : IAsyncDisposable, IDisposable /// is empty. /// /// - /// The secret vault does not contain a secret with the specified name. + /// The does not contain a secret with the specified name. /// /// /// is -or- is . @@ -41,8 +41,8 @@ public interface ISecretSymmetricKeyReader : IAsyncDisposable, IDisposable /// The object is disposed. /// /// - /// raised an exception -or- the secret vault does not contain a valid secret of the - /// specified type. + /// raised an exception -or- the does not contain a valid secret + /// of the specified type. /// public Task ReadAsync(String name, Action readAction); @@ -63,7 +63,7 @@ public interface ISecretSymmetricKeyReader : IAsyncDisposable, IDisposable /// is empty. /// /// - /// The secret vault does not contain a secret with the specified name. + /// The does not contain a secret with the specified name. /// /// /// is -or- is . @@ -72,14 +72,14 @@ public interface ISecretSymmetricKeyReader : IAsyncDisposable, IDisposable /// The object is disposed. /// /// - /// raised an exception -or- the secret vault does not contain a valid secret of the - /// specified type. + /// raised an exception -or- the does not contain a valid secret + /// of the specified type. /// public Task ReadAsync(String name, Action readAction); /// /// Gets the number of and objects that are - /// stored by the . + /// stored by the . /// /// /// The object is disposed. @@ -91,7 +91,7 @@ public Int32 SymmetricKeySecretCount /// /// Gets the textual names that uniquely identify the and - /// objects that are stored by the . + /// objects that are stored by the . /// /// /// The object is disposed. diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretVaultBasicInformation.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretVaultBasicInformation.cs deleted file mode 100644 index e7845ad3..00000000 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretVaultBasicInformation.cs +++ /dev/null @@ -1,46 +0,0 @@ -// ================================================================================================================================= -// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. -// ================================================================================================================================= - -using System; -using System.Collections.Generic; - -namespace RapidField.SolidInstruments.Cryptography.Secrets -{ - /// - /// Represents basic information about a secure container for named secret values which are encrypted and pinned in memory at - /// rest. - /// - public interface ISecretVaultBasicInformation : IAsyncDisposable, IDisposable - { - /// - /// Gets the unique semantic identifier for the . - /// - public String Identifier - { - get; - } - - /// - /// Gets the number of secrets that are stored by the . - /// - /// - /// The object is disposed. - /// - public Int32 SecretCount - { - get; - } - - /// - /// Gets the textual names that uniquely identify the secrets that are stored by the . - /// - /// - /// The object is disposed. - /// - public IEnumerable SecretNames - { - get; - } - } -} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretWriter.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretWriter.cs index b75f598f..cb260b37 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretWriter.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretWriter.cs @@ -12,10 +12,10 @@ namespace RapidField.SolidInstruments.Cryptography.Secrets /// /// Represents a write facility for named secret values which are encrypted and pinned in memory at rest. /// - public interface ISecretWriter : ISecretVaultBasicInformation + public interface ISecretWriter : ISecretStore { /// - /// Adds the specified secret using the specified name to the , or updates it if a secret with + /// Adds the specified secret using the specified name to the , or updates it if a secret with /// the same name already exists. /// /// @@ -36,7 +36,7 @@ public interface ISecretWriter : ISecretVaultBasicInformation public void AddOrUpdate(String name, Byte[] secret); /// - /// Adds the specified secret using the specified name to the , or updates it if a secret with + /// Adds the specified secret using the specified name to the , or updates it if a secret with /// the same name already exists. /// /// @@ -57,7 +57,7 @@ public interface ISecretWriter : ISecretVaultBasicInformation public void AddOrUpdate(String name, String secret); /// - /// Adds the specified secret using the specified name to the , or updates it if a secret with + /// Adds the specified secret using the specified name to the , or updates it if a secret with /// the same name already exists. /// /// @@ -78,7 +78,7 @@ public interface ISecretWriter : ISecretVaultBasicInformation public void AddOrUpdate(String name, Guid secret); /// - /// Adds the specified secret using the specified name to the , or updates it if a secret with + /// Adds the specified secret using the specified name to the , or updates it if a secret with /// the same name already exists. /// /// @@ -99,7 +99,7 @@ public interface ISecretWriter : ISecretVaultBasicInformation public void AddOrUpdate(String name, Double secret); /// - /// Adds the specified secret using the specified name to the , or updates it if a secret with + /// Adds the specified secret using the specified name to the , or updates it if a secret with /// the same name already exists. /// /// @@ -120,7 +120,7 @@ public interface ISecretWriter : ISecretVaultBasicInformation public void AddOrUpdate(String name, SymmetricKey secret); /// - /// Adds the specified secret using the specified name to the , or updates it if a secret with + /// Adds the specified secret using the specified name to the , or updates it if a secret with /// the same name already exists. /// /// @@ -141,7 +141,7 @@ public interface ISecretWriter : ISecretVaultBasicInformation public void AddOrUpdate(String name, CascadingSymmetricKey secret); /// - /// Adds the specified secret using the specified name to the , or updates it if a secret with + /// Adds the specified secret using the specified name to the , or updates it if a secret with /// the same name already exists. /// /// diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretX509CertificateReader.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretX509CertificateReader.cs index e42ce7c0..5054563d 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretX509CertificateReader.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretX509CertificateReader.cs @@ -13,7 +13,7 @@ namespace RapidField.SolidInstruments.Cryptography.Secrets /// /// Represents a read facility for named X.509 certificates which are encrypted and pinned in memory at rest. /// - public interface ISecretX509CertificateReader : IAsyncDisposable, IDisposable + public interface ISecretX509CertificateReader : ISecretStore { /// /// Asynchronously decrypts the specified named secret, pins a copy of it in memory, and performs the specified read @@ -32,7 +32,7 @@ public interface ISecretX509CertificateReader : IAsyncDisposable, IDisposable /// is empty. /// /// - /// The secret vault does not contain a secret with the specified name. + /// The does not contain a secret with the specified name. /// /// /// is -or- is . @@ -41,13 +41,13 @@ public interface ISecretX509CertificateReader : IAsyncDisposable, IDisposable /// The object is disposed. /// /// - /// raised an exception -or- the secret vault does not contain a valid secret of the - /// specified type. + /// raised an exception -or- the does not contain a valid secret + /// of the specified type. /// public Task ReadAsync(String name, Action readAction); /// - /// Gets the number of objects that are stored by the . + /// Gets the number of objects that are stored by the . /// /// /// The object is disposed. @@ -59,7 +59,7 @@ public Int32 X509CertificateSecretCount /// /// Gets the textual names that uniquely identify the objects that are stored by the - /// . + /// . /// /// /// The object is disposed. diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/Password.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/Password.cs index 8b753ab2..b190ad19 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/Password.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/Password.cs @@ -344,7 +344,7 @@ public Boolean MeetsRequirements(IPasswordCompositionRequirements requirements) lowercaseAlphabeticCharacterCount = plaintextPassword.Count(character => character.IsLowercaseAlphabetic()); uppercaseAlphabeticCharacterCount = plaintextPassword.Count(character => character.IsUppercaseAlphabetic()); numericCharacterCount = plaintextPassword.Count(character => character.IsNumeric()); - nonAlphanumericCharacterCount = plaintextPassword.Count(character => character.IsSymbolic() || character.IsWhiteSpaceCharacter()); + nonAlphanumericCharacterCount = plaintextPassword.Count(character => character.IsPunctuation() || character.IsSymbolic() || character.IsWhiteSpaceCharacter()); matchesCaseInsensitiveCommonBreachedPassword = PasswordCompositionRequirements.CommonBreachedPasswords.Contains(plaintextPassword.ToLower()); }); diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/PersistentSecretStore.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/PersistentSecretStore.cs new file mode 100644 index 00000000..fcadff9a --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/PersistentSecretStore.cs @@ -0,0 +1,858 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Core.Concurrency; +using RapidField.SolidInstruments.Cryptography.Symmetric; +using System; +using System.Collections.Generic; +using System.Security.Cryptography.X509Certificates; +using SystemTimeoutException = System.TimeoutException; + +namespace RapidField.SolidInstruments.Cryptography.Secrets +{ + /// + /// Represents a persistent, secure storage facility for named secret values. + /// + /// + /// is the default implementation of + /// . + /// + /// + /// The type of the provider that facilitates persistence of the in-memory . + /// + public abstract class PersistentSecretStore : PersistentSecretStore, IPersistentSecretStore + where TPersistenceVehicle : class, ISecretStorePersistenceVehicle + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A provider that facilitates persistence of the in-memory secret store. + /// + /// + /// is . + /// + /// + /// is disposed. + /// + protected PersistentSecretStore(TPersistenceVehicle persistenceVehicle) + : base(persistenceVehicle) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A provider that facilitates persistence of the in-memory secret store. + /// + /// + /// A value indicating whether or not to load persisted state during construction. The default value is + /// . + /// + /// + /// is . + /// + /// + /// is disposed. + /// + /// + /// is -and- an exception was raised while loading persisted + /// state. + /// + /// + /// The specified timeout threshold was exceeded while attempting to load persisted state. + /// + protected PersistentSecretStore(TPersistenceVehicle persistenceVehicle, Boolean loadPersistedState) + : base(persistenceVehicle, loadPersistedState) + { + return; + } + + /// + /// Adds the specified secret using the specified name to the current + /// , or updates it if a secret with the same name already exists. + /// + /// + /// A textual name that uniquely identifies . + /// + /// + /// The secret value. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + protected sealed override void AddOrUpdate(String name, Byte[] secret, IConcurrencyControlToken controlToken) => InMemoryStore.AddOrUpdate(name, secret); + + /// + /// Adds the specified secret using the specified name to the current + /// , or updates it if a secret with the same name already exists. + /// + /// + /// A textual name that uniquely identifies . + /// + /// + /// The secret value. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + protected sealed override void AddOrUpdate(String name, String secret, IConcurrencyControlToken controlToken) => InMemoryStore.AddOrUpdate(name, secret); + + /// + /// Adds the specified secret using the specified name to the current + /// , or updates it if a secret with the same name already exists. + /// + /// + /// A textual name that uniquely identifies . + /// + /// + /// The secret value. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + protected sealed override void AddOrUpdate(String name, Guid secret, IConcurrencyControlToken controlToken) => InMemoryStore.AddOrUpdate(name, secret); + + /// + /// Adds the specified secret using the specified name to the current + /// , or updates it if a secret with the same name already exists. + /// + /// + /// A textual name that uniquely identifies . + /// + /// + /// The secret value. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + protected sealed override void AddOrUpdate(String name, Double secret, IConcurrencyControlToken controlToken) => InMemoryStore.AddOrUpdate(name, secret); + + /// + /// Adds the specified secret using the specified name to the current + /// , or updates it if a secret with the same name already exists. + /// + /// + /// A textual name that uniquely identifies . + /// + /// + /// The secret value. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + protected sealed override void AddOrUpdate(String name, SymmetricKey secret, IConcurrencyControlToken controlToken) => InMemoryStore.AddOrUpdate(name, secret); + + /// + /// Adds the specified secret using the specified name to the current + /// , or updates it if a secret with the same name already exists. + /// + /// + /// A textual name that uniquely identifies . + /// + /// + /// The secret value. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + protected sealed override void AddOrUpdate(String name, CascadingSymmetricKey secret, IConcurrencyControlToken controlToken) => InMemoryStore.AddOrUpdate(name, secret); + + /// + /// Adds the specified secret using the specified name to the current + /// , or updates it if a secret with the same name already exists. + /// + /// + /// A textual name that uniquely identifies . + /// + /// + /// The secret value. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + protected sealed override void AddOrUpdate(String name, X509Certificate2 secret, IConcurrencyControlToken controlToken) => InMemoryStore.AddOrUpdate(name, secret); + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + } + + /// + /// Represents a persistent, secure storage facility for named secret values. + /// + /// + /// is the default implementation of + /// . + /// + /// + /// The type of the in-memory . + /// + /// + /// The type of the provider that facilitates persistence of the in-memory . + /// + public abstract class PersistentSecretStore : Instrument, IPersistentSecretStore + where TInMemorySecretStore : class, ISecretStore + where TPersistenceVehicle : class, ISecretStorePersistenceVehicle + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A provider that facilitates persistence of the in-memory secret store. + /// + /// + /// is . + /// + /// + /// is disposed. + /// + protected PersistentSecretStore(TPersistenceVehicle persistenceVehicle) + : this(persistenceVehicle, false) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A provider that facilitates persistence of the in-memory secret store. + /// + /// + /// A value indicating whether or not to load persisted state during construction. The default value is + /// . + /// + /// + /// is . + /// + /// + /// is disposed. + /// + /// + /// is -and- an exception was raised while loading persisted + /// state. + /// + /// + /// The specified timeout threshold was exceeded while attempting to load persisted state. + /// + protected PersistentSecretStore(TPersistenceVehicle persistenceVehicle, Boolean loadPersistedState) + : base(ConcurrencyControlMode.ProcessorCountSemaphore) + { + PersistenceVehicle = persistenceVehicle.RejectIf().IsNull(nameof(persistenceVehicle)); + InMemoryStore = persistenceVehicle.InMemoryStore; + + if (loadPersistedState) + { + PersistenceVehicle.LoadPersistedState(); + } + } + + /// + /// Adds the specified secret using the specified name to the current + /// , or updates it if a secret with the same + /// name already exists. + /// + /// + /// A textual name that uniquely identifies . + /// + /// + /// The secret value. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised while attempting to persist the state of the in-memory store. + /// + public void AddOrUpdate(String name, Byte[] secret) + { + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + AddOrUpdate(name, secret, controlToken); + PersistInMemoryStore(); + } + } + + /// + /// Adds the specified secret using the specified name to the current + /// , or updates it if a secret with the same + /// name already exists. + /// + /// + /// A textual name that uniquely identifies . + /// + /// + /// The secret value. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised while attempting to persist the state of the in-memory store. + /// + public void AddOrUpdate(String name, String secret) + { + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + AddOrUpdate(name, secret, controlToken); + PersistInMemoryStore(); + } + } + + /// + /// Adds the specified secret using the specified name to the current + /// , or updates it if a secret with the same + /// name already exists. + /// + /// + /// A textual name that uniquely identifies . + /// + /// + /// The secret value. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised while attempting to persist the state of the in-memory store. + /// + public void AddOrUpdate(String name, Guid secret) + { + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + AddOrUpdate(name, secret, controlToken); + PersistInMemoryStore(); + } + } + + /// + /// Adds the specified secret using the specified name to the current + /// , or updates it if a secret with the same + /// name already exists. + /// + /// + /// A textual name that uniquely identifies . + /// + /// + /// The secret value. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised while attempting to persist the state of the in-memory store. + /// + public void AddOrUpdate(String name, Double secret) + { + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + AddOrUpdate(name, secret, controlToken); + PersistInMemoryStore(); + } + } + + /// + /// Adds the specified secret using the specified name to the current + /// , or updates it if a secret with the same + /// name already exists. + /// + /// + /// A textual name that uniquely identifies . + /// + /// + /// The secret value. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised while attempting to persist the state of the in-memory store. + /// + public void AddOrUpdate(String name, SymmetricKey secret) + { + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + AddOrUpdate(name, secret, controlToken); + PersistInMemoryStore(); + } + } + + /// + /// Adds the specified secret using the specified name to the current + /// , or updates it if a secret with the same + /// name already exists. + /// + /// + /// A textual name that uniquely identifies . + /// + /// + /// The secret value. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised while attempting to persist the state of the in-memory store. + /// + public void AddOrUpdate(String name, CascadingSymmetricKey secret) + { + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + AddOrUpdate(name, secret, controlToken); + PersistInMemoryStore(); + } + } + + /// + /// Adds the specified secret using the specified name to the current + /// , or updates it if a secret with the same + /// name already exists. + /// + /// + /// A textual name that uniquely identifies . + /// + /// + /// The secret value. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised while attempting to persist the state of the in-memory store. + /// + public void AddOrUpdate(String name, X509Certificate2 secret) + { + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + AddOrUpdate(name, secret, controlToken); + PersistInMemoryStore(); + } + } + + /// + /// Removes and safely disposes of all secrets that are stored by the current . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised while attempting to persist the state of the in-memory store. + /// + public void Clear() + { + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + InMemoryStore.Clear(); + PersistInMemoryStore(); + } + } + + /// + /// Attempts to remove a secret with the specified name. + /// + /// + /// A textual name that uniquely identifies the target secret. + /// + /// + /// if the secret was removed, otherwise . + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised while attempting to persist the state of the in-memory store. + /// + public Boolean TryRemove(String name) + { + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + + if (InMemoryStore.TryRemove(name)) + { + PersistInMemoryStore(); + return true; + } + + return false; + } + } + + /// + /// Adds the specified secret using the specified name to the current + /// , or updates it if a secret with the same + /// name already exists. + /// + /// + /// A textual name that uniquely identifies . + /// + /// + /// The secret value. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + protected abstract void AddOrUpdate(String name, Byte[] secret, IConcurrencyControlToken controlToken); + + /// + /// Adds the specified secret using the specified name to the current + /// , or updates it if a secret with the same + /// name already exists. + /// + /// + /// A textual name that uniquely identifies . + /// + /// + /// The secret value. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + protected abstract void AddOrUpdate(String name, String secret, IConcurrencyControlToken controlToken); + + /// + /// Adds the specified secret using the specified name to the current + /// , or updates it if a secret with the same + /// name already exists. + /// + /// + /// A textual name that uniquely identifies . + /// + /// + /// The secret value. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + protected abstract void AddOrUpdate(String name, Guid secret, IConcurrencyControlToken controlToken); + + /// + /// Adds the specified secret using the specified name to the current + /// , or updates it if a secret with the same + /// name already exists. + /// + /// + /// A textual name that uniquely identifies . + /// + /// + /// The secret value. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + protected abstract void AddOrUpdate(String name, Double secret, IConcurrencyControlToken controlToken); + + /// + /// Adds the specified secret using the specified name to the current + /// , or updates it if a secret with the same + /// name already exists. + /// + /// + /// A textual name that uniquely identifies . + /// + /// + /// The secret value. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + protected abstract void AddOrUpdate(String name, SymmetricKey secret, IConcurrencyControlToken controlToken); + + /// + /// Adds the specified secret using the specified name to the current + /// , or updates it if a secret with the same + /// name already exists. + /// + /// + /// A textual name that uniquely identifies . + /// + /// + /// The secret value. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + protected abstract void AddOrUpdate(String name, CascadingSymmetricKey secret, IConcurrencyControlToken controlToken); + + /// + /// Adds the specified secret using the specified name to the current + /// , or updates it if a secret with the same + /// name already exists. + /// + /// + /// A textual name that uniquely identifies . + /// + /// + /// The secret value. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + /// + /// is empty. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + protected abstract void AddOrUpdate(String name, X509Certificate2 secret, IConcurrencyControlToken controlToken); + + /// + /// Releases all resources consumed by the current + /// . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Persists the state of . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised while attempting to persist the state of the in-memory store. + /// + protected void PersistInMemoryStore() + { + RejectIfDisposed(); + + try + { + PersistenceVehicle.PersistInMemoryStoreAsync().Wait(); + } + catch (Exception exception) + { + throw new SecretStorePersistenceException("Secret store persistence failed. See inner exception.", exception); + } + } + + /// + /// Gets the unique semantic identifier for the current + /// . + /// + public String Identifier => InMemoryStore.Identifier; + + /// + /// Gets an that represents the in-memory state of the current + /// . + /// + public TInMemorySecretStore InMemoryStore + { + get; + } + + /// + /// Gets a provider that facilitates persistence of . + /// + public TPersistenceVehicle PersistenceVehicle + { + get; + } + + /// + /// Gets the number of secrets that are stored by the current + /// . + /// + /// + /// The object is disposed. + /// + public Int32 SecretCount => InMemoryStore.SecretCount; + + /// + /// Gets the textual names that uniquely identify the secrets that are stored by the current + /// . + /// + /// + /// The object is disposed. + /// + public IEnumerable SecretNames => InMemoryStore.SecretNames; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretStoreFilePersistenceVehicle.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretStoreFilePersistenceVehicle.cs new file mode 100644 index 00000000..47b6be6c --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretStoreFilePersistenceVehicle.cs @@ -0,0 +1,180 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Core.Concurrency; +using RapidField.SolidInstruments.Core.Extensions; +using System; +using System.Diagnostics; +using System.IO; +using System.Threading.Tasks; + +namespace RapidField.SolidInstruments.Cryptography.Secrets +{ + /// + /// Represents a utility that provides file-based state persistence for an in-memory . + /// + public sealed class SecretStoreFilePersistenceVehicle : SecretStorePersistenceVehicle + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The in-memory store for which state is persisted by the persistence vehicle. + /// + /// + /// is . + /// + [DebuggerHidden] + internal SecretStoreFilePersistenceVehicle(SecretVault inMemoryStore) + : this(inMemoryStore, DefaultDeleteStateFileUponDisposal) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The in-memory store for which state is persisted by the persistence vehicle. + /// + /// + /// A value indicating whether or not the persisted state file should be deleted when the persistence vehicle is disposed. + /// The default value is . + /// + /// + /// is . + /// + [DebuggerHidden] + internal SecretStoreFilePersistenceVehicle(SecretVault inMemoryStore, Boolean deleteStateFileUponDisposal) + : this(inMemoryStore, deleteStateFileUponDisposal, String.IsNullOrEmpty(inMemoryStore?.SemanticIdentity) ? null : $"__{inMemoryStore.SemanticIdentity}.pss") + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The in-memory store for which state is persisted by the persistence vehicle. + /// + /// + /// A value indicating whether or not the persisted state file should be deleted when the persistence vehicle is disposed. + /// The default value is . + /// + /// + /// The full or relative path of the persisted state file. The default value is a pre-defined local path incorporating the + /// semantic identity of . + /// + /// + /// is empty. + /// + /// + /// is invalid, or the caller does not have access to the path. + /// + /// + /// is -or- is + /// . + /// + [DebuggerHidden] + internal SecretStoreFilePersistenceVehicle(SecretVault inMemoryStore, Boolean deleteStateFileUponDisposal, String filePath) + : base(inMemoryStore) + { + _ = filePath.RejectIf().IsNullOrEmpty(nameof(filePath)); + DeleteStateFileUponDisposal = deleteStateFileUponDisposal; + + try + { + FilePath = Path.IsPathRooted(filePath) ? filePath : Path.GetFullPath(filePath); + } + catch (Exception exception) + { + throw new ArgumentException($"The specified path, \"{filePath}\", is invalid, or the caller does not have access to the path. See inner exception.", nameof(filePath), exception); + } + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) + { + try + { + if (disposing) + { + if (DeleteStateFileUponDisposal && File.Exists(FilePath)) + { + File.Delete(FilePath); + } + } + } + finally + { + base.Dispose(disposing); + } + } + + /// + /// Asynchronously requests and returns the persisted state. + /// + /// + /// The unique portion of the semantic identifier for the in-memory store. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + /// + /// A task representing the asynchronous operation and containing the obscured, Base-64-encoded persisted state. + /// + protected sealed override Task LoadPersistedStateAsync(String semanticIdentity, IConcurrencyControlToken controlToken) + { + if (File.Exists(FilePath)) + { + return File.ReadAllTextAsync(FilePath); + } + + throw new SecretStorePersistenceException($"The specified secret state persistence file, \"{FilePath}\", does not exist or the user does not have access to it."); + } + + /// + /// Asynchronously persists the state of . + /// + /// + /// The unique portion of the semantic identifier for the in-memory store. + /// + /// + /// The obscured, Base64-encoded ciphertext of the in-memory store for which state is persisted. + /// + /// + /// A task representing the asynchronous operation. + /// + protected sealed override Task PersistInMemoryStoreAsync(String semanticIdentity, String inMemoryStore) => File.WriteAllTextAsync(FilePath, inMemoryStore); + + /// + /// Gets the full path of the persisted state file. + /// + public String FilePath + { + get; + } + + /// + /// Represents the default value indicating whether or not the persisted state file should be deleted when the persistence + /// vehicle is disposed. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal const Boolean DefaultDeleteStateFileUponDisposal = false; + + /// + /// Represents a value indicating whether or not the persisted state file should be deleted when the persistence vehicle is + /// disposed. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly Boolean DeleteStateFileUponDisposal; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretStorePersistenceException.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretStorePersistenceException.cs new file mode 100644 index 00000000..4ea40755 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretStorePersistenceException.cs @@ -0,0 +1,64 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Extensions; +using System; + +namespace RapidField.SolidInstruments.Cryptography.Secrets +{ + /// + /// Represents an exception that is raised by an instance + /// while attempting to persist an in-memory . + /// + public class SecretStorePersistenceException : Exception + { + /// + /// Initializes a new instance of the class. + /// + public SecretStorePersistenceException() + : this(message: null) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The error message that explains the reason for the exception. + /// + public SecretStorePersistenceException(String message) + : this(message, null) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The exception that is the cause of the current exception. + /// + public SecretStorePersistenceException(Exception innerException) + : this(null, innerException) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The error message that explains the reason for the exception. + /// + /// + /// The exception that is the cause of the current exception. + /// + public SecretStorePersistenceException(String message, Exception innerException) + : base(message.IsNullOrEmpty() ? "An exception was raised while attempting to persist the in-memory secret store." : message, innerException) + { + return; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretStorePersistenceVehicle.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretStorePersistenceVehicle.cs new file mode 100644 index 00000000..8581c475 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretStorePersistenceVehicle.cs @@ -0,0 +1,424 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Core.Concurrency; +using RapidField.SolidInstruments.Core.Extensions; +using RapidField.SolidInstruments.Cryptography.Extensions; +using RapidField.SolidInstruments.Cryptography.Symmetric; +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using SystemTimeoutException = System.TimeoutException; + +namespace RapidField.SolidInstruments.Cryptography.Secrets +{ + /// + /// Represents a utility that provides state persistence for an in-memory . + /// + /// + /// is the default implementation of . + /// + public abstract class SecretStorePersistenceVehicle : SecretStorePersistenceVehicle, ISecretStorePersistenceVehicle + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The in-memory store for which state is persisted by the persistence vehicle. + /// + /// + /// is . + /// + protected SecretStorePersistenceVehicle(SecretVault inMemoryStore) + : base(inMemoryStore) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Asynchronously requests and returns the persisted state. + /// + /// + /// The unique portion of the semantic identifier for the in-memory store. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + /// + /// A task representing the asynchronous operation and containing the obscured, Base-64-encoded persisted state. + /// + protected abstract Task LoadPersistedStateAsync(String semanticIdentity, IConcurrencyControlToken controlToken); + + /// + /// Asynchronously requests the persisted state and hydrates using the result. + /// + /// + /// The in-memory store to which state is loaded. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + /// + /// A task representing the asynchronous operation. + /// + protected sealed override Task LoadPersistedStateAsync(SecretVault inMemoryStore, IConcurrencyControlToken controlToken) => LoadPersistedStateAsync(inMemoryStore.SemanticIdentity, controlToken).ContinueWith(loadPersistedStateTask => + { + var inMemoryStoreCiphertext = ObscurityProcessor.DecryptFromBase64String(loadPersistedStateTask.Result, ObscurityKey); + InMemoryStore.ImportEncryptedSecretVault(inMemoryStoreCiphertext); + }); + + /// + /// Asynchronously persists the state of . + /// + /// + /// The unique portion of the semantic identifier for the in-memory store. + /// + /// + /// The obscured, Base64-encoded ciphertext of the in-memory store for which state is persisted. + /// + /// + /// A task representing the asynchronous operation. + /// + protected abstract Task PersistInMemoryStoreAsync(String semanticIdentity, String inMemoryStore); + + /// + /// Asynchronously persists the state of . + /// + /// + /// The in-memory store for which state is persisted. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + /// + /// A task representing the asynchronous operation. + /// + protected sealed override Task PersistInMemoryStoreAsync(SecretVault inMemoryStore, IConcurrencyControlToken controlToken) => inMemoryStore.ExportAsync().ContinueWith(async exportTask => + { + var inMemoryStoreCiphertext = ObscurityProcessor.EncryptToBase64String(exportTask.Result, ObscurityKey); + await PersistInMemoryStoreAsync(inMemoryStore.SemanticIdentity, inMemoryStoreCiphertext).ConfigureAwait(false); + }); + + /// + /// Finalizes static members of the class. + /// + [DebuggerHidden] + private static void FinalizeStaticMembers() + { + try + { + LazyObscurityKey.Dispose(); + } + finally + { + LazyObscurityPassword.Dispose(); + } + } + + /// + /// Initializes a universal static key that is used to obscure serialized + /// instances. + /// + /// + /// A static key that is used to obscure serialized instances. + /// + [DebuggerHidden] + private static SymmetricKey InitializeObscurityKey() + { + try + { + return SymmetricKey.FromPassword(ObscurityPassword); + } + finally + { + LazyObscurityPassword.Dispose(); + } + } + + /// + /// Initializes the password from which is derived. + /// + /// + /// The password from which is derived. + /// + [DebuggerHidden] + private static IPassword InitializeObscurityPassword() => Password.FromAsciiString(ObscurityKeyPasswordString); + + /// + /// Gets a universal static key that is used to obscure serialized instances. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static SymmetricKey ObscurityKey => LazyObscurityKey.Value; + + /// + /// Gets the password from which is derived. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static IPassword ObscurityPassword => LazyObscurityPassword.Value; + + /// + /// Represents the plaintext password from which is derived. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String ObscurityKeyPasswordString = "Z=h^Yixu[yTaL+2ISD4tr!BG"; + + /// + /// Represents a lazily-initialized, universal static key that obscures serialized + /// instances. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static readonly Lazy LazyObscurityKey = new Lazy(InitializeObscurityKey, LazyThreadSafetyMode.ExecutionAndPublication); + + /// + /// Represents the lazily-initialized password from which is derived. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static readonly Lazy LazyObscurityPassword = new Lazy(InitializeObscurityPassword, LazyThreadSafetyMode.ExecutionAndPublication); + + /// + /// Represents the that is used to encrypt and decrypt persisted state objects. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static readonly ISymmetricProcessor ObscurityProcessor = SymmetricProcessor.ForType(); + + /// + /// Represents a finalizer for static members of the class. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static readonly StaticMemberFinalizer StaticMemberFinalizer = new StaticMemberFinalizer(FinalizeStaticMembers); + } + + /// + /// Represents a utility that provides state persistence for an in-memory . + /// + /// + /// is the default implementation of + /// . + /// + /// + /// The type of the in-memory that the persistence vehicle providers state persistence for. + /// + public abstract class SecretStorePersistenceVehicle : Instrument, ISecretStorePersistenceVehicle + where TInMemorySecretStore : class, ISecretStore + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The in-memory store for which state is persisted by the persistence vehicle. + /// + /// + /// is . + /// + protected SecretStorePersistenceVehicle(TInMemorySecretStore inMemoryStore) + : this(inMemoryStore, ConcurrencyControlMode.SingleThreadLock) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The in-memory store for which state is persisted by the persistence vehicle. + /// + /// + /// The concurrency control mode that is used to manage state. The default value is + /// . + /// + /// + /// is . + /// + /// + /// is equal to . + /// + protected SecretStorePersistenceVehicle(TInMemorySecretStore inMemoryStore, ConcurrencyControlMode stateControlMode) + : base(stateControlMode) + { + InMemoryStore = inMemoryStore.RejectIf().IsNull(nameof(inMemoryStore)); + } + + /// + /// Requests the persisted state and hydrates using the result. + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised while retrieving persisted state or hydrating . + /// + /// + /// The specified timeout threshold was exceeded while attempting to load persisted state. + /// + public void LoadPersistedState() => LoadPersistedState(DefaultLoadPersistedStateTimeoutThreshold); + + /// + /// Requests the persisted state and hydrates using the result. + /// + /// + /// The maximum length of time to wait for the state to be hydrated before raising an exception, or + /// to specify an infinite duration. The default value is one minute. + /// + /// + /// is a negative number other than -or- + /// is greater than . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised while retrieving persisted state or hydrating . + /// + /// + /// The specified timeout threshold was exceeded while attempting to load persisted state. + /// + public void LoadPersistedState(TimeSpan timeoutThreshold) + { + RejectIfDisposed(); + + try + { + LoadPersistedStateAsync().Wait(timeoutThreshold); + } + catch (AggregateException exception) + { + if (exception.InnerExceptions.Any(innerException => innerException.GetType() == typeof(TaskCanceledException))) + { + throw new SystemTimeoutException("The timeout threshold was exceeded while attempting to load persisted state.", exception); + } + + throw new SecretStorePersistenceException(exception); + } + } + + /// + /// Asynchronously requests the persisted state and hydrates using the result. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised while retrieving persisted state or hydrating . + /// + public Task LoadPersistedStateAsync() + { + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + + try + { + return LoadPersistedStateAsync(InMemoryStore, controlToken); + } + catch (SecretStorePersistenceException) + { + throw; + } + catch (Exception exception) + { + throw new SecretStorePersistenceException(exception); + } + } + } + + /// + /// Asynchronously persists the state of . + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised while attempting to persist . + /// + public Task PersistInMemoryStoreAsync() + { + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + + try + { + return PersistInMemoryStoreAsync(InMemoryStore, controlToken); + } + catch (SecretStorePersistenceException) + { + throw; + } + catch (Exception exception) + { + throw new SecretStorePersistenceException(exception); + } + } + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Asynchronously requests the persisted state and hydrates using the result. + /// + /// + /// The in-memory store to which state is loaded. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + /// + /// A task representing the asynchronous operation. + /// + protected abstract Task LoadPersistedStateAsync(TInMemorySecretStore inMemoryStore, IConcurrencyControlToken controlToken); + + /// + /// Asynchronously persists the state of . + /// + /// + /// The in-memory store for which state is persisted. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + /// + /// A task representing the asynchronous operation. + /// + protected abstract Task PersistInMemoryStoreAsync(TInMemorySecretStore inMemoryStore, IConcurrencyControlToken controlToken); + + /// + /// Gets the in-memory that the current + /// provides state persistence for. + /// + public TInMemorySecretStore InMemoryStore + { + get; + } + + /// + /// Represents the default timeout threshold duration that is used by . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static readonly TimeSpan DefaultLoadPersistedStateTimeoutThreshold = TimeSpan.FromMinutes(1); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretVault.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretVault.cs index 9e053439..90fb1f6a 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretVault.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/SecretVault.cs @@ -33,16 +33,59 @@ public sealed class SecretVault : Instrument, ISecretVault /// Initializes a new instance of the class. /// public SecretVault() + : this(semanticIdentity: null) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A compliant password from which to derive the master key, which is + /// used as the default encryption key for exported secrets. A master key is generated on demand if the parameterless + /// constructor is used. + /// + /// + /// does not comply with . + /// + /// + /// is . + /// + /// + /// is disposed. + /// + public SecretVault(IPassword masterPassword) + : this(masterPassword?.DerivedIdentity.ToSerializedString(), masterPassword) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The unique, lowercase alphanumeric portion of the semantic identifier for the secret vault, or + /// to generate a random identity. + /// + /// + /// is not null or empty -and- is not a lowercase alphanumeric string. + /// + public SecretVault(String semanticIdentity) : base() { LazyReferenceManager = new Lazy(() => new ReferenceManager(), LazyThreadSafetyMode.ExecutionAndPublication); Secrets = new Dictionary(); - SemanticIdentity = Secret.NewRandomSemanticIdentifier(); + SemanticIdentity = semanticIdentity.IsNullOrEmpty() ? Secret.NewRandomSemanticIdentifier() : semanticIdentity.RejectIf().DoesNotMatchRegularExpression(SemanticIdentityRegularExpression, nameof(semanticIdentity)); } /// /// Initializes a new instance of the class. /// + /// + /// The unique, lowercase alphanumeric portion of the semantic identifier for the secret vault, or + /// to generate a random identity. + /// /// /// A compliant password from which to derive the master key, which is /// used as the default encryption key for exported secrets. A master key is generated on demand if the parameterless @@ -57,8 +100,11 @@ public SecretVault() /// /// is disposed. /// - public SecretVault(IPassword masterPassword) - : this() + /// + /// is not null or empty -and- is not a lowercase alphanumeric string. + /// + public SecretVault(String semanticIdentity, IPassword masterPassword) + : this(semanticIdentity) { _ = CreateMasterKey(masterPassword); } @@ -1111,6 +1157,22 @@ public Boolean TryRemove(String name) } } + /// + /// Creates and stores a new master key for the current . + /// + /// + /// The resulting master key secret. + /// + /// + /// The object is disposed. + /// + [DebuggerHidden] + internal IReadOnlySecret CreateMasterKey() + { + using var masterPassword = Password.NewStrongPassword(); + return CreateMasterKey(masterPassword); + } + /// /// Releases all resources consumed by the current . /// @@ -1202,22 +1264,6 @@ private void AddOrUpdate(String name, IReadOnlySecret secret, IConcurrencyContro Secrets.Add(name, secret.RejectIf().IsNull(nameof(secret)).TargetArgument); } - /// - /// Creates and stores a new master key for the current . - /// - /// - /// The resulting master key secret. - /// - /// - /// The object is disposed. - /// - [DebuggerHidden] - private IReadOnlySecret CreateMasterKey() - { - using var masterPassword = Password.NewStrongPassword(); - return CreateMasterKey(masterPassword); - } - /// /// Creates and stores a new master key for the current . /// @@ -1653,29 +1699,35 @@ public IEnumerable X509CertificateSecretNames internal static readonly IPasswordCompositionRequirements MasterKeyPasswordCompositionRequirements = PasswordCompositionRequirements.Strict; /// - /// Gets the length of time, in seconds, for expiration of in-memory keys at which the probability of a key regeneration and - /// replacement event becomes 1 (100%). + /// Represents a collection of secrets that are stored by the current . /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const Int32 InMemoryKeyAgeRegenerationThresholdInSeconds = 180; + internal readonly IDictionary Secrets; /// - /// Represents the lazily-initialized utility that disposes of the secrets that are managed by the current - /// . + /// Represents the unique portion of the semantic identifier for the current . /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly Lazy LazyReferenceManager; + internal readonly String SemanticIdentity; /// - /// Represents a collection of secrets that are stored by the current . + /// Gets the length of time, in seconds, for expiration of in-memory keys at which the probability of a key regeneration and + /// replacement event becomes 1 (100%). /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly IDictionary Secrets; + private const Int32 InMemoryKeyAgeRegenerationThresholdInSeconds = 180; /// - /// Represents the unique portion of the semantic identifier for the current . + /// Represents a regular expression that is used to validate . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String SemanticIdentityRegularExpression = "^[0-9a-z]*$"; + + /// + /// Represents the lazily-initialized utility that disposes of the secrets that are managed by the current + /// . /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly String SemanticIdentity; + private readonly Lazy LazyReferenceManager; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/SoftwareSecurityModule.cs b/src/RapidField.SolidInstruments.Cryptography/SoftwareSecurityModule.cs new file mode 100644 index 00000000..5ad522f7 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/SoftwareSecurityModule.cs @@ -0,0 +1,816 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Cryptography.Extensions; +using RapidField.SolidInstruments.Cryptography.Secrets; +using RapidField.SolidInstruments.Cryptography.Symmetric; +using System; +using System.Diagnostics; +using System.IO; +using System.Security; +using SystemTimeoutException = System.TimeoutException; + +namespace RapidField.SolidInstruments.Cryptography +{ + /// + /// Represents a centralized facility for safeguarding digital secrets and performing cryptographic operations. + /// + /// + /// is the default implementation of . + /// + public sealed class SoftwareSecurityModule : SoftwareSecurityModule + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A compliant password from which to derive the master key, which is + /// used as the default encryption key for exported secrets. A master key is generated on demand if a passwordless + /// constructor is used. + /// + /// + /// does not comply with . + /// + /// + /// is . + /// + /// + /// is disposed. + /// + /// + /// The specified timeout threshold was exceeded while attempting to load persisted state. + /// + public SoftwareSecurityModule(IPassword masterPassword) + : this(masterPassword, SecretStoreFilePersistenceVehicle.DefaultDeleteStateFileUponDisposal) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The full or relative path of the persisted state file. The default value is a pre-defined local path incorporating the + /// semantic identity of the in-memory store. + /// + /// + /// is empty. + /// + /// + /// is invalid, or the caller does not have access to the path. + /// + /// + /// is . + /// + /// + /// The specified timeout threshold was exceeded while attempting to load persisted state. + /// + public SoftwareSecurityModule(String filePath) + : this(SecretStoreFilePersistenceVehicle.DefaultDeleteStateFileUponDisposal, filePath) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A compliant password from which to derive the master key, which is + /// used as the default encryption key for exported secrets. A master key is generated on demand if a passwordless + /// constructor is used. + /// + /// + /// The full or relative path of the persisted state file. The default value is a pre-defined local path incorporating the + /// semantic identity of the in-memory store. + /// + /// + /// is empty. + /// + /// + /// is invalid, or the caller does not have access to the path -or- + /// does not comply with . + /// + /// + /// is -or- is + /// . + /// + /// + /// is disposed. + /// + /// + /// The specified timeout threshold was exceeded while attempting to load persisted state. + /// + public SoftwareSecurityModule(IPassword masterPassword, String filePath) + : this(masterPassword, SecretStoreFilePersistenceVehicle.DefaultDeleteStateFileUponDisposal, filePath) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A compliant password from which to derive the master key, which is + /// used as the default encryption key for exported secrets. A master key is generated on demand if a passwordless + /// constructor is used. + /// + /// + /// A value indicating whether or not the persisted state file should be deleted when the persistence vehicle is disposed. + /// The default value is . + /// + /// + /// does not comply with . + /// + /// + /// is . + /// + /// + /// is disposed. + /// + /// + /// The specified timeout threshold was exceeded while attempting to load persisted state. + /// + [DebuggerHidden] + internal SoftwareSecurityModule(IPassword masterPassword, Boolean deleteStateFileUponDisposal) + : this(new SecretStoreFilePersistenceVehicle(new SecretVault(masterPassword), deleteStateFileUponDisposal)) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A value indicating whether or not the persisted state file should be deleted when the persistence vehicle is disposed. + /// The default value is . + /// + /// + /// The full or relative path of the persisted state file. The default value is a pre-defined local path incorporating the + /// semantic identity of the in-memory store. + /// + /// + /// is empty. + /// + /// + /// is invalid, or the caller does not have access to the path. + /// + /// + /// is . + /// + /// + /// The specified timeout threshold was exceeded while attempting to load persisted state. + /// + [DebuggerHidden] + internal SoftwareSecurityModule(Boolean deleteStateFileUponDisposal, String filePath) + : this(new SecretStoreFilePersistenceVehicle(new SecretVault(), deleteStateFileUponDisposal, filePath)) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A compliant password from which to derive the master key, which is + /// used as the default encryption key for exported secrets. A master key is generated on demand if a passwordless + /// constructor is used. + /// + /// + /// A value indicating whether or not the persisted state file should be deleted when the persistence vehicle is disposed. + /// The default value is . + /// + /// + /// The full or relative path of the persisted state file. The default value is a pre-defined local path incorporating the + /// semantic identity of the in-memory store. + /// + /// + /// is empty. + /// + /// + /// is invalid, or the caller does not have access to the path -or- + /// does not comply with . + /// + /// + /// is -or- is + /// . + /// + /// + /// is disposed. + /// + /// + /// The specified timeout threshold was exceeded while attempting to load persisted state. + /// + [DebuggerHidden] + internal SoftwareSecurityModule(IPassword masterPassword, Boolean deleteStateFileUponDisposal, String filePath) + : this(new SecretStoreFilePersistenceVehicle(new SecretVault(masterPassword), deleteStateFileUponDisposal, filePath)) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A provider that facilitates persistence of the in-memory secret store. + /// + /// + /// is . + /// + /// + /// is disposed. + /// + /// + /// The file path specified by references an existing file -and- an exception was + /// raised while loading persisted state. + /// + /// + /// The specified timeout threshold was exceeded while attempting to load persisted state. + /// + [DebuggerHidden] + private SoftwareSecurityModule(SecretStoreFilePersistenceVehicle persistenceVehicle) + : base(persistenceVehicle, String.IsNullOrEmpty(persistenceVehicle?.FilePath) ? false : File.Exists(persistenceVehicle.FilePath)) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) + { + try + { + if (disposing) + { + PersistenceVehicle?.InMemoryStore.Dispose(); + PersistenceVehicle?.Dispose(); + } + } + finally + { + base.Dispose(disposing); + } + } + } + + /// + /// Represents a centralized facility for safeguarding digital secrets and performing cryptographic operations. + /// + /// + /// is the default implementation of + /// . + /// + /// + /// The type of the provider that facilitates persistence of the in-memory . + /// + public abstract class SoftwareSecurityModule : PersistentSecretStore, ISoftwareSecurityModule + where TPersistenceVehicle : class, ISecretStorePersistenceVehicle + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A provider that facilitates persistence of the in-memory secret store. + /// + /// + /// is . + /// + /// + /// is disposed. + /// + protected SoftwareSecurityModule(TPersistenceVehicle persistenceVehicle) + : base(persistenceVehicle) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A provider that facilitates persistence of the in-memory secret store. + /// + /// + /// A value indicating whether or not to load persisted state during construction. The default value is + /// . + /// + /// + /// is . + /// + /// + /// is disposed. + /// + /// + /// is -and- an exception was raised while loading persisted + /// state. + /// + /// + /// The specified timeout threshold was exceeded while attempting to load persisted state. + /// + protected SoftwareSecurityModule(TPersistenceVehicle persistenceVehicle, Boolean loadPersistedState) + : base(persistenceVehicle, loadPersistedState) + { + return; + } + + /// + /// Decrypts the specified model using the master key. + /// + /// + /// The type of the model to decrypt. + /// + /// + /// The model to decrypt. + /// + /// + /// The decrypted model. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during decryption or deserialization. + /// + public TModel Decrypt(IEncryptedModel model) + where TModel : class, IModel + { + EnsureExistenceOfMasterKey(); + return Decrypt(model, InMemoryStore.MasterKeyName); + } + + /// + /// Decrypts the specified model using the specified key. + /// + /// + /// The type of the model to decrypt. + /// + /// + /// The model to encrypt. + /// + /// + /// The name of the key to use when encrypting the model, or to use the master key. + /// + /// + /// The decrypted model. + /// + /// + /// is empty. + /// + /// + /// The secret store does not contain a key with the specified name. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during decryption or deserialization. + /// + public TModel Decrypt(IEncryptedModel model, String keyName) + where TModel : class, IModel + { + _ = model.RejectIf().IsNull(nameof(model)); + + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + + if (InMemoryStore.Secrets.ContainsKey(keyName.RejectIf().IsNullOrEmpty(nameof(keyName)))) + { + var plaintextModel = (TModel)null; + var keySecret = InMemoryStore.Secrets[keyName]; + + try + { + if (keySecret.ValueType == typeof(SymmetricKey)) + { + ((SymmetricKeySecret)keySecret).ReadAsync((SymmetricKey key) => + { + plaintextModel = model.ToPlaintextModel(key); + }).Wait(); + } + else if (keySecret.ValueType == typeof(CascadingSymmetricKey)) + { + ((CascadingSymmetricKeySecret)keySecret).ReadAsync((CascadingSymmetricKey key) => + { + plaintextModel = model.ToPlaintextModel(key); + }).Wait(); + } + else + { + throw new ArgumentException($"The specified key name, \"{keyName}\" does not reference a valid key.", nameof(keySecret.Name)); + } + } + catch (AggregateException exception) + { + throw new SecurityException($"The specified key, \"{keyName}\", could not be used to decrypt the specified model. Decryption or deserialization failed.", exception); + } + + return plaintextModel; + } + else if (keyName == InMemoryStore.MasterKeyName) + { + throw new SecurityException("The model cannot be decrypted without specifying an explicit key because the secret store does not have a master key."); + } + + throw new ArgumentException($"The secret store does not contain a key with the specified name, \"{keyName}\".", nameof(keyName)); + } + } + + /// + /// Decrypts the specified Base-64 encoded ciphertext string using the master key. + /// + /// + /// The Base-64 encoded ciphertext string to decrypt. + /// + /// + /// The resulting plaintext string. + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during decryption. + /// + public String Decrypt(String ciphertext) + { + EnsureExistenceOfMasterKey(); + return Decrypt(ciphertext, InMemoryStore.MasterKeyName); + } + + /// + /// Decrypts the specified Base-64 encoded ciphertext string using the specified key. + /// + /// + /// The Base-64 encoded ciphertext string to decrypt. + /// + /// + /// The name of the key to use when decrypting the model, or to use the master key. + /// + /// + /// The resulting plaintext string. + /// + /// + /// is empty -or- is empty. + /// + /// + /// The secret store does not contain a key with the specified name. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during decryption. + /// + public String Decrypt(String ciphertext, String keyName) + { + _ = ciphertext.RejectIf().IsNullOrEmpty(nameof(ciphertext)); + + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + + if (InMemoryStore.Secrets.ContainsKey(keyName.RejectIf().IsNullOrEmpty(nameof(keyName)))) + { + var plaintext = (String)null; + var keySecret = InMemoryStore.Secrets[keyName]; + + try + { + if (keySecret.ValueType == typeof(SymmetricKey)) + { + ((SymmetricKeySecret)keySecret).ReadAsync((SymmetricKey key) => + { + plaintext = SymmetricStringProcessor.Instance.DecryptFromBase64String(ciphertext, key); + }).Wait(); + } + else if (keySecret.ValueType == typeof(CascadingSymmetricKey)) + { + ((CascadingSymmetricKeySecret)keySecret).ReadAsync((CascadingSymmetricKey key) => + { + plaintext = SymmetricStringProcessor.Instance.DecryptFromBase64String(ciphertext, key); + }).Wait(); + } + else + { + throw new ArgumentException($"The specified key name, \"{keyName}\" does not reference a valid key.", nameof(keySecret.Name)); + } + } + catch (AggregateException exception) + { + throw new SecurityException($"The specified key, \"{keyName}\", could not be used to decrypt the specified string. Decryption failed.", exception); + } + + return plaintext; + } + else if (keyName == InMemoryStore.MasterKeyName) + { + throw new SecurityException("The string cannot be decrypted without specifying an explicit key because the secret store does not have a master key."); + } + + throw new ArgumentException($"The secret store does not contain a key with the specified name, \"{keyName}\".", nameof(keyName)); + } + } + + /// + /// Encrypts the specified model using the master key. + /// + /// + /// The type of the model to encrypt. + /// + /// + /// The model to encrypt. + /// + /// + /// The encrypted model. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during encryption or serialization. + /// + public IEncryptedModel Encrypt(TModel model) + where TModel : class, IModel + { + EnsureExistenceOfMasterKey(); + return Encrypt(model, InMemoryStore.MasterKeyName); + } + + /// + /// Encrypts the specified model using the specified key. + /// + /// + /// The type of the model to encrypt. + /// + /// + /// The model to encrypt. + /// + /// + /// The name of the key to use when encrypting the model, or to use the master key. + /// + /// + /// The encrypted model. + /// + /// + /// is empty. + /// + /// + /// The secret store does not contain a key with the specified name. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during encryption or serialization. + /// + public IEncryptedModel Encrypt(TModel model, String keyName) + where TModel : class, IModel + { + _ = model.RejectIf().IsNull(nameof(model)); + + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + + if (InMemoryStore.Secrets.ContainsKey(keyName.RejectIf().IsNullOrEmpty(nameof(keyName)))) + { + var encryptedModel = (IEncryptedModel)null; + var keySecret = InMemoryStore.Secrets[keyName]; + + try + { + if (keySecret.ValueType == typeof(SymmetricKey)) + { + ((SymmetricKeySecret)keySecret).ReadAsync((SymmetricKey key) => + { + encryptedModel = model.Encrypt(key); + }).Wait(); + } + else if (keySecret.ValueType == typeof(CascadingSymmetricKey)) + { + ((CascadingSymmetricKeySecret)keySecret).ReadAsync((CascadingSymmetricKey key) => + { + encryptedModel = model.Encrypt(key); + }).Wait(); + } + else + { + throw new ArgumentException($"The specified key name, \"{keyName}\" does not reference a valid key.", nameof(keySecret.Name)); + } + } + catch (AggregateException exception) + { + throw new SecurityException($"The specified key, \"{keyName}\", could not be used to encrypt the specified model. Encryption or serialization failed.", exception); + } + + return encryptedModel; + } + else if (keyName == InMemoryStore.MasterKeyName) + { + throw new SecurityException("The model cannot be encrypted without specifying an explicit key because the secret store does not have a master key."); + } + + throw new ArgumentException($"The secret store does not contain a key with the specified name, \"{keyName}\".", nameof(keyName)); + } + } + + /// + /// Encrypts the specified plaintext string using the master key. + /// + /// + /// The plaintext string to encrypt. + /// + /// + /// The resulting Base-64 encoded, encrypted string. + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during encryption. + /// + public String Encrypt(String plaintext) + { + EnsureExistenceOfMasterKey(); + return Encrypt(plaintext, InMemoryStore.MasterKeyName); + } + + /// + /// Encrypts the specified plaintext string using the specified key. + /// + /// + /// The plaintext string to encrypt. + /// + /// + /// The name of the key to use when encrypting the plaintext string, or to use the master key. + /// + /// + /// The resulting Base-64 encoded, encrypted string. + /// + /// + /// is empty -or- is empty. + /// + /// + /// The secret store does not contain a key with the specified name. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during encryption. + /// + public String Encrypt(String plaintext, String keyName) + { + _ = plaintext.RejectIf().IsNullOrEmpty(nameof(plaintext)); + + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + + if (InMemoryStore.Secrets.ContainsKey(keyName.RejectIf().IsNullOrEmpty(nameof(keyName)))) + { + var ciphertext = (String)null; + var keySecret = InMemoryStore.Secrets[keyName]; + + try + { + if (keySecret.ValueType == typeof(SymmetricKey)) + { + ((SymmetricKeySecret)keySecret).ReadAsync((SymmetricKey key) => + { + ciphertext = SymmetricStringProcessor.Instance.EncryptToBase64String(plaintext, key); + }).Wait(); + } + else if (keySecret.ValueType == typeof(CascadingSymmetricKey)) + { + ((CascadingSymmetricKeySecret)keySecret).ReadAsync((CascadingSymmetricKey key) => + { + ciphertext = SymmetricStringProcessor.Instance.EncryptToBase64String(plaintext, key); + }).Wait(); + } + else + { + throw new ArgumentException($"The specified key name, \"{keyName}\" does not reference a valid key.", nameof(keySecret.Name)); + } + } + catch (AggregateException exception) + { + throw new SecurityException($"The specified key, \"{keyName}\", could not be used to encrypt the specified string. Encryption failed.", exception); + } + + return ciphertext; + } + else if (keyName == InMemoryStore.MasterKeyName) + { + throw new SecurityException("The string cannot be encrypted without specifying an explicit key because the secret store does not have a master key."); + } + + throw new ArgumentException($"The secret store does not contain a key with the specified name, \"{keyName}\".", nameof(keyName)); + } + } + + /// + /// Generates a new and returns its assigned name. + /// + /// + /// The textual name assigned to the new secret. + /// + /// + /// The object is disposed. + /// + public String NewCascadingSymmetricKey() + { + RejectIfDisposed(); + var keyName = InMemoryStore.NewCascadingSymmetricKey(); + PersistInMemoryStore(); + return keyName; + } + + /// + /// Generates a new and returns its assigned name. + /// + /// + /// The textual name assigned to the new secret. + /// + /// + /// The object is disposed. + /// + public String NewSymmetricKey() + { + RejectIfDisposed(); + var keyName = InMemoryStore.NewSymmetricKey(); + PersistInMemoryStore(); + return keyName; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Creates a master key for the in-memory store and persists it if one does not exist. + /// + /// + /// The object is disposed. + /// + [DebuggerHidden] + private void EnsureExistenceOfMasterKey() + { + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + + if (InMemoryStore.Secrets.ContainsKey(InMemoryStore.MasterKeyName) == false) + { + _ = InMemoryStore.CreateMasterKey(); + PersistInMemoryStore(); + } + } + } + + /// + /// Gets the secret reading facility for the current . + /// + public ISecretReader SecretReader => InMemoryStore; + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SecretVaultTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SecretVaultTests.cs index ce9b170c..b444a65b 100644 --- a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SecretVaultTests.cs +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Secrets/SecretVaultTests.cs @@ -153,8 +153,8 @@ public void ExportAsync_ShouldBeReversible_UsingMasterKey() targetOne.SymmetricKeySecretCount.Should().Be(1); targetOne.SecretCount.Should().Be(3); targetOne.SecretNames.Should().Contain(secretNames); - targetTwo.SymmetricKeySecretCount.Should().Be(2); - targetTwo.SecretCount.Should().Be(4); + targetTwo.SymmetricKeySecretCount.Should().Be(1); + targetTwo.SecretCount.Should().Be(3); targetTwo.SecretNames.Should().Contain(secretNames); targetTwo.ReadAsync(secretOneName, (String value) => { value.Should().Be(secretOneValue); }).Wait(); targetTwo.ReadAsync(secretTwoName, (Double value) => { value.Should().Be(secretTwoValue); }).Wait(); @@ -172,11 +172,11 @@ public void ExportAsync_ShouldBeReversible_UsingMasterKey() }).Wait(); // Assert. - targetOne.SymmetricKeySecretCount.Should().Be(2); - targetTwo.SecretCount.Should().Be(4); + targetOne.SymmetricKeySecretCount.Should().Be(1); + targetTwo.SecretCount.Should().Be(3); targetTwo.SecretNames.Should().Contain(secretNames); - targetTwo.SymmetricKeySecretCount.Should().Be(2); - targetOne.SecretCount.Should().Be(4); + targetTwo.SymmetricKeySecretCount.Should().Be(1); + targetOne.SecretCount.Should().Be(3); targetOne.SecretNames.Should().Contain(secretNames); targetOne.ReadAsync(secretOneName, (String value) => { value.Should().Be(secretOneValue); }).Wait(); targetOne.ReadAsync(secretTwoName, (Double value) => { value.Should().Be(secretTwoValue); }).Wait(); diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/SoftwareSecurityModuleTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/SoftwareSecurityModuleTests.cs new file mode 100644 index 00000000..b5daa1d4 --- /dev/null +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/SoftwareSecurityModuleTests.cs @@ -0,0 +1,220 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RapidField.SolidInstruments.Cryptography.Secrets; +using System; +using System.Security; +using System.Security.Cryptography; + +namespace RapidField.SolidInstruments.Cryptography.UnitTests +{ + [TestClass] + public class SoftwareSecurityModuleTests + { + [TestMethod] + public void Encrypt_ShouldBeReversible_ForModelPlaintext_UsingMultipleModules_WithSharedMasterPassword() + { + // Arrange. + using var randomnessProvider = RandomNumberGenerator.Create(); + var plaintext = SimulatedModel.Random(randomnessProvider); + using var masterPassword = Password.NewStrongPassword(); + using var targetOne = new SoftwareSecurityModule(masterPassword, true); + using var targetTwo = new SoftwareSecurityModule(masterPassword, true); + + // Act. + var ciphertext = targetOne.Encrypt(plaintext); + var result = targetTwo.Decrypt(ciphertext); + + // Assert. + result.Should().NotBeNull(); + result.Should().Be(plaintext); + } + + [TestMethod] + public void Encrypt_ShouldBeReversible_ForModelPlaintext_UsingSingleModule_WithMatchingCascadingSymmetricKey() + { + // Arrange. + using var randomnessProvider = RandomNumberGenerator.Create(); + var plaintext = SimulatedModel.Random(randomnessProvider); + using var masterPassword = Password.NewStrongPassword(); + using var target = new SoftwareSecurityModule(masterPassword, true); + var keyName = target.NewCascadingSymmetricKey(); + + // Act. + var ciphertext = target.Encrypt(plaintext, keyName); + var result = target.Decrypt(ciphertext, keyName); + + // Assert. + result.Should().NotBeNull(); + result.Should().Be(plaintext); + } + + [TestMethod] + public void Encrypt_ShouldBeReversible_ForModelPlaintext_UsingSingleModule_WithMatchingSymmetricKeys() + { + // Arrange. + using var randomnessProvider = RandomNumberGenerator.Create(); + var plaintext = SimulatedModel.Random(randomnessProvider); + using var masterPassword = Password.NewStrongPassword(); + using var target = new SoftwareSecurityModule(masterPassword, true); + var keyName = target.NewSymmetricKey(); + + // Act. + var ciphertext = target.Encrypt(plaintext, keyName); + var result = target.Decrypt(ciphertext, keyName); + + // Assert. + result.Should().NotBeNull(); + result.Should().Be(plaintext); + } + + [TestMethod] + public void Encrypt_ShouldBeReversible_ForStringPlaintext_UsingMultipleModules_WithSharedMasterPassword() + { + // Arrange. + var plaintext = "foo"; + using var masterPassword = Password.NewStrongPassword(); + using var targetOne = new SoftwareSecurityModule(masterPassword, true); + using var targetTwo = new SoftwareSecurityModule(masterPassword, true); + + // Act. + var ciphertext = targetOne.Encrypt(plaintext); + var result = targetTwo.Decrypt(ciphertext); + + // Assert. + result.Should().NotBeNullOrEmpty(); + result.Should().Be(plaintext); + } + + [TestMethod] + public void Encrypt_ShouldBeReversible_ForStringPlaintext_UsingSingleModule_WithMatchingCascadingSymmetricKey() + { + // Arrange. + var plaintext = "foo"; + using var masterPassword = Password.NewStrongPassword(); + using var target = new SoftwareSecurityModule(masterPassword, true); + var keyName = target.NewCascadingSymmetricKey(); + + // Act. + var ciphertext = target.Encrypt(plaintext, keyName); + var result = target.Decrypt(ciphertext, keyName); + + // Assert. + result.Should().NotBeNullOrEmpty(); + result.Should().Be(plaintext); + } + + [TestMethod] + public void Encrypt_ShouldBeReversible_ForStringPlaintext_UsingSingleModule_WithMatchingSymmetricKeys() + { + // Arrange. + var plaintext = "foo"; + using var masterPassword = Password.NewStrongPassword(); + using var target = new SoftwareSecurityModule(masterPassword, true); + var keyName = target.NewSymmetricKey(); + + // Act. + var ciphertext = target.Encrypt(plaintext, keyName); + var result = target.Decrypt(ciphertext, keyName); + + // Assert. + result.Should().NotBeNullOrEmpty(); + result.Should().Be(plaintext); + } + + [TestMethod] + public void Encrypt_ShouldNotBeReversible_ForModelPlaintext_UsingMultipleModules_WithDifferingMasterPassword() + { + // Arrange. + using var randomnessProvider = RandomNumberGenerator.Create(); + var plaintext = SimulatedModel.Random(randomnessProvider); + using var masterPasswordOne = Password.NewStrongPassword(); + using var masterPasswordTwo = Password.NewStrongPassword(); + using var targetOne = new SoftwareSecurityModule(masterPasswordOne, true); + using var targetTwo = new SoftwareSecurityModule(masterPasswordTwo, true); + + // Act. + var ciphertext = targetOne.Encrypt(plaintext); + var action = new Action(() => targetTwo.Decrypt(ciphertext)); + + // Assert. + action.Should().Throw(); + } + + [TestMethod] + public void Encrypt_ShouldNotBeReversible_ForStringPlaintext_UsingMultipleModules_WithDifferingMasterPassword() + { + // Arrange. + var plaintext = "foo"; + using var masterPasswordOne = Password.NewStrongPassword(); + using var masterPasswordTwo = Password.NewStrongPassword(); + using var targetOne = new SoftwareSecurityModule(masterPasswordOne, true); + using var targetTwo = new SoftwareSecurityModule(masterPasswordTwo, true); + + // Act. + var ciphertext = targetOne.Encrypt(plaintext); + var action = new Action(() => targetTwo.Decrypt(ciphertext)); + + // Assert. + action.Should().Throw(); + } + + [TestMethod] + public void FunctionalLifeSpanTest_ShouldProduceDesiredResults() + { + using (var masterPassword = Password.NewStrongPassword()) + { + // Arrange. + var plaintext = "foo"; + var guidSecretValue = Guid.NewGuid(); + var guidSecretName = "GuidSecret"; + var ciphertextOne = (String)null; + var ciphertextTwo = (String)null; + var keyName = (String)null; + var resultOne = (String)null; + var resultTwo = (String)null; + + using (var target = new SoftwareSecurityModule(masterPassword, false)) + { + // Act. + keyName = target.NewSymmetricKey(); + ciphertextOne = target.Encrypt(plaintext); + ciphertextTwo = target.Encrypt(plaintext, keyName); + target.AddOrUpdate(guidSecretName, guidSecretValue); + + // Assert. + target.SecretCount.Should().Be(3); + target.SecretNames.Should().Contain(keyName); + target.SecretNames.Should().Contain(guidSecretName); + ciphertextOne.Should().NotBeNullOrEmpty(); + ciphertextTwo.Should().NotBeNullOrEmpty(); + ciphertextOne.Should().NotBe(ciphertextTwo); + } + + using (var target = new SoftwareSecurityModule(masterPassword, true)) + { + // Assert. + target.SecretCount.Should().Be(3); + target.SecretNames.Should().Contain(keyName); + target.SecretNames.Should().Contain(guidSecretName); + + // Act. + resultOne = target.Decrypt(ciphertextOne); + resultTwo = target.Decrypt(ciphertextTwo, keyName); + + // Assert. + target.SecretReader.ReadAsync(guidSecretName, (Guid secretValue) => { secretValue.Should().Be(guidSecretValue); }).Wait(); + } + + // Assert. + resultOne.Should().NotBeNullOrEmpty(); + resultOne.Should().Be(plaintext); + resultTwo.Should().NotBeNullOrEmpty(); + resultTwo.Should().Be(plaintext); + } + } + } +} \ No newline at end of file From 2e4904e1302493b930fbed262f18916c77fef0f7 Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Fri, 19 Jun 2020 19:50:51 -0500 Subject: [PATCH 37/55] Interface segregation and extension around secret management. --- .../IManagedKeyCipher.cs | 235 +++++++++++++ .../ISecurityAppliance.cs | 246 +------------- .../Secrets/ISecretCertificateImporter.cs | 66 ++++ .../Secrets/ISecretExporter.cs | 107 ++++++ .../Secrets/ISecretImporter.cs | 13 + .../Secrets/ISecretKeyProducer.cs | 77 +++++ .../Secrets/ISecretManager.cs | 320 +----------------- .../Secrets/ISecretValueImporter.cs | 77 +++++ .../Secrets/ISecretVaultImporter.cs | 63 ++++ .../SoftwareSecurityModule.cs | 116 +++++++ .../SoftwareSecurityModuleTests.cs | 40 ++- 11 files changed, 785 insertions(+), 575 deletions(-) create mode 100644 src/RapidField.SolidInstruments.Cryptography/IManagedKeyCipher.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretCertificateImporter.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretExporter.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretImporter.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretKeyProducer.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretValueImporter.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretVaultImporter.cs diff --git a/src/RapidField.SolidInstruments.Cryptography/IManagedKeyCipher.cs b/src/RapidField.SolidInstruments.Cryptography/IManagedKeyCipher.cs new file mode 100644 index 00000000..dd784e52 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/IManagedKeyCipher.cs @@ -0,0 +1,235 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Cryptography.Symmetric; +using System; +using System.Security; + +namespace RapidField.SolidInstruments.Cryptography +{ + /// + /// Represents a symmetric key encryption facility that uses managed keys to perform cryptographic operations. + /// + public interface IManagedKeyCipher : IAsyncDisposable, IDisposable + { + /// + /// Decrypts the specified model using the master key. + /// + /// + /// The type of the model to decrypt. + /// + /// + /// The model to decrypt. + /// + /// + /// The decrypted model. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during decryption or deserialization. + /// + public TModel Decrypt(IEncryptedModel model) + where TModel : class, IModel; + + /// + /// Decrypts the specified model using the specified key. + /// + /// + /// The type of the model to decrypt. + /// + /// + /// The model to decrypt. + /// + /// + /// The name of the key to use when decrypting the model, or to use the master key. + /// + /// + /// The decrypted model. + /// + /// + /// is empty. + /// + /// + /// The secret store does not contain a key with the specified name. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during decryption or deserialization. + /// + public TModel Decrypt(IEncryptedModel model, String keyName) + where TModel : class, IModel; + + /// + /// Decrypts the specified Base-64 encoded ciphertext string using the master key. + /// + /// + /// The Base-64 encoded ciphertext string to decrypt. + /// + /// + /// The resulting plaintext string. + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during decryption. + /// + public String Decrypt(String ciphertext); + + /// + /// Decrypts the specified Base-64 encoded ciphertext string using the specified key. + /// + /// + /// The Base-64 encoded ciphertext string to decrypt. + /// + /// + /// The name of the key to use when decrypting the model, or to use the master key. + /// + /// + /// The resulting plaintext string. + /// + /// + /// is empty -or- is empty. + /// + /// + /// The secret store does not contain a key with the specified name. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during decryption. + /// + public String Decrypt(String ciphertext, String keyName); + + /// + /// Encrypts the specified model using the master key. + /// + /// + /// The type of the model to encrypt. + /// + /// + /// The model to encrypt. + /// + /// + /// The encrypted model. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during encryption or serialization. + /// + public IEncryptedModel Encrypt(TModel model) + where TModel : class, IModel; + + /// + /// Encrypts the specified model using the specified key. + /// + /// + /// The type of the model to encrypt. + /// + /// + /// The model to encrypt. + /// + /// + /// The name of the key to use when encrypting the model, or to use the master key. + /// + /// + /// The encrypted model. + /// + /// + /// is empty. + /// + /// + /// The secret store does not contain a key with the specified name. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during encryption or serialization. + /// + public IEncryptedModel Encrypt(TModel model, String keyName) + where TModel : class, IModel; + + /// + /// Encrypts the specified plaintext string using the master key. + /// + /// + /// The plaintext string to encrypt. + /// + /// + /// The resulting Base-64 encoded, encrypted string. + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during encryption. + /// + public String Encrypt(String plaintext); + + /// + /// Encrypts the specified plaintext string using the specified key. + /// + /// + /// The plaintext string to encrypt. + /// + /// + /// The name of the key to use when encrypting the plaintext string, or to use the master key. + /// + /// + /// The resulting Base-64 encoded, encrypted string. + /// + /// + /// is empty -or- is empty. + /// + /// + /// The secret store does not contain a key with the specified name. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during encryption. + /// + public String Encrypt(String plaintext, String keyName); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/ISecurityAppliance.cs b/src/RapidField.SolidInstruments.Cryptography/ISecurityAppliance.cs index 65152518..7b1ec775 100644 --- a/src/RapidField.SolidInstruments.Cryptography/ISecurityAppliance.cs +++ b/src/RapidField.SolidInstruments.Cryptography/ISecurityAppliance.cs @@ -2,259 +2,15 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= -using RapidField.SolidInstruments.Core; using RapidField.SolidInstruments.Cryptography.Secrets; -using RapidField.SolidInstruments.Cryptography.Symmetric; -using System; -using System.Security; namespace RapidField.SolidInstruments.Cryptography { /// /// Represents a centralized utility for performing cryptographic operations. /// - public interface ISecurityAppliance : IAsyncDisposable, IDisposable + public interface ISecurityAppliance : IManagedKeyCipher, ISecretCertificateImporter, ISecretKeyProducer { - /// - /// Decrypts the specified model using the master key. - /// - /// - /// The type of the model to decrypt. - /// - /// - /// The model to decrypt. - /// - /// - /// The decrypted model. - /// - /// - /// is . - /// - /// - /// The object is disposed. - /// - /// - /// An exception was raised during decryption or deserialization. - /// - public TModel Decrypt(IEncryptedModel model) - where TModel : class, IModel; - - /// - /// Decrypts the specified model using the specified key. - /// - /// - /// The type of the model to decrypt. - /// - /// - /// The model to decrypt. - /// - /// - /// The name of the key to use when decrypting the model, or to use the master key. - /// - /// - /// The decrypted model. - /// - /// - /// is empty. - /// - /// - /// The secret store does not contain a key with the specified name. - /// - /// - /// is -or- is . - /// - /// - /// The object is disposed. - /// - /// - /// An exception was raised during decryption or deserialization. - /// - public TModel Decrypt(IEncryptedModel model, String keyName) - where TModel : class, IModel; - - /// - /// Decrypts the specified Base-64 encoded ciphertext string using the master key. - /// - /// - /// The Base-64 encoded ciphertext string to decrypt. - /// - /// - /// The resulting plaintext string. - /// - /// - /// is empty. - /// - /// - /// is . - /// - /// - /// The object is disposed. - /// - /// - /// An exception was raised during decryption. - /// - public String Decrypt(String ciphertext); - - /// - /// Decrypts the specified Base-64 encoded ciphertext string using the specified key. - /// - /// - /// The Base-64 encoded ciphertext string to decrypt. - /// - /// - /// The name of the key to use when decrypting the model, or to use the master key. - /// - /// - /// The resulting plaintext string. - /// - /// - /// is empty -or- is empty. - /// - /// - /// The secret store does not contain a key with the specified name. - /// - /// - /// is -or- is . - /// - /// - /// The object is disposed. - /// - /// - /// An exception was raised during decryption. - /// - public String Decrypt(String ciphertext, String keyName); - - /// - /// Encrypts the specified model using the master key. - /// - /// - /// The type of the model to encrypt. - /// - /// - /// The model to encrypt. - /// - /// - /// The encrypted model. - /// - /// - /// is . - /// - /// - /// The object is disposed. - /// - /// - /// An exception was raised during encryption or serialization. - /// - public IEncryptedModel Encrypt(TModel model) - where TModel : class, IModel; - - /// - /// Encrypts the specified model using the specified key. - /// - /// - /// The type of the model to encrypt. - /// - /// - /// The model to encrypt. - /// - /// - /// The name of the key to use when encrypting the model, or to use the master key. - /// - /// - /// The encrypted model. - /// - /// - /// is empty. - /// - /// - /// The secret store does not contain a key with the specified name. - /// - /// - /// is -or- is . - /// - /// - /// The object is disposed. - /// - /// - /// An exception was raised during encryption or serialization. - /// - public IEncryptedModel Encrypt(TModel model, String keyName) - where TModel : class, IModel; - - /// - /// Encrypts the specified plaintext string using the master key. - /// - /// - /// The plaintext string to encrypt. - /// - /// - /// The resulting Base-64 encoded, encrypted string. - /// - /// - /// is empty. - /// - /// - /// is . - /// - /// - /// The object is disposed. - /// - /// - /// An exception was raised during encryption. - /// - public String Encrypt(String plaintext); - - /// - /// Encrypts the specified plaintext string using the specified key. - /// - /// - /// The plaintext string to encrypt. - /// - /// - /// The name of the key to use when encrypting the plaintext string, or to use the master key. - /// - /// - /// The resulting Base-64 encoded, encrypted string. - /// - /// - /// is empty -or- is empty. - /// - /// - /// The secret store does not contain a key with the specified name. - /// - /// - /// is -or- is . - /// - /// - /// The object is disposed. - /// - /// - /// An exception was raised during encryption. - /// - public String Encrypt(String plaintext, String keyName); - - /// - /// Generates a new and returns its assigned name. - /// - /// - /// The textual name assigned to the new secret. - /// - /// - /// The object is disposed. - /// - public String NewCascadingSymmetricKey(); - - /// - /// Generates a new and returns its assigned name. - /// - /// - /// The textual name assigned to the new secret. - /// - /// - /// The object is disposed. - /// - public String NewSymmetricKey(); - /// /// Gets the secret reading facility for the current . /// diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretCertificateImporter.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretCertificateImporter.cs new file mode 100644 index 00000000..bd0ab524 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretCertificateImporter.cs @@ -0,0 +1,66 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Security; +using System.Security.Cryptography.X509Certificates; + +namespace RapidField.SolidInstruments.Cryptography.Secrets +{ + /// + /// Represents an import facility for named certificates which are encrypted and pinned in memory at rest. + /// + public interface ISecretCertificateImporter : IAsyncDisposable, IDisposable + { + /// + /// Imports all valid certificates from the current user's personal certificate store. + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised while trying to access or read the specified store or one of the certificates contained therein. + /// + public void ImportStoreCertificates(); + + /// + /// Imports all valid certificates from the specified local certificate store. + /// + /// + /// The name of the store from which the certificates are imported. The default value is . + /// + /// + /// is not a valid store name. + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised while trying to access or read the specified store or one of the certificates contained therein. + /// + public void ImportStoreCertificates(StoreName storeName); + + /// + /// Imports all valid certificates from the specified local certificate store. + /// + /// + /// The name of the store from which the certificates are imported. The default value is . + /// + /// + /// The location of the store from which the certificates are imported. The default value is + /// . + /// + /// + /// is not a valid store name -or- is not a valid store + /// location. + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised while trying to access or read the specified store or one of the certificates contained therein. + /// + public void ImportStoreCertificates(StoreName storeName, StoreLocation storeLocation); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretExporter.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretExporter.cs new file mode 100644 index 00000000..00b0752b --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretExporter.cs @@ -0,0 +1,107 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using System; +using System.Threading.Tasks; + +namespace RapidField.SolidInstruments.Cryptography.Secrets +{ + /// + /// Represents an export facility for named secret values which are encrypted and pinned in memory at rest. + /// + public interface ISecretExporter : IAsyncDisposable, IDisposable + { + /// + /// Asynchronously exports the specified secret and encrypts it using the master key for the current + /// . + /// + /// + /// The textual name of the secret to export. + /// + /// + /// A task representing the asynchronous operation and containing the exported encrypted secret. + /// + /// + /// is empty. + /// + /// + /// The does not contain a secret with the specified name. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during encryption or serialization. + /// + public Task ExportEncryptedSecretAsync(String name); + + /// + /// Asynchronously exports the specified secret and encrypts it using the specified key. + /// + /// + /// The textual name of the secret to export. + /// + /// + /// The name of the secret associated with a key that is used to encrypt the exported secret. + /// + /// + /// A task representing the asynchronous operation and containing the exported encrypted secret. + /// + /// + /// is empty -or- is empty. + /// + /// + /// The does not contain a secret with the specified name -or- the + /// does not contain a key with the specified name. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during encryption or serialization. + /// + public Task ExportEncryptedSecretAsync(String name, String keyName); + + /// + /// Asynchronously exports the master key for the in plaintext form. + /// + /// + /// A task representing the asynchronous operation and containing the exported master key. + /// + /// + /// The object is disposed. + /// + public Task ExportMasterKeyAsync(); + + /// + /// Asynchronously exports the specified secret in plaintext form. + /// + /// + /// The textual name of the secret to export. + /// + /// + /// A task representing the asynchronous operation and containing the exported plaintext secret. + /// + /// + /// is empty. + /// + /// + /// The does not contain a secret with the specified name. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public Task ExportSecretAsync(String name); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretImporter.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretImporter.cs new file mode 100644 index 00000000..b2c7340f --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretImporter.cs @@ -0,0 +1,13 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +namespace RapidField.SolidInstruments.Cryptography.Secrets +{ + /// + /// Represents an import facility for named secrets which are encrypted and pinned in memory at rest. + /// + public interface ISecretImporter : ISecretCertificateImporter, ISecretValueImporter, ISecretVaultImporter + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretKeyProducer.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretKeyProducer.cs new file mode 100644 index 00000000..eeb70683 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretKeyProducer.cs @@ -0,0 +1,77 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using System; + +namespace RapidField.SolidInstruments.Cryptography.Secrets +{ + /// + /// Represents a production facility for named secret keys which are encrypted and pinned in memory at rest. + /// + public interface ISecretKeyProducer : IAsyncDisposable, IDisposable + { + /// + /// Generates a new and returns its assigned name. + /// + /// + /// The textual name assigned to the new secret. + /// + /// + /// The object is disposed. + /// + public String NewCascadingSymmetricKey(); + + /// + /// Generates a new with the specified name. + /// + /// + /// The textual name to assign to the new secret. + /// + /// + /// is empty. + /// + /// + /// The already contains a secret with the specified name. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public void NewCascadingSymmetricKey(String name); + + /// + /// Generates a new and returns its assigned name. + /// + /// + /// The textual name assigned to the new secret. + /// + /// + /// The object is disposed. + /// + public String NewSymmetricKey(); + + /// + /// Generates a new with the specified name. + /// + /// + /// The textual name to assign to the new secret. + /// + /// + /// is empty. + /// + /// + /// The already contains a secret with the specified name. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public void NewSymmetricKey(String name); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretManager.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretManager.cs index 5362f475..f32919ce 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretManager.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretManager.cs @@ -2,330 +2,12 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= -using RapidField.SolidInstruments.Core; -using System; -using System.Security; -using System.Security.Cryptography.X509Certificates; -using System.Threading.Tasks; - namespace RapidField.SolidInstruments.Cryptography.Secrets { /// /// Represents a management facility for named secret values which are encrypted and pinned in memory at rest. /// - public interface ISecretManager : ISecretStore + public interface ISecretManager : ISecretExporter, ISecretImporter, ISecretKeyProducer, ISecretStore { - /// - /// Asynchronously exports the specified secret and encrypts it using the master key for the current - /// . - /// - /// - /// The textual name of the secret to export. - /// - /// - /// A task representing the asynchronous operation and containing the exported encrypted secret. - /// - /// - /// is empty. - /// - /// - /// The does not contain a secret with the specified name. - /// - /// - /// is . - /// - /// - /// The object is disposed. - /// - /// - /// An exception was raised during encryption or serialization. - /// - public Task ExportEncryptedSecretAsync(String name); - - /// - /// Asynchronously exports the specified secret and encrypts it using the specified key. - /// - /// - /// The textual name of the secret to export. - /// - /// - /// The name of the secret associated with a key that is used to encrypt the exported secret. - /// - /// - /// A task representing the asynchronous operation and containing the exported encrypted secret. - /// - /// - /// is empty -or- is empty. - /// - /// - /// The does not contain a secret with the specified name -or- the - /// does not contain a key with the specified name. - /// - /// - /// is -or- is . - /// - /// - /// The object is disposed. - /// - /// - /// An exception was raised during encryption or serialization. - /// - public Task ExportEncryptedSecretAsync(String name, String keyName); - - /// - /// Asynchronously exports the master key for the in plaintext form. - /// - /// - /// A task representing the asynchronous operation and containing the exported master key. - /// - /// - /// The object is disposed. - /// - public Task ExportMasterKeyAsync(); - - /// - /// Asynchronously exports the specified secret in plaintext form. - /// - /// - /// The textual name of the secret to export. - /// - /// - /// A task representing the asynchronous operation and containing the exported plaintext secret. - /// - /// - /// is empty. - /// - /// - /// The does not contain a secret with the specified name. - /// - /// - /// is . - /// - /// - /// The object is disposed. - /// - public Task ExportSecretAsync(String name); - - /// - /// Decrypts the specified secret using the master key of the current and imports it. - /// - /// - /// When using this method, note that the master key of the current must match the key that - /// was used when exporting the secret. If the exporting vault is not the current , the master - /// keys will need to have been synchronized beforehand. - /// - /// - /// The encrypted secret to import. - /// - /// - /// is . - /// - /// - /// The object is disposed. - /// - /// - /// An exception was raised during decryption or deserialization. - /// - public void ImportEncryptedSecret(IEncryptedExportedSecret secret); - - /// - /// Decrypts the specified secret using the specified key and imports it. - /// - /// - /// The encrypted secret to import. - /// - /// - /// The name of the secret associated with a key that was used to encrypt the exported secret. - /// - /// - /// is empty. - /// - /// - /// The does not contain a key with the specified name. - /// - /// - /// is -or- is . - /// - /// - /// The object is disposed. - /// - /// - /// An exception was raised during decryption or deserialization. - /// - public void ImportEncryptedSecret(IEncryptedExportedSecret secret, String keyName); - - /// - /// Decrypts the specified secret vault using the master key of the current and imports it. - /// - /// - /// When using this method, note that the master key of the current must match the key that - /// was used when exporting the secret vault. If the exporting vault is not the current , the - /// master keys will need to have been synchronized beforehand. - /// - /// - /// The encrypted secret vault to import. - /// - /// - /// is . - /// - /// - /// The object is disposed. - /// - /// - /// An exception was raised during decryption or deserialization. - /// - public void ImportEncryptedSecretVault(IEncryptedExportedSecretVault secretVault); - - /// - /// Decrypts the specified secret vault using the specified key and imports it. - /// - /// - /// The encrypted secret vault to import. - /// - /// - /// The name of the secret associated with a key that was used to encrypt the exported secret vault. - /// - /// - /// is empty. - /// - /// - /// The does not contain a key with the specified name. - /// - /// - /// is -or- is . - /// - /// - /// The object is disposed. - /// - /// - /// An exception was raised during decryption or deserialization. - /// - public void ImportEncryptedSecretVault(IEncryptedExportedSecretVault secretVault, String keyName); - - /// - /// Imports the specified secret in plaintext form. - /// - /// - /// The plaintext secret to import. - /// - /// - /// is . - /// - /// - /// The object is disposed. - /// - public void ImportSecret(IExportedSecret secret); - - /// - /// Imports all valid certificates from the current user's personal certificate store. - /// - /// - /// The object is disposed. - /// - /// - /// An exception was raised while trying to access or read the specified store or one of the certificates contained therein. - /// - public void ImportStoreCertificates(); - - /// - /// Imports all valid certificates from the specified local certificate store. - /// - /// - /// The name of the store from which the certificates are imported. The default value is . - /// - /// - /// is not a valid store name. - /// - /// - /// The object is disposed. - /// - /// - /// An exception was raised while trying to access or read the specified store or one of the certificates contained therein. - /// - public void ImportStoreCertificates(StoreName storeName); - - /// - /// Imports all valid certificates from the specified local certificate store. - /// - /// - /// The name of the store from which the certificates are imported. The default value is . - /// - /// - /// The location of the store from which the certificates are imported. The default value is - /// . - /// - /// - /// is not a valid store name -or- is not a valid store - /// location. - /// - /// - /// The object is disposed. - /// - /// - /// An exception was raised while trying to access or read the specified store or one of the certificates contained therein. - /// - public void ImportStoreCertificates(StoreName storeName, StoreLocation storeLocation); - - /// - /// Generates a new and returns its assigned name. - /// - /// - /// The textual name assigned to the new secret. - /// - /// - /// The object is disposed. - /// - public String NewCascadingSymmetricKey(); - - /// - /// Generates a new with the specified name. - /// - /// - /// The textual name to assign to the new secret. - /// - /// - /// is empty. - /// - /// - /// The already contains a secret with the specified name. - /// - /// - /// is . - /// - /// - /// The object is disposed. - /// - public void NewCascadingSymmetricKey(String name); - - /// - /// Generates a new and returns its assigned name. - /// - /// - /// The textual name assigned to the new secret. - /// - /// - /// The object is disposed. - /// - public String NewSymmetricKey(); - - /// - /// Generates a new with the specified name. - /// - /// - /// The textual name to assign to the new secret. - /// - /// - /// is empty. - /// - /// - /// The already contains a secret with the specified name. - /// - /// - /// is . - /// - /// - /// The object is disposed. - /// - public void NewSymmetricKey(String name); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretValueImporter.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretValueImporter.cs new file mode 100644 index 00000000..0a3d05b0 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretValueImporter.cs @@ -0,0 +1,77 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using System; + +namespace RapidField.SolidInstruments.Cryptography.Secrets +{ + /// + /// Represents an import facility for named secret values which are encrypted and pinned in memory at rest. + /// + public interface ISecretValueImporter : IAsyncDisposable, IDisposable + { + /// + /// Decrypts the specified secret using the master key of the current and imports it. + /// + /// + /// When using this method, note that the master key of the current must match the key that + /// was used when exporting the secret. If the exporting vault is not the current , the master + /// keys will need to have been synchronized beforehand. + /// + /// + /// The encrypted secret to import. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during decryption or deserialization. + /// + public void ImportEncryptedSecret(IEncryptedExportedSecret secret); + + /// + /// Decrypts the specified secret using the specified key and imports it. + /// + /// + /// The encrypted secret to import. + /// + /// + /// The name of the secret associated with a key that was used to encrypt the exported secret. + /// + /// + /// is empty. + /// + /// + /// The does not contain a key with the specified name. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during decryption or deserialization. + /// + public void ImportEncryptedSecret(IEncryptedExportedSecret secret, String keyName); + + /// + /// Imports the specified secret in plaintext form. + /// + /// + /// The plaintext secret to import. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public void ImportSecret(IExportedSecret secret); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretVaultImporter.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretVaultImporter.cs new file mode 100644 index 00000000..0bf5b365 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/ISecretVaultImporter.cs @@ -0,0 +1,63 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using System; + +namespace RapidField.SolidInstruments.Cryptography.Secrets +{ + /// + /// Represents an import facility for exported secret vaults. + /// + public interface ISecretVaultImporter : IAsyncDisposable, IDisposable + { + /// + /// Decrypts the specified secret vault using the master key of the current and imports it. + /// + /// + /// When using this method, note that the master key of the current must match the key that + /// was used when exporting the secret vault. If the exporting vault is not the current , the + /// master keys will need to have been synchronized beforehand. + /// + /// + /// The encrypted secret vault to import. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during decryption or deserialization. + /// + public void ImportEncryptedSecretVault(IEncryptedExportedSecretVault secretVault); + + /// + /// Decrypts the specified secret vault using the specified key and imports it. + /// + /// + /// The encrypted secret vault to import. + /// + /// + /// The name of the secret associated with a key that was used to encrypt the exported secret vault. + /// + /// + /// is empty. + /// + /// + /// The does not contain a key with the specified name. + /// + /// + /// is -or- is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised during decryption or deserialization. + /// + public void ImportEncryptedSecretVault(IEncryptedExportedSecretVault secretVault, String keyName); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/SoftwareSecurityModule.cs b/src/RapidField.SolidInstruments.Cryptography/SoftwareSecurityModule.cs index 5ad522f7..73646315 100644 --- a/src/RapidField.SolidInstruments.Cryptography/SoftwareSecurityModule.cs +++ b/src/RapidField.SolidInstruments.Cryptography/SoftwareSecurityModule.cs @@ -11,6 +11,7 @@ using System.Diagnostics; using System.IO; using System.Security; +using System.Security.Cryptography.X509Certificates; using SystemTimeoutException = System.TimeoutException; namespace RapidField.SolidInstruments.Cryptography @@ -745,6 +746,71 @@ public String Encrypt(String plaintext, String keyName) } } + /// + /// Imports all valid certificates from the current user's personal certificate store. + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised while trying to access or read the specified store or one of the certificates contained therein. + /// + public void ImportStoreCertificates() + { + RejectIfDisposed(); + InMemoryStore.ImportStoreCertificates(); + PersistInMemoryStore(); + } + + /// + /// Imports all valid certificates from the specified local certificate store. + /// + /// + /// The name of the store from which the certificates are imported. The default value is . + /// + /// + /// is not a valid store name. + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised while trying to access or read the specified store or one of the certificates contained therein. + /// + public void ImportStoreCertificates(StoreName storeName) + { + RejectIfDisposed(); + InMemoryStore.ImportStoreCertificates(storeName); + PersistInMemoryStore(); + } + + /// + /// Imports all valid certificates from the specified local certificate store. + /// + /// + /// The name of the store from which the certificates are imported. The default value is . + /// + /// + /// The location of the store from which the certificates are imported. The default value is + /// . + /// + /// + /// is not a valid store name -or- is not a valid store + /// location. + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised while trying to access or read the specified store or one of the certificates contained therein. + /// + public void ImportStoreCertificates(StoreName storeName, StoreLocation storeLocation) + { + RejectIfDisposed(); + InMemoryStore.ImportStoreCertificates(storeName, storeLocation); + PersistInMemoryStore(); + } + /// /// Generates a new and returns its assigned name. /// @@ -762,6 +828,31 @@ public String NewCascadingSymmetricKey() return keyName; } + /// + /// Generates a new with the specified name. + /// + /// + /// The textual name to assign to the new secret. + /// + /// + /// is empty. + /// + /// + /// The software security module already contains a secret with the specified name. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public void NewCascadingSymmetricKey(String name) + { + RejectIfDisposed(); + InMemoryStore.NewCascadingSymmetricKey(name); + PersistInMemoryStore(); + } + /// /// Generates a new and returns its assigned name. /// @@ -779,6 +870,31 @@ public String NewSymmetricKey() return keyName; } + /// + /// Generates a new with the specified name. + /// + /// + /// The textual name to assign to the new secret. + /// + /// + /// is empty. + /// + /// + /// The software security module already contains a secret with the specified name. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public void NewSymmetricKey(String name) + { + RejectIfDisposed(); + InMemoryStore.NewSymmetricKey(name); + PersistInMemoryStore(); + } + /// /// Releases all resources consumed by the current . /// diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/SoftwareSecurityModuleTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/SoftwareSecurityModuleTests.cs index b5daa1d4..7f59228e 100644 --- a/test/RapidField.SolidInstruments.Cryptography.UnitTests/SoftwareSecurityModuleTests.cs +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/SoftwareSecurityModuleTests.cs @@ -6,6 +6,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using RapidField.SolidInstruments.Cryptography.Secrets; using System; +using System.IO; using System.Security; using System.Security.Cryptography; @@ -165,27 +166,30 @@ public void Encrypt_ShouldNotBeReversible_ForStringPlaintext_UsingMultipleModule [TestMethod] public void FunctionalLifeSpanTest_ShouldProduceDesiredResults() { + // Arrange. + var plaintext = "foo"; + var guidSecretValue = Guid.NewGuid(); + var guidSecretName = "GuidSecret"; + var ciphertextOne = (String)null; + var ciphertextTwo = (String)null; + var filePath = (String)null; + var keyName = (String)null; + var resultOne = (String)null; + var resultTwo = (String)null; + using (var masterPassword = Password.NewStrongPassword()) { - // Arrange. - var plaintext = "foo"; - var guidSecretValue = Guid.NewGuid(); - var guidSecretName = "GuidSecret"; - var ciphertextOne = (String)null; - var ciphertextTwo = (String)null; - var keyName = (String)null; - var resultOne = (String)null; - var resultTwo = (String)null; - using (var target = new SoftwareSecurityModule(masterPassword, false)) { // Act. + filePath = target.PersistenceVehicle.FilePath; keyName = target.NewSymmetricKey(); ciphertextOne = target.Encrypt(plaintext); ciphertextTwo = target.Encrypt(plaintext, keyName); target.AddOrUpdate(guidSecretName, guidSecretValue); // Assert. + filePath.Should().NotBeNullOrEmpty(); target.SecretCount.Should().Be(3); target.SecretNames.Should().Contain(keyName); target.SecretNames.Should().Contain(guidSecretName); @@ -194,7 +198,10 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults() ciphertextOne.Should().NotBe(ciphertextTwo); } - using (var target = new SoftwareSecurityModule(masterPassword, true)) + // Assert. + File.Exists(filePath).Should().BeTrue(); + + using (var target = new SoftwareSecurityModule(masterPassword, false)) { // Assert. target.SecretCount.Should().Be(3); @@ -204,17 +211,28 @@ public void FunctionalLifeSpanTest_ShouldProduceDesiredResults() // Act. resultOne = target.Decrypt(ciphertextOne); resultTwo = target.Decrypt(ciphertextTwo, keyName); + target.ImportStoreCertificates(); // Assert. target.SecretReader.ReadAsync(guidSecretName, (Guid secretValue) => { secretValue.Should().Be(guidSecretValue); }).Wait(); } // Assert. + File.Exists(filePath).Should().BeTrue(); resultOne.Should().NotBeNullOrEmpty(); resultOne.Should().Be(plaintext); resultTwo.Should().NotBeNullOrEmpty(); resultTwo.Should().Be(plaintext); + + using (var target = new SoftwareSecurityModule(masterPassword, true)) + { + // Assert. + target.SecretCount.Should().BeGreaterOrEqualTo(3); + } } + + // Assert. + File.Exists(filePath).Should().BeFalse(); } } } \ No newline at end of file From 91725c67d83a4705edf687cd12f95adf2e676dce Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Fri, 19 Jun 2020 21:42:43 -0500 Subject: [PATCH 38/55] Upgrading NuGet packages. --- ...truments.Example.ServiceApplication.csproj | 4 +- ...dInstruments.Example.WebApplication.csproj | 6 +- .../RapidField.SolidInstruments.Core.csproj | 2 +- ...Field.SolidInstruments.Cryptography.csproj | 3 + .../Symmetric/SymmetricKey.cs | 120 +++++++++++++++++- ...truments.DataAccess.EntityFramework.csproj | 6 +- ...truments.InversionOfControl.Autofac.csproj | 4 +- ...nts.InversionOfControl.DotNetNative.csproj | 6 +- ...SolidInstruments.InversionOfControl.csproj | 6 +- ...struments.Messaging.AzureServiceBus.csproj | 2 +- ...SolidInstruments.Messaging.RabbitMq.csproj | 2 +- ....SolidInstruments.ObjectComposition.csproj | 2 +- ...idInstruments.Collections.UnitTests.csproj | 6 +- ....SolidInstruments.Command.UnitTests.csproj | 8 +- ...eld.SolidInstruments.Core.UnitTests.csproj | 8 +- ...dInstruments.Cryptography.UnitTests.csproj | 6 +- ...ataAccess.EntityFramework.UnitTests.csproj | 6 +- ...lidInstruments.DataAccess.UnitTests.csproj | 6 +- ...nstruments.EventAuthoring.UnitTests.csproj | 6 +- ...nversionOfControl.Autofac.UnitTests.csproj | 8 +- ...ionOfControl.DotNetNative.UnitTests.csproj | 8 +- ...uments.InversionOfControl.UnitTests.csproj | 8 +- ...idInstruments.Mathematics.UnitTests.csproj | 6 +- ...uments.Messaging.InMemory.UnitTests.csproj | 6 +- ...olidInstruments.Messaging.UnitTests.csproj | 6 +- ...ruments.ObjectComposition.UnitTests.csproj | 8 +- ...Instruments.Serialization.UnitTests.csproj | 6 +- ...truments.SignalProcessing.UnitTests.csproj | 6 +- ...dInstruments.TextEncoding.UnitTests.csproj | 6 +- 29 files changed, 193 insertions(+), 84 deletions(-) diff --git a/example/RapidField.SolidInstruments.Example.ServiceApplication/RapidField.SolidInstruments.Example.ServiceApplication.csproj b/example/RapidField.SolidInstruments.Example.ServiceApplication/RapidField.SolidInstruments.Example.ServiceApplication.csproj index 1247820f..971218e3 100644 --- a/example/RapidField.SolidInstruments.Example.ServiceApplication/RapidField.SolidInstruments.Example.ServiceApplication.csproj +++ b/example/RapidField.SolidInstruments.Example.ServiceApplication/RapidField.SolidInstruments.Example.ServiceApplication.csproj @@ -37,8 +37,8 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - + + diff --git a/example/RapidField.SolidInstruments.Example.WebApplication/RapidField.SolidInstruments.Example.WebApplication.csproj b/example/RapidField.SolidInstruments.Example.WebApplication/RapidField.SolidInstruments.Example.WebApplication.csproj index 8d4e7c93..cd451e83 100644 --- a/example/RapidField.SolidInstruments.Example.WebApplication/RapidField.SolidInstruments.Example.WebApplication.csproj +++ b/example/RapidField.SolidInstruments.Example.WebApplication/RapidField.SolidInstruments.Example.WebApplication.csproj @@ -17,9 +17,9 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - + + + diff --git a/src/RapidField.SolidInstruments.Core/RapidField.SolidInstruments.Core.csproj b/src/RapidField.SolidInstruments.Core/RapidField.SolidInstruments.Core.csproj index 26193e29..04d268e7 100644 --- a/src/RapidField.SolidInstruments.Core/RapidField.SolidInstruments.Core.csproj +++ b/src/RapidField.SolidInstruments.Core/RapidField.SolidInstruments.Core.csproj @@ -38,6 +38,6 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - + \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/RapidField.SolidInstruments.Cryptography.csproj b/src/RapidField.SolidInstruments.Cryptography/RapidField.SolidInstruments.Cryptography.csproj index 4478a787..889c1cf9 100644 --- a/src/RapidField.SolidInstruments.Cryptography/RapidField.SolidInstruments.Cryptography.csproj +++ b/src/RapidField.SolidInstruments.Cryptography/RapidField.SolidInstruments.Cryptography.csproj @@ -36,6 +36,9 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in + + + diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKey.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKey.cs index 39bae26a..9aa32a9a 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKey.cs @@ -418,6 +418,39 @@ public ISecureMemory ToSecureMemory() } } + /// + /// Generates private key source bytes using the specified, arbitrary length bit field. + /// + /// + /// A non-empty byte array from which the symmetric key is derived. + /// + /// + /// The desired number of key source bytes. + /// + /// + /// The key source bytes. + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// is less than or equal to zero. + /// + [DebuggerHidden] + internal static PinnedMemory DeriveKeySourceBytesFromKeyMaterial(Byte[] keyMaterial, Int32 keySourceLengthInBytes) + { + var hashingProcessor = new HashingProcessor(RandomnessProvider); + + using (var saltBytes = new ReadOnlyPinnedMemory(hashingProcessor.CalculateHash(keyMaterial, PasswordSaltHashingAlgorithm))) + { + var pbkdf2Provider = new Rfc2898DeriveBytes(keyMaterial, saltBytes, Pbkdf2MinimumIterationCount); + return new PinnedMemory(pbkdf2Provider.GetBytes(keySourceLengthInBytes.RejectIf().IsLessThanOrEqualTo(0, nameof(keySourceLengthInBytes)))); + } + } + /// /// Generates private key source bytes using the specified password. /// @@ -445,16 +478,89 @@ public ISecureMemory ToSecureMemory() [DebuggerHidden] internal static PinnedMemory DeriveKeySourceBytesFromPassword(IPassword password, Int32 keySourceLengthInBytes) { - password.RejectIf().IsNull(nameof(password)).OrIf(argument => argument.GetCharacterLength() < MinimumPasswordLength, nameof(password), $"The specified password is shorter than {MinimumPasswordLength} characters."); + _ = password.RejectIf().IsNull(nameof(password)).OrIf(argument => argument.GetCharacterLength() < MinimumPasswordLength, nameof(password), $"The specified password is shorter than {MinimumPasswordLength} characters."); + using (var passwordBytes = new ReadOnlyPinnedMemory(Password.GetPasswordPlaintextBytes(password))) { - var hashingProcessor = new HashingProcessor(RandomnessProvider); + return DeriveKeySourceBytesFromKeyMaterial(passwordBytes, keySourceLengthInBytes); + } + } - using (var saltBytes = new ReadOnlyPinnedMemory(hashingProcessor.CalculateHash(passwordBytes, PasswordSaltHashingAlgorithm))) - { - var pbkdf2Provider = new Rfc2898DeriveBytes(passwordBytes, saltBytes, Pbkdf2MinimumIterationCount); - return new PinnedMemory(pbkdf2Provider.GetBytes(keySourceLengthInBytes.RejectIf().IsLessThanOrEqualTo(0, nameof(keySourceLengthInBytes)))); - } + /// + /// Derives a new from the specified, arbitrary length bit field. + /// + /// + /// A non-empty byte array from which the symmetric key is derived. + /// + /// + /// A new . + /// + /// + /// is empty. + /// + /// + /// is . + /// + [DebuggerHidden] + internal static SymmetricKey FromKeyMaterial(Byte[] keyMaterial) => FromKeyMaterial(keyMaterial, DefaultAlgorithm); + + /// + /// Derives a new from the specified, arbitrary length bit field. + /// + /// + /// A non-empty byte array from which the symmetric key is derived. + /// + /// + /// The symmetric-key algorithm that the generated key is derived to interoperate with. The default value is + /// . + /// + /// + /// A new . + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + [DebuggerHidden] + internal static SymmetricKey FromKeyMaterial(Byte[] keyMaterial, SymmetricAlgorithmSpecification algorithm) => FromKeyMaterial(keyMaterial, algorithm, DefaultDerivationMode); + + /// + /// Derives a new from the specified, arbitrary length bit field. + /// + /// + /// A non-empty byte array from which the symmetric key is derived. + /// + /// + /// The symmetric-key algorithm that the generated key is derived to interoperate with. The default value is + /// . + /// + /// + /// The mode used to derive the generated key. The default value is . + /// + /// + /// A new . + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// is equal to -or- + /// is equal to . + /// + [DebuggerHidden] + internal static SymmetricKey FromKeyMaterial(Byte[] keyMaterial, SymmetricAlgorithmSpecification algorithm, SymmetricKeyDerivationMode derivationMode) + { + using (var keySource = DeriveKeySourceBytesFromKeyMaterial(keyMaterial, KeySourceLengthInBytes)) + { + return New(algorithm.RejectIf().IsEqualToValue(SymmetricAlgorithmSpecification.Unspecified, nameof(algorithm)), derivationMode.RejectIf().IsEqualToValue(SymmetricKeyDerivationMode.Unspecified, nameof(derivationMode)), keySource); } } diff --git a/src/RapidField.SolidInstruments.DataAccess.EntityFramework/RapidField.SolidInstruments.DataAccess.EntityFramework.csproj b/src/RapidField.SolidInstruments.DataAccess.EntityFramework/RapidField.SolidInstruments.DataAccess.EntityFramework.csproj index 25273cf6..0881bea7 100644 --- a/src/RapidField.SolidInstruments.DataAccess.EntityFramework/RapidField.SolidInstruments.DataAccess.EntityFramework.csproj +++ b/src/RapidField.SolidInstruments.DataAccess.EntityFramework/RapidField.SolidInstruments.DataAccess.EntityFramework.csproj @@ -37,9 +37,9 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - + + + diff --git a/src/RapidField.SolidInstruments.InversionOfControl.Autofac/RapidField.SolidInstruments.InversionOfControl.Autofac.csproj b/src/RapidField.SolidInstruments.InversionOfControl.Autofac/RapidField.SolidInstruments.InversionOfControl.Autofac.csproj index db17845b..dfd359f1 100644 --- a/src/RapidField.SolidInstruments.InversionOfControl.Autofac/RapidField.SolidInstruments.InversionOfControl.Autofac.csproj +++ b/src/RapidField.SolidInstruments.InversionOfControl.Autofac/RapidField.SolidInstruments.InversionOfControl.Autofac.csproj @@ -37,9 +37,9 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - + - + diff --git a/src/RapidField.SolidInstruments.InversionOfControl.DotNetNative/RapidField.SolidInstruments.InversionOfControl.DotNetNative.csproj b/src/RapidField.SolidInstruments.InversionOfControl.DotNetNative/RapidField.SolidInstruments.InversionOfControl.DotNetNative.csproj index 185ea1f6..a47aae29 100644 --- a/src/RapidField.SolidInstruments.InversionOfControl.DotNetNative/RapidField.SolidInstruments.InversionOfControl.DotNetNative.csproj +++ b/src/RapidField.SolidInstruments.InversionOfControl.DotNetNative/RapidField.SolidInstruments.InversionOfControl.DotNetNative.csproj @@ -37,9 +37,9 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - + + + diff --git a/src/RapidField.SolidInstruments.InversionOfControl/RapidField.SolidInstruments.InversionOfControl.csproj b/src/RapidField.SolidInstruments.InversionOfControl/RapidField.SolidInstruments.InversionOfControl.csproj index 022409fe..dcceaac4 100644 --- a/src/RapidField.SolidInstruments.InversionOfControl/RapidField.SolidInstruments.InversionOfControl.csproj +++ b/src/RapidField.SolidInstruments.InversionOfControl/RapidField.SolidInstruments.InversionOfControl.csproj @@ -37,9 +37,9 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - + + + diff --git a/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/RapidField.SolidInstruments.Messaging.AzureServiceBus.csproj b/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/RapidField.SolidInstruments.Messaging.AzureServiceBus.csproj index 9d7ae17b..a78e9486 100644 --- a/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/RapidField.SolidInstruments.Messaging.AzureServiceBus.csproj +++ b/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/RapidField.SolidInstruments.Messaging.AzureServiceBus.csproj @@ -37,7 +37,7 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - + diff --git a/src/RapidField.SolidInstruments.Messaging.RabbitMq/RapidField.SolidInstruments.Messaging.RabbitMq.csproj b/src/RapidField.SolidInstruments.Messaging.RabbitMq/RapidField.SolidInstruments.Messaging.RabbitMq.csproj index 5f0413a7..cbf098aa 100644 --- a/src/RapidField.SolidInstruments.Messaging.RabbitMq/RapidField.SolidInstruments.Messaging.RabbitMq.csproj +++ b/src/RapidField.SolidInstruments.Messaging.RabbitMq/RapidField.SolidInstruments.Messaging.RabbitMq.csproj @@ -37,7 +37,7 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - + diff --git a/src/RapidField.SolidInstruments.ObjectComposition/RapidField.SolidInstruments.ObjectComposition.csproj b/src/RapidField.SolidInstruments.ObjectComposition/RapidField.SolidInstruments.ObjectComposition.csproj index 1f99d6d5..931e4ef9 100644 --- a/src/RapidField.SolidInstruments.ObjectComposition/RapidField.SolidInstruments.ObjectComposition.csproj +++ b/src/RapidField.SolidInstruments.ObjectComposition/RapidField.SolidInstruments.ObjectComposition.csproj @@ -37,7 +37,7 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - + diff --git a/test/RapidField.SolidInstruments.Collections.UnitTests/RapidField.SolidInstruments.Collections.UnitTests.csproj b/test/RapidField.SolidInstruments.Collections.UnitTests/RapidField.SolidInstruments.Collections.UnitTests.csproj index 00fff528..7e32da60 100644 --- a/test/RapidField.SolidInstruments.Collections.UnitTests/RapidField.SolidInstruments.Collections.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.Collections.UnitTests/RapidField.SolidInstruments.Collections.UnitTests.csproj @@ -19,9 +19,9 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - + + + diff --git a/test/RapidField.SolidInstruments.Command.UnitTests/RapidField.SolidInstruments.Command.UnitTests.csproj b/test/RapidField.SolidInstruments.Command.UnitTests/RapidField.SolidInstruments.Command.UnitTests.csproj index 0181eb74..e9411fa4 100644 --- a/test/RapidField.SolidInstruments.Command.UnitTests/RapidField.SolidInstruments.Command.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.Command.UnitTests/RapidField.SolidInstruments.Command.UnitTests.csproj @@ -18,11 +18,11 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - + - - - + + + diff --git a/test/RapidField.SolidInstruments.Core.UnitTests/RapidField.SolidInstruments.Core.UnitTests.csproj b/test/RapidField.SolidInstruments.Core.UnitTests/RapidField.SolidInstruments.Core.UnitTests.csproj index de024ef2..b1fe647f 100644 --- a/test/RapidField.SolidInstruments.Core.UnitTests/RapidField.SolidInstruments.Core.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.Core.UnitTests/RapidField.SolidInstruments.Core.UnitTests.csproj @@ -19,10 +19,10 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - - + + + + diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/RapidField.SolidInstruments.Cryptography.UnitTests.csproj b/test/RapidField.SolidInstruments.Cryptography.UnitTests/RapidField.SolidInstruments.Cryptography.UnitTests.csproj index f45dbe68..df887ac5 100644 --- a/test/RapidField.SolidInstruments.Cryptography.UnitTests/RapidField.SolidInstruments.Cryptography.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/RapidField.SolidInstruments.Cryptography.UnitTests.csproj @@ -35,10 +35,10 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - + - - + + diff --git a/test/RapidField.SolidInstruments.DataAccess.EntityFramework.UnitTests/RapidField.SolidInstruments.DataAccess.EntityFramework.UnitTests.csproj b/test/RapidField.SolidInstruments.DataAccess.EntityFramework.UnitTests/RapidField.SolidInstruments.DataAccess.EntityFramework.UnitTests.csproj index 87f98c73..4f2c5401 100644 --- a/test/RapidField.SolidInstruments.DataAccess.EntityFramework.UnitTests/RapidField.SolidInstruments.DataAccess.EntityFramework.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.DataAccess.EntityFramework.UnitTests/RapidField.SolidInstruments.DataAccess.EntityFramework.UnitTests.csproj @@ -19,9 +19,9 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - + + + diff --git a/test/RapidField.SolidInstruments.DataAccess.UnitTests/RapidField.SolidInstruments.DataAccess.UnitTests.csproj b/test/RapidField.SolidInstruments.DataAccess.UnitTests/RapidField.SolidInstruments.DataAccess.UnitTests.csproj index b5fafba6..3232fa4f 100644 --- a/test/RapidField.SolidInstruments.DataAccess.UnitTests/RapidField.SolidInstruments.DataAccess.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.DataAccess.UnitTests/RapidField.SolidInstruments.DataAccess.UnitTests.csproj @@ -19,9 +19,9 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - + + + diff --git a/test/RapidField.SolidInstruments.EventAuthoring.UnitTests/RapidField.SolidInstruments.EventAuthoring.UnitTests.csproj b/test/RapidField.SolidInstruments.EventAuthoring.UnitTests/RapidField.SolidInstruments.EventAuthoring.UnitTests.csproj index 3c1dff2c..b73ff1c7 100644 --- a/test/RapidField.SolidInstruments.EventAuthoring.UnitTests/RapidField.SolidInstruments.EventAuthoring.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.EventAuthoring.UnitTests/RapidField.SolidInstruments.EventAuthoring.UnitTests.csproj @@ -19,9 +19,9 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - + + + diff --git a/test/RapidField.SolidInstruments.InversionOfControl.Autofac.UnitTests/RapidField.SolidInstruments.InversionOfControl.Autofac.UnitTests.csproj b/test/RapidField.SolidInstruments.InversionOfControl.Autofac.UnitTests/RapidField.SolidInstruments.InversionOfControl.Autofac.UnitTests.csproj index a5ce77c6..ec57bf25 100644 --- a/test/RapidField.SolidInstruments.InversionOfControl.Autofac.UnitTests/RapidField.SolidInstruments.InversionOfControl.Autofac.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.InversionOfControl.Autofac.UnitTests/RapidField.SolidInstruments.InversionOfControl.Autofac.UnitTests.csproj @@ -19,10 +19,10 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - - + + + + diff --git a/test/RapidField.SolidInstruments.InversionOfControl.DotNetNative.UnitTests/RapidField.SolidInstruments.InversionOfControl.DotNetNative.UnitTests.csproj b/test/RapidField.SolidInstruments.InversionOfControl.DotNetNative.UnitTests/RapidField.SolidInstruments.InversionOfControl.DotNetNative.UnitTests.csproj index 2bfee811..2619de6d 100644 --- a/test/RapidField.SolidInstruments.InversionOfControl.DotNetNative.UnitTests/RapidField.SolidInstruments.InversionOfControl.DotNetNative.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.InversionOfControl.DotNetNative.UnitTests/RapidField.SolidInstruments.InversionOfControl.DotNetNative.UnitTests.csproj @@ -19,10 +19,10 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - - + + + + diff --git a/test/RapidField.SolidInstruments.InversionOfControl.UnitTests/RapidField.SolidInstruments.InversionOfControl.UnitTests.csproj b/test/RapidField.SolidInstruments.InversionOfControl.UnitTests/RapidField.SolidInstruments.InversionOfControl.UnitTests.csproj index f508e5e5..c06211d7 100644 --- a/test/RapidField.SolidInstruments.InversionOfControl.UnitTests/RapidField.SolidInstruments.InversionOfControl.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.InversionOfControl.UnitTests/RapidField.SolidInstruments.InversionOfControl.UnitTests.csproj @@ -19,10 +19,10 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - - + + + + diff --git a/test/RapidField.SolidInstruments.Mathematics.UnitTests/RapidField.SolidInstruments.Mathematics.UnitTests.csproj b/test/RapidField.SolidInstruments.Mathematics.UnitTests/RapidField.SolidInstruments.Mathematics.UnitTests.csproj index 63cfafa4..8e43f1c4 100644 --- a/test/RapidField.SolidInstruments.Mathematics.UnitTests/RapidField.SolidInstruments.Mathematics.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.Mathematics.UnitTests/RapidField.SolidInstruments.Mathematics.UnitTests.csproj @@ -22,10 +22,10 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - + - - + + diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/RapidField.SolidInstruments.Messaging.InMemory.UnitTests.csproj b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/RapidField.SolidInstruments.Messaging.InMemory.UnitTests.csproj index 76131cd1..7c1aaa73 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/RapidField.SolidInstruments.Messaging.InMemory.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/RapidField.SolidInstruments.Messaging.InMemory.UnitTests.csproj @@ -19,9 +19,9 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - + + + diff --git a/test/RapidField.SolidInstruments.Messaging.UnitTests/RapidField.SolidInstruments.Messaging.UnitTests.csproj b/test/RapidField.SolidInstruments.Messaging.UnitTests/RapidField.SolidInstruments.Messaging.UnitTests.csproj index 40edfa06..3de866c4 100644 --- a/test/RapidField.SolidInstruments.Messaging.UnitTests/RapidField.SolidInstruments.Messaging.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.Messaging.UnitTests/RapidField.SolidInstruments.Messaging.UnitTests.csproj @@ -19,9 +19,9 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - + + + diff --git a/test/RapidField.SolidInstruments.ObjectComposition.UnitTests/RapidField.SolidInstruments.ObjectComposition.UnitTests.csproj b/test/RapidField.SolidInstruments.ObjectComposition.UnitTests/RapidField.SolidInstruments.ObjectComposition.UnitTests.csproj index c8c80856..479957ee 100644 --- a/test/RapidField.SolidInstruments.ObjectComposition.UnitTests/RapidField.SolidInstruments.ObjectComposition.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.ObjectComposition.UnitTests/RapidField.SolidInstruments.ObjectComposition.UnitTests.csproj @@ -19,10 +19,10 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - - + + + + diff --git a/test/RapidField.SolidInstruments.Serialization.UnitTests/RapidField.SolidInstruments.Serialization.UnitTests.csproj b/test/RapidField.SolidInstruments.Serialization.UnitTests/RapidField.SolidInstruments.Serialization.UnitTests.csproj index 7172a240..9e4bc930 100644 --- a/test/RapidField.SolidInstruments.Serialization.UnitTests/RapidField.SolidInstruments.Serialization.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.Serialization.UnitTests/RapidField.SolidInstruments.Serialization.UnitTests.csproj @@ -19,9 +19,9 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - + + + diff --git a/test/RapidField.SolidInstruments.SignalProcessing.UnitTests/RapidField.SolidInstruments.SignalProcessing.UnitTests.csproj b/test/RapidField.SolidInstruments.SignalProcessing.UnitTests/RapidField.SolidInstruments.SignalProcessing.UnitTests.csproj index 59e575f5..ba7fc375 100644 --- a/test/RapidField.SolidInstruments.SignalProcessing.UnitTests/RapidField.SolidInstruments.SignalProcessing.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.SignalProcessing.UnitTests/RapidField.SolidInstruments.SignalProcessing.UnitTests.csproj @@ -19,9 +19,9 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - + + + diff --git a/test/RapidField.SolidInstruments.TextEncoding.UnitTests/RapidField.SolidInstruments.TextEncoding.UnitTests.csproj b/test/RapidField.SolidInstruments.TextEncoding.UnitTests/RapidField.SolidInstruments.TextEncoding.UnitTests.csproj index 53904f34..603e3b78 100644 --- a/test/RapidField.SolidInstruments.TextEncoding.UnitTests/RapidField.SolidInstruments.TextEncoding.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.TextEncoding.UnitTests/RapidField.SolidInstruments.TextEncoding.UnitTests.csproj @@ -19,9 +19,9 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - + + + From 8989f33e592fce61293ed3f43d6314e731a18b15 Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Sun, 21 Jun 2020 20:23:14 -0500 Subject: [PATCH 39/55] Add scaffolding for asymmetric key support. --- en-US_User.dic | 1 + .../AsymmetricAlgorithmSpecification.cs | 19 +++ .../Asymmetric/AsymmetricKey.cs | 71 ++++++++ .../Asymmetric/AsymmetricKeyPair.cs | 153 ++++++++++++++++++ .../Asymmetric/AsymmetricProcessor.cs | 40 +++++ .../DigitalSignature/DigitalSignatureKey.cs | 53 ++++++ .../DigitalSignatureKeyPair.cs | 62 +++++++ .../DigitalSignaturePrivateKey.cs | 53 ++++++ .../DigitalSignatureProcessor.cs | 119 ++++++++++++++ .../DigitalSignaturePublicKey.cs | 53 ++++++ .../DigitalSignature/Ecdsa/EcdsaKeyPair.cs | 52 ++++++ .../DigitalSignature/Ecdsa/EcdsaPrivateKey.cs | 52 ++++++ .../DigitalSignature/Ecdsa/EcdsaPublicKey.cs | 52 ++++++ .../DigitalSignature/IDigitalSignatureKey.cs | 13 ++ .../IDigitalSignatureKeyPair.cs | 28 ++++ .../IDigitalSignaturePrivateKey.cs | 13 ++ .../IDigitalSignatureProcessor.cs | 26 +++ .../IDigitalSignaturePublicKey.cs | 13 ++ .../Asymmetric/IAsymmetricKey.cs | 30 ++++ .../Asymmetric/IAsymmetricKeyPair.cs | 56 +++++++ .../Asymmetric/IAsymmetricPrivateKey.cs | 13 ++ .../Asymmetric/IAsymmetricProcessor.cs | 13 ++ .../Asymmetric/IAsymmetricPublicKey.cs | 13 ++ .../KeyExchange/Ecdh/EcdhKeyPair.cs | 52 ++++++ .../KeyExchange/Ecdh/EcdhPrivateKey.cs | 53 ++++++ .../KeyExchange/Ecdh/EcdhPublicKey.cs | 53 ++++++ .../Asymmetric/KeyExchange/IKeyExchangeKey.cs | 13 ++ .../KeyExchange/IKeyExchangeKeyPair.cs | 28 ++++ .../KeyExchange/IKeyExchangePrivateKey.cs | 13 ++ .../KeyExchange/IKeyExchangeProcessor.cs | 13 ++ .../KeyExchange/IKeyExchangePublicKey.cs | 13 ++ .../Asymmetric/KeyExchange/KeyExchangeKey.cs | 53 ++++++ .../KeyExchange/KeyExchangeKeyPair.cs | 62 +++++++ .../KeyExchange/KeyExchangePrivateKey.cs | 53 ++++++ .../KeyExchange/KeyExchangeProcessor.cs | 38 +++++ .../KeyExchange/KeyExchangePublicKey.cs | 53 ++++++ ...Field.SolidInstruments.Cryptography.csproj | 3 - 37 files changed, 1495 insertions(+), 3 deletions(-) create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricAlgorithmSpecification.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricKey.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricKeyPair.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricProcessor.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignatureKey.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignatureKeyPair.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignaturePrivateKey.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignatureProcessor.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignaturePublicKey.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/Ecdsa/EcdsaKeyPair.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/Ecdsa/EcdsaPrivateKey.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/Ecdsa/EcdsaPublicKey.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/IDigitalSignatureKey.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/IDigitalSignatureKeyPair.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/IDigitalSignaturePrivateKey.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/IDigitalSignatureProcessor.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/IDigitalSignaturePublicKey.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricKey.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricKeyPair.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricPrivateKey.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricProcessor.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricPublicKey.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhKeyPair.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhPrivateKey.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhPublicKey.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/IKeyExchangeKey.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/IKeyExchangeKeyPair.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/IKeyExchangePrivateKey.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/IKeyExchangeProcessor.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/IKeyExchangePublicKey.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangeKey.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangeKeyPair.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangePrivateKey.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangeProcessor.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangePublicKey.cs diff --git a/en-US_User.dic b/en-US_User.dic index 6282114c..c4685697 100644 --- a/en-US_User.dic +++ b/en-US_User.dic @@ -70,6 +70,7 @@ Deserializes Destructors deterministically di +Diffie docfx docurl docweb diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricAlgorithmSpecification.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricAlgorithmSpecification.cs new file mode 100644 index 00000000..92649794 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricAlgorithmSpecification.cs @@ -0,0 +1,19 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric +{ + /// + /// Defines distinct combinations of asymmetric key encryption algorithms, key bit-lengths and operational modes. + /// + public enum AsymmetricAlgorithmSpecification : Byte + { + /// + /// The asymmetric algorithm is not specified. + /// + Unspecified = 0x00 + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricKey.cs new file mode 100644 index 00000000..65c633dc --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricKey.cs @@ -0,0 +1,71 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using System; + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric +{ + /// + /// Represents an asymmetric-key algorithm and the key bits for one key in an asymmetric key pair. + /// + /// + /// is the default implementation of . + /// + public abstract class AsymmetricKey : Instrument, IAsymmetricKey + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The asymmetric-key algorithm for which the key is used. + /// + /// + /// is equal to . + /// + protected AsymmetricKey(AsymmetricAlgorithmSpecification algorithm) + : base() + { + Algorithm = algorithm.RejectIf().IsEqualToValue(AsymmetricAlgorithmSpecification.Unspecified, nameof(algorithm)); + } + + /// + /// Converts the value of the current to a secure bit field. + /// + /// + /// A secure bit field containing a representation of the current . + /// + public ISecureMemory ToSecureMemory() => throw new NotImplementedException(); + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) + { + try + { + if (disposing) + { + throw new NotImplementedException(); + } + } + finally + { + base.Dispose(disposing); + } + } + + /// + /// Gets the asymmetric-key algorithm for which the key is used. + /// + public AsymmetricAlgorithmSpecification Algorithm + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricKeyPair.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricKeyPair.cs new file mode 100644 index 00000000..7ca8a5b5 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricKeyPair.cs @@ -0,0 +1,153 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Core.Extensions; +using System; +using System.Diagnostics; +using System.Threading; + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric +{ + /// + /// Represents an asymmetric-key algorithm and the key bits for an asymmetric key pair. + /// + /// + /// is the default implementation of + /// . + /// + /// + /// A shared key type from which both the private and public key types derive. + /// + /// + /// The type of the private key. + /// + /// + /// The type of the public key. + /// + public abstract class AsymmetricKeyPair : AsymmetricKeyPair + where TKey : class, IAsymmetricKey + where TPrivateKey : class, TKey, IAsymmetricPrivateKey + where TPublicKey : class, TKey, IAsymmetricPublicKey + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The asymmetric-key algorithm for which the key is used. + /// + /// + /// is equal to . + /// + protected AsymmetricKeyPair(AsymmetricAlgorithmSpecification algorithm) + : base(algorithm) + { + LazyPrivateKey = new Lazy(InitializePrivateKey, LazyThreadSafetyMode.ExecutionAndPublication); + LazyPublicKey = new Lazy(InitializePublicKey, LazyThreadSafetyMode.ExecutionAndPublication); + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) + { + try + { + if (disposing) + { + LazyPrivateKey?.Dispose(); + LazyPublicKey?.Dispose(); + } + } + finally + { + base.Dispose(disposing); + } + } + + /// + /// Initializes the private key. + /// + /// + /// The private key. + /// + [DebuggerHidden] + private TPrivateKey InitializePrivateKey() => throw new NotImplementedException(); + + /// + /// Initializes the public key. + /// + /// + /// The public key. + /// + [DebuggerHidden] + private TPublicKey InitializePublicKey() => throw new NotImplementedException(); + + /// + /// Gets the private key. + /// + public TPrivateKey PrivateKey => LazyPrivateKey.Value; + + /// + /// Gets the public key. + /// + public TPublicKey PublicKey => LazyPublicKey.Value; + + /// + /// Represents the lazily-initialized private key. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly Lazy LazyPrivateKey; + + /// + /// Represents the lazily-initialized public key. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly Lazy LazyPublicKey; + } + + /// + /// Represents an asymmetric-key algorithm and the key bits for an asymmetric key pair. + /// + /// + /// is the default implementation of . + /// + public abstract class AsymmetricKeyPair : Instrument, IAsymmetricKeyPair + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The asymmetric-key algorithm for which the key is used. + /// + /// + /// is equal to . + /// + protected AsymmetricKeyPair(AsymmetricAlgorithmSpecification algorithm) + : base() + { + Algorithm = algorithm.RejectIf().IsEqualToValue(AsymmetricAlgorithmSpecification.Unspecified, nameof(algorithm)); + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Gets the asymmetric-key algorithm for which the key pair is used. + /// + public AsymmetricAlgorithmSpecification Algorithm + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricProcessor.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricProcessor.cs new file mode 100644 index 00000000..3dcd942f --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricProcessor.cs @@ -0,0 +1,40 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.ArgumentValidation; +using System; +using System.Diagnostics; +using System.Security.Cryptography; + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric +{ + /// + /// Provides facilities for performing asymmetric key operations. + /// + /// + /// is the default implementation of . + /// + public abstract class AsymmetricProcessor : IAsymmetricProcessor + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A random number generator that is used to generate initialization vectors. + /// + /// + /// is . + /// + protected AsymmetricProcessor(RandomNumberGenerator randomnessProvider) + { + RandomnessProvider = randomnessProvider.RejectIf().IsNull(nameof(randomnessProvider)); + } + + /// + /// Represents a random number generator that is used to generate initialization vectors. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly RandomNumberGenerator RandomnessProvider; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignatureKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignatureKey.cs new file mode 100644 index 00000000..117035a3 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignatureKey.cs @@ -0,0 +1,53 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature +{ + /// + /// Represents an asymmetric digital signature algorithm and the key bits for one key in an asymmetric key pair. + /// + /// + /// is the default implementation of . + /// + public abstract class DigitalSignatureKey : AsymmetricKey, IDigitalSignatureKey + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The asymmetric-key algorithm for which the key is used. + /// + /// + /// is equal to . + /// + protected DigitalSignatureKey(AsymmetricAlgorithmSpecification algorithm) + : base(algorithm) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) + { + try + { + if (disposing) + { + throw new NotImplementedException(); + } + } + finally + { + base.Dispose(disposing); + } + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignatureKeyPair.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignatureKeyPair.cs new file mode 100644 index 00000000..d89be1c7 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignatureKeyPair.cs @@ -0,0 +1,62 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature +{ + /// + /// Represents an asymmetric digital signature algorithm and the key bits for an asymmetric key pair. + /// + /// + /// is the default implementation of + /// . + /// + /// + /// The type of the private key. + /// + /// + /// The type of the public key. + /// + public abstract class DigitalSignatureKeyPair : AsymmetricKeyPair, IDigitalSignatureKeyPair + where TPrivateKey : DigitalSignaturePrivateKey + where TPublicKey : DigitalSignaturePublicKey + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The asymmetric-key algorithm for which the key is used. + /// + /// + /// is equal to . + /// + protected DigitalSignatureKeyPair(AsymmetricAlgorithmSpecification algorithm) + : base(algorithm) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) + { + try + { + if (disposing) + { + throw new NotImplementedException(); + } + } + finally + { + base.Dispose(disposing); + } + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignaturePrivateKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignaturePrivateKey.cs new file mode 100644 index 00000000..b1ce42a7 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignaturePrivateKey.cs @@ -0,0 +1,53 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature +{ + /// + /// Represents an asymmetric digital signature algorithm and the private key bits for an asymmetric key pair. + /// + /// + /// is the default implementation of . + /// + public abstract class DigitalSignaturePrivateKey : DigitalSignatureKey, IDigitalSignaturePrivateKey + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The asymmetric-key algorithm for which the key is used. + /// + /// + /// is equal to . + /// + protected DigitalSignaturePrivateKey(AsymmetricAlgorithmSpecification algorithm) + : base(algorithm) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) + { + try + { + if (disposing) + { + throw new NotImplementedException(); + } + } + finally + { + base.Dispose(disposing); + } + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignatureProcessor.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignatureProcessor.cs new file mode 100644 index 00000000..465546fa --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignatureProcessor.cs @@ -0,0 +1,119 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Serialization; +using System; +using System.Diagnostics; +using System.Security.Cryptography; + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature +{ + /// + /// Provides facilities for digitally signing byte arrays. + /// + /// + /// is the default implementation of . + /// + public class DigitalSignatureProcessor : DigitalSignatureProcessor, IDigitalSignatureProcessor + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A random number generator that is used to generate initialization vectors. + /// + /// + /// is . + /// + public DigitalSignatureProcessor(RandomNumberGenerator randomnessProvider) + : base(randomnessProvider, new PassThroughSerializer()) + { + return; + } + + /// + /// Creates a new for the specified serializable type. + /// + /// + /// The serializable object type that the processor can encrypt or decrypt. + /// + /// + /// A new for the specified serializable type. + /// + public static IDigitalSignatureProcessor ForType() + where T : class => new DigitalSignatureProcessor(); + + /// + /// Represents a singleton instance of the class. + /// + public static readonly IDigitalSignatureProcessor Instance = new DigitalSignatureProcessor(HardenedRandomNumberGenerator.Instance); + } + + /// + /// Provides facilities for digitally signing typed objects. + /// + /// + /// is the default implementation of . + /// + public class DigitalSignatureProcessor : AsymmetricProcessor, IDigitalSignatureProcessor + where T : class + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A random number generator that is used to generate initialization vectors. + /// + /// + /// is . + /// + public DigitalSignatureProcessor(RandomNumberGenerator randomnessProvider) + : this(randomnessProvider, DefaultSerializer) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A random number generator that is used to generate initialization vectors. + /// + /// + /// A serializer that is used to transform plaintext. + /// + /// + /// is -or- is + /// . + /// + public DigitalSignatureProcessor(RandomNumberGenerator randomnessProvider, ISerializer serializer) + : base(randomnessProvider) + { + Serializer = serializer.RejectIf().IsNull(nameof(serializer)).TargetArgument; + } + + /// + /// Initializes a new instance of the class. + /// + [DebuggerHidden] + internal DigitalSignatureProcessor() + : this(HardenedRandomNumberGenerator.Instance) + { + return; + } + + /// + /// Represents the default serializer that is used to transform plaintext. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static readonly ISerializer DefaultSerializer = new CompressedJsonSerializer(); + + /// + /// Represents a serializer that is used to transform plaintext. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly ISerializer Serializer; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignaturePublicKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignaturePublicKey.cs new file mode 100644 index 00000000..cdcee530 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignaturePublicKey.cs @@ -0,0 +1,53 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature +{ + /// + /// Represents an asymmetric digital signature algorithm and the public key bits for an asymmetric key pair. + /// + /// + /// is the default implementation of . + /// + public abstract class DigitalSignaturePublicKey : DigitalSignatureKey, IDigitalSignaturePublicKey + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The asymmetric-key algorithm for which the key is used. + /// + /// + /// is equal to . + /// + protected DigitalSignaturePublicKey(AsymmetricAlgorithmSpecification algorithm) + : base(algorithm) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) + { + try + { + if (disposing) + { + throw new NotImplementedException(); + } + } + finally + { + base.Dispose(disposing); + } + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/Ecdsa/EcdsaKeyPair.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/Ecdsa/EcdsaKeyPair.cs new file mode 100644 index 00000000..a76210fb --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/Ecdsa/EcdsaKeyPair.cs @@ -0,0 +1,52 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Diagnostics; + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature.Ecdsa +{ + /// + /// Represents an asymmetric, elliptic curve digital signature algorithm and the key bits for an asymmetric key pair. + /// + public sealed class EcdsaKeyPair : DigitalSignatureKeyPair + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The asymmetric-key algorithm for which the key is used. + /// + /// + /// is equal to . + /// + [DebuggerHidden] + internal EcdsaKeyPair(AsymmetricAlgorithmSpecification algorithm) + : base(algorithm) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) + { + try + { + if (disposing) + { + throw new NotImplementedException(); + } + } + finally + { + base.Dispose(disposing); + } + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/Ecdsa/EcdsaPrivateKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/Ecdsa/EcdsaPrivateKey.cs new file mode 100644 index 00000000..0465dddc --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/Ecdsa/EcdsaPrivateKey.cs @@ -0,0 +1,52 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Diagnostics; + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature.Ecdsa +{ + /// + /// Represents an asymmetric, elliptic curve digital signature algorithm and the private key bits for an asymmetric key pair. + /// + public sealed class EcdsaPrivateKey : DigitalSignaturePrivateKey + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The asymmetric-key algorithm for which the key is used. + /// + /// + /// is equal to . + /// + [DebuggerHidden] + internal EcdsaPrivateKey(AsymmetricAlgorithmSpecification algorithm) + : base(algorithm) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) + { + try + { + if (disposing) + { + throw new NotImplementedException(); + } + } + finally + { + base.Dispose(disposing); + } + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/Ecdsa/EcdsaPublicKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/Ecdsa/EcdsaPublicKey.cs new file mode 100644 index 00000000..f9eeb112 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/Ecdsa/EcdsaPublicKey.cs @@ -0,0 +1,52 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Diagnostics; + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature.Ecdsa +{ + /// + /// Represents an asymmetric, elliptic curve digital signature algorithm and the private key bits for an asymmetric key pair. + /// + public sealed class EcdsaPublicKey : DigitalSignaturePublicKey + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The asymmetric-key algorithm for which the key is used. + /// + /// + /// is equal to . + /// + [DebuggerHidden] + internal EcdsaPublicKey(AsymmetricAlgorithmSpecification algorithm) + : base(algorithm) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) + { + try + { + if (disposing) + { + throw new NotImplementedException(); + } + } + finally + { + base.Dispose(disposing); + } + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/IDigitalSignatureKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/IDigitalSignatureKey.cs new file mode 100644 index 00000000..ef945cc3 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/IDigitalSignatureKey.cs @@ -0,0 +1,13 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature +{ + /// + /// Represents an asymmetric digital signature algorithm and the key bits for one key in an asymmetric key pair. + /// + public interface IDigitalSignatureKey : IAsymmetricKey + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/IDigitalSignatureKeyPair.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/IDigitalSignatureKeyPair.cs new file mode 100644 index 00000000..ed96d428 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/IDigitalSignatureKeyPair.cs @@ -0,0 +1,28 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature +{ + /// + /// Represents an asymmetric digital signature algorithm and the key bits for an asymmetric key pair. + /// + /// + /// The type of the private key. + /// + /// + /// The type of the public key. + /// + public interface IDigitalSignatureKeyPair : IAsymmetricKeyPair, IDigitalSignatureKeyPair + where TPrivateKey : DigitalSignaturePrivateKey + where TPublicKey : DigitalSignaturePublicKey + { + } + + /// + /// Represents an asymmetric digital signature algorithm and the key bits for an asymmetric key pair. + /// + public interface IDigitalSignatureKeyPair : IAsymmetricKeyPair + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/IDigitalSignaturePrivateKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/IDigitalSignaturePrivateKey.cs new file mode 100644 index 00000000..9796c879 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/IDigitalSignaturePrivateKey.cs @@ -0,0 +1,13 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature +{ + /// + /// Represents an asymmetric digital signature algorithm and the private key bits for an asymmetric key pair. + /// + public interface IDigitalSignaturePrivateKey : IAsymmetricPrivateKey, IDigitalSignatureKey + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/IDigitalSignatureProcessor.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/IDigitalSignatureProcessor.cs new file mode 100644 index 00000000..35a1e526 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/IDigitalSignatureProcessor.cs @@ -0,0 +1,26 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature +{ + /// + /// Provides facilities for digitally signing byte arrays. + /// + public interface IDigitalSignatureProcessor : IDigitalSignatureProcessor + { + } + + /// + /// Provides facilities for digitally signing typed objects. + /// + /// + /// The type of the object that can be digitally signed. + /// + public interface IDigitalSignatureProcessor : IAsymmetricProcessor + where T : class + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/IDigitalSignaturePublicKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/IDigitalSignaturePublicKey.cs new file mode 100644 index 00000000..de4cd227 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/IDigitalSignaturePublicKey.cs @@ -0,0 +1,13 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature +{ + /// + /// Represents an asymmetric digital signature algorithm and the public key bits for an asymmetric key pair. + /// + public interface IDigitalSignaturePublicKey : IAsymmetricPublicKey, IDigitalSignatureKey + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricKey.cs new file mode 100644 index 00000000..bb54391f --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricKey.cs @@ -0,0 +1,30 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric +{ + /// + /// Represents an asymmetric-key algorithm and the key bits for one key in an asymmetric key pair. + /// + public interface IAsymmetricKey : IAsyncDisposable, IDisposable + { + /// + /// Converts the value of the current to a secure bit field. + /// + /// + /// A secure bit field containing a representation of the current . + /// + public ISecureMemory ToSecureMemory(); + + /// + /// Gets the asymmetric-key algorithm for which the key is used. + /// + public AsymmetricAlgorithmSpecification Algorithm + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricKeyPair.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricKeyPair.cs new file mode 100644 index 00000000..6702bc1c --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricKeyPair.cs @@ -0,0 +1,56 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric +{ + /// + /// Represents an asymmetric-key algorithm and the key bits for an asymmetric key pair. + /// + /// + /// A shared key type from which both the private and public key types derive. + /// + /// + /// The type of the private key. + /// + /// + /// The type of the public key. + /// + public interface IAsymmetricKeyPair : IAsymmetricKeyPair + where TKey : class, IAsymmetricKey + where TPrivateKey : class, TKey, IAsymmetricPrivateKey + where TPublicKey : class, TKey, IAsymmetricPublicKey + { + /// + /// Gets the private key. + /// + public TPrivateKey PrivateKey + { + get; + } + + /// + /// Gets the public key. + /// + public TPublicKey PublicKey + { + get; + } + } + + /// + /// Represents an asymmetric-key algorithm and the key bits for an asymmetric key pair. + /// + public interface IAsymmetricKeyPair : IAsyncDisposable, IDisposable + { + /// + /// Gets the asymmetric-key algorithm for which the key pair is used. + /// + public AsymmetricAlgorithmSpecification Algorithm + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricPrivateKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricPrivateKey.cs new file mode 100644 index 00000000..f9700e86 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricPrivateKey.cs @@ -0,0 +1,13 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric +{ + /// + /// Represents an asymmetric-key algorithm and the private key bits for an asymmetric key pair. + /// + public interface IAsymmetricPrivateKey : IAsymmetricKey + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricProcessor.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricProcessor.cs new file mode 100644 index 00000000..d9acca4a --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricProcessor.cs @@ -0,0 +1,13 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric +{ + /// + /// Provides facilities for performing asymmetric key operations. + /// + public interface IAsymmetricProcessor + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricPublicKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricPublicKey.cs new file mode 100644 index 00000000..36bbfc1d --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricPublicKey.cs @@ -0,0 +1,13 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric +{ + /// + /// Represents an asymmetric-key algorithm and the public key bits for an asymmetric key pair. + /// + public interface IAsymmetricPublicKey : IAsymmetricKey + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhKeyPair.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhKeyPair.cs new file mode 100644 index 00000000..8668bd9e --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhKeyPair.cs @@ -0,0 +1,52 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Diagnostics; + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange.Ecdh +{ + /// + /// Represents an asymmetric, elliptic curve Diffie-Hellman key exchange algorithm and the key bits for an asymmetric key pair. + /// + public sealed class EcdhKeyPair : KeyExchangeKeyPair + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The asymmetric-key algorithm for which the key is used. + /// + /// + /// is equal to . + /// + [DebuggerHidden] + internal EcdhKeyPair(AsymmetricAlgorithmSpecification algorithm) + : base(algorithm) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) + { + try + { + if (disposing) + { + throw new NotImplementedException(); + } + } + finally + { + base.Dispose(disposing); + } + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhPrivateKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhPrivateKey.cs new file mode 100644 index 00000000..91ece4c3 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhPrivateKey.cs @@ -0,0 +1,53 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Diagnostics; + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange.Ecdh +{ + /// + /// Represents an asymmetric, elliptic curve Diffie-Hellman key exchange algorithm and the private key bits for an asymmetric + /// key pair. + /// + public sealed class EcdhPrivateKey : KeyExchangePrivateKey + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The asymmetric-key algorithm for which the key is used. + /// + /// + /// is equal to . + /// + [DebuggerHidden] + internal EcdhPrivateKey(AsymmetricAlgorithmSpecification algorithm) + : base(algorithm) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) + { + try + { + if (disposing) + { + throw new NotImplementedException(); + } + } + finally + { + base.Dispose(disposing); + } + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhPublicKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhPublicKey.cs new file mode 100644 index 00000000..b580cded --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhPublicKey.cs @@ -0,0 +1,53 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Diagnostics; + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange.Ecdh +{ + /// + /// Represents an asymmetric, elliptic curve Diffie-Hellman key exchange algorithm and the private key bits for an asymmetric + /// key pair. + /// + public sealed class EcdhPublicKey : KeyExchangePublicKey + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The asymmetric-key algorithm for which the key is used. + /// + /// + /// is equal to . + /// + [DebuggerHidden] + internal EcdhPublicKey(AsymmetricAlgorithmSpecification algorithm) + : base(algorithm) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) + { + try + { + if (disposing) + { + throw new NotImplementedException(); + } + } + finally + { + base.Dispose(disposing); + } + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/IKeyExchangeKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/IKeyExchangeKey.cs new file mode 100644 index 00000000..13a5c480 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/IKeyExchangeKey.cs @@ -0,0 +1,13 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange +{ + /// + /// Represents an asymmetric key exchange algorithm and the key bits for one key in an asymmetric key pair. + /// + public interface IKeyExchangeKey : IAsymmetricKey + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/IKeyExchangeKeyPair.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/IKeyExchangeKeyPair.cs new file mode 100644 index 00000000..62fe6e59 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/IKeyExchangeKeyPair.cs @@ -0,0 +1,28 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange +{ + /// + /// Represents an asymmetric key exchange algorithm and the key bits for an asymmetric key pair. + /// + /// + /// The type of the private key. + /// + /// + /// The type of the public key. + /// + public interface IKeyExchangeKeyPair : IAsymmetricKeyPair, IKeyExchangeKeyPair + where TPrivateKey : KeyExchangePrivateKey + where TPublicKey : KeyExchangePublicKey + { + } + + /// + /// Represents an asymmetric key exchange algorithm and the key bits for an asymmetric key pair. + /// + public interface IKeyExchangeKeyPair : IAsymmetricKeyPair + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/IKeyExchangePrivateKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/IKeyExchangePrivateKey.cs new file mode 100644 index 00000000..09489bf9 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/IKeyExchangePrivateKey.cs @@ -0,0 +1,13 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange +{ + /// + /// Represents an asymmetric key exchange algorithm and the private key bits for an asymmetric key pair. + /// + public interface IKeyExchangePrivateKey : IAsymmetricPrivateKey, IKeyExchangeKey + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/IKeyExchangeProcessor.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/IKeyExchangeProcessor.cs new file mode 100644 index 00000000..1af6f9e8 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/IKeyExchangeProcessor.cs @@ -0,0 +1,13 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange +{ + /// + /// Provides facilities for exchanging symmetric keys using asymmetric key pairs. + /// + public interface IKeyExchangeProcessor : IAsymmetricProcessor + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/IKeyExchangePublicKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/IKeyExchangePublicKey.cs new file mode 100644 index 00000000..f11b314d --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/IKeyExchangePublicKey.cs @@ -0,0 +1,13 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange +{ + /// + /// Represents an asymmetric key exchange algorithm and the public key bits for an asymmetric key pair. + /// + public interface IKeyExchangePublicKey : IAsymmetricPublicKey, IKeyExchangeKey + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangeKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangeKey.cs new file mode 100644 index 00000000..2f940bb8 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangeKey.cs @@ -0,0 +1,53 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange +{ + /// + /// Represents an asymmetric key exchange algorithm and the key bits for one key in an asymmetric key pair. + /// + /// + /// is the default implementation of . + /// + public abstract class KeyExchangeKey : AsymmetricKey, IKeyExchangeKey + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The asymmetric-key algorithm for which the key is used. + /// + /// + /// is equal to . + /// + protected KeyExchangeKey(AsymmetricAlgorithmSpecification algorithm) + : base(algorithm) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) + { + try + { + if (disposing) + { + throw new NotImplementedException(); + } + } + finally + { + base.Dispose(disposing); + } + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangeKeyPair.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangeKeyPair.cs new file mode 100644 index 00000000..2bc5a474 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangeKeyPair.cs @@ -0,0 +1,62 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange +{ + /// + /// Represents an asymmetric key exchange algorithm and the key bits for an asymmetric key pair. + /// + /// + /// is the default implementation of + /// . + /// + /// + /// The type of the private key. + /// + /// + /// The type of the public key. + /// + public abstract class KeyExchangeKeyPair : AsymmetricKeyPair, IKeyExchangeKeyPair + where TPrivateKey : KeyExchangePrivateKey + where TPublicKey : KeyExchangePublicKey + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The asymmetric-key algorithm for which the key is used. + /// + /// + /// is equal to . + /// + protected KeyExchangeKeyPair(AsymmetricAlgorithmSpecification algorithm) + : base(algorithm) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) + { + try + { + if (disposing) + { + throw new NotImplementedException(); + } + } + finally + { + base.Dispose(disposing); + } + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangePrivateKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangePrivateKey.cs new file mode 100644 index 00000000..8e30d55c --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangePrivateKey.cs @@ -0,0 +1,53 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange +{ + /// + /// Represents an asymmetric key exchange algorithm and the private key bits for an asymmetric key pair. + /// + /// + /// is the default implementation of . + /// + public abstract class KeyExchangePrivateKey : KeyExchangeKey, IKeyExchangePrivateKey + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The asymmetric-key algorithm for which the key is used. + /// + /// + /// is equal to . + /// + protected KeyExchangePrivateKey(AsymmetricAlgorithmSpecification algorithm) + : base(algorithm) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) + { + try + { + if (disposing) + { + throw new NotImplementedException(); + } + } + finally + { + base.Dispose(disposing); + } + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangeProcessor.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangeProcessor.cs new file mode 100644 index 00000000..6ea229e1 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangeProcessor.cs @@ -0,0 +1,38 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Security.Cryptography; + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange +{ + /// + /// Provides facilities for exchanging symmetric keys using asymmetric key pairs. + /// + /// + /// is the default implementation of . + /// + public sealed class KeyExchangeProcessor : AsymmetricProcessor, IKeyExchangeProcessor + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A random number generator that is used to generate initialization vectors. + /// + /// + /// is . + /// + public KeyExchangeProcessor(RandomNumberGenerator randomnessProvider) + : base(randomnessProvider) + { + return; + } + + /// + /// Represents a singleton instance of the class. + /// + public static readonly IKeyExchangeProcessor Instance = new KeyExchangeProcessor(HardenedRandomNumberGenerator.Instance); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangePublicKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangePublicKey.cs new file mode 100644 index 00000000..dd3527d1 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangePublicKey.cs @@ -0,0 +1,53 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange +{ + /// + /// Represents an asymmetric key exchange algorithm and the public key bits for an asymmetric key pair. + /// + /// + /// is the default implementation of . + /// + public abstract class KeyExchangePublicKey : KeyExchangeKey, IKeyExchangePublicKey + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The asymmetric-key algorithm for which the key is used. + /// + /// + /// is equal to . + /// + protected KeyExchangePublicKey(AsymmetricAlgorithmSpecification algorithm) + : base(algorithm) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) + { + try + { + if (disposing) + { + throw new NotImplementedException(); + } + } + finally + { + base.Dispose(disposing); + } + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/RapidField.SolidInstruments.Cryptography.csproj b/src/RapidField.SolidInstruments.Cryptography/RapidField.SolidInstruments.Cryptography.csproj index 889c1cf9..4478a787 100644 --- a/src/RapidField.SolidInstruments.Cryptography/RapidField.SolidInstruments.Cryptography.csproj +++ b/src/RapidField.SolidInstruments.Cryptography/RapidField.SolidInstruments.Cryptography.csproj @@ -36,9 +36,6 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - From de9f77441a5d421c2b76e1abdfb940638809380b Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Tue, 23 Jun 2020 22:14:26 -0500 Subject: [PATCH 40/55] Refactor SymmetricKey and CascadingSymmetricKey. --- .../CryptographicKey.cs | 676 ++++++++++++++++++ ...e.cs => CryptographicKeyDerivationMode.cs} | 10 +- .../ICryptographicKey.cs | 57 ++ .../Symmetric/CascadingSymmetricKey.cs | 184 ++--- .../Symmetric/ICascadingSymmetricKey.cs | 10 +- .../Symmetric/ISymmetricKey.cs | 28 +- .../Symmetric/SymmetricKey.cs | 601 +--------------- .../Symmetric/CascadingSymmetricKeyTests.cs | 20 +- .../Symmetric/SymmetricKeyTests.cs | 8 +- .../Symmetric/SymmetricProcessorTests.cs | 74 +- .../SymmetricStringProcessorTests.cs | 6 +- 11 files changed, 919 insertions(+), 755 deletions(-) create mode 100644 src/RapidField.SolidInstruments.Cryptography/CryptographicKey.cs rename src/RapidField.SolidInstruments.Cryptography/{Symmetric/SymmetricKeyDerivationMode.cs => CryptographicKeyDerivationMode.cs} (88%) create mode 100644 src/RapidField.SolidInstruments.Cryptography/ICryptographicKey.cs diff --git a/src/RapidField.SolidInstruments.Cryptography/CryptographicKey.cs b/src/RapidField.SolidInstruments.Cryptography/CryptographicKey.cs new file mode 100644 index 00000000..3329b418 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/CryptographicKey.cs @@ -0,0 +1,676 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Collections; +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Core.Concurrency; +using RapidField.SolidInstruments.Core.Extensions; +using RapidField.SolidInstruments.Cryptography.Extensions; +using RapidField.SolidInstruments.Cryptography.Hashing; +using RapidField.SolidInstruments.Cryptography.Secrets; +using RapidField.SolidInstruments.Cryptography.Symmetric; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Security; +using System.Security.Cryptography; +using System.Text; +using System.Threading; + +namespace RapidField.SolidInstruments.Cryptography +{ + /// + /// Represents a cryptographic algorithm and source bits for a derived key, encapsulates key derivation operations and secures + /// key bits in memory. + /// + /// + /// is the default implementation of . + /// + /// + /// The type of the cryptographic algorithm for which a key is derived. + /// + public abstract class CryptographicKey : CryptographicKey, ICryptographicKey + where TAlgorithm : struct, Enum + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The cryptographic algorithm for which a key is derived. + /// + /// + /// The mode used to derive the output key. + /// + /// + /// A bit field that is used to derive key bits. + /// + /// + /// The length of the derived key, in bits. + /// + /// + /// is equal to the default/unspecified value -or- + /// is less than or equal to zero. + /// + [DebuggerHidden] + protected CryptographicKey(TAlgorithm algorithm, CryptographicKeyDerivationMode derivationMode, PinnedMemory keySource, Int32 derivedKeyLengthInBits) + : base() + { + Algorithm = algorithm.RejectIf().IsEqualToValue(default, nameof(algorithm)); + DerivationMode = derivationMode.RejectIf().IsEqualToValue(CryptographicKeyDerivationMode.Unspecified, nameof(derivationMode)); + KeySource = new SecureMemory(KeySourceLengthInBytes); + LazyPbkdf2Provider = new Lazy(InitializePbkdf2Algorithm, LazyThreadSafetyMode.ExecutionAndPublication); + + // Gather information about the derived key. + DerivedKeyLength = (derivedKeyLengthInBits.RejectIf().IsLessThanOrEqualTo(0, nameof(derivedKeyLengthInBits)).TargetArgument / 8); + BlockWordCount = (derivedKeyLengthInBits / 32); + BlockCount = (KeySourceWordCount / BlockWordCount); + + // Copy in the key source bits. + KeySource.Access(memory => Array.Copy(keySource, memory, memory.Length)); + } + + /// + /// Converts the current to cryptographic key plaintext with correct bit-length + /// for the encryption mode specified by . + /// + /// + /// The derived key. + /// + /// + /// The object is disposed. + /// + [DebuggerHidden] + public ISecureMemory ToDerivedKeyBytes() + { + var result = new SecureMemory(DerivedKeyLength); + + try + { + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + + if (DerivationMode == CryptographicKeyDerivationMode.Pbkdf2) + { + try + { + result.Access(memory => + { + // Perform PBKDF2 key derivation. + var keyBytes = new Span(Pbkdf2Provider.GetBytes(DerivedKeyLength)); + keyBytes.CopyTo(memory); + keyBytes.Clear(); + }); + } + finally + { + Pbkdf2Provider.Reset(); + } + + return result; + } + + using (var sourceWords = new PinnedMemory(KeySourceWordCount, true)) + { + KeySource.Access(memory => + { + // Convert the source bit field to an array of 32-bit words. + Buffer.BlockCopy(memory, 0, sourceWords, 0, KeySourceLengthInBytes); + }); + + using (var transformedWords = new PinnedMemory(BlockWordCount, true)) + { + // Copy out the first block. If nothing further is done, this satisfies truncation mode. + Array.Copy(sourceWords, transformedWords, BlockWordCount); + + switch (DerivationMode) + { + case CryptographicKeyDerivationMode.Truncation: + + break; + + case CryptographicKeyDerivationMode.XorLayering: + + for (var i = 1; i < BlockCount; i++) + { + for (var j = 0; j < BlockWordCount; j++) + { + // Perform the XOR layering operation. + transformedWords[j] = (transformedWords[j] ^ sourceWords[(i * BlockWordCount) + j]); + } + } + + break; + + case CryptographicKeyDerivationMode.XorLayeringWithSubstitution: + + for (var i = 1; i < BlockCount; i++) + { + for (var j = 0; j < BlockWordCount; j++) + { + // Perform the XOR layering operation with substitution. + transformedWords[j] = (SubstituteWord(transformedWords[j]) ^ sourceWords[(i * BlockWordCount) + j]); + } + } + + break; + + default: + + throw new UnsupportedSpecificationException($"The specified key derivation mode, {DerivationMode}, is not supported."); + } + + result.Access(memory => + { + // Copy out the key bits. + var keyBytes = new Byte[DerivedKeyLength]; + Buffer.BlockCopy(transformedWords, 0, keyBytes, 0, DerivedKeyLength); + keyBytes.CopyTo(memory); + + for (var i = 0; i < DerivedKeyLength; i++) + { + keyBytes[i] = 0x00; + } + }); + } + } + + return result; + } + } + catch + { + result?.Dispose(); + throw new SecurityException("Key derivation failed."); + } + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) + { + try + { + if (disposing) + { + SecureMemoryEncryptionKey.Dispose(); + KeySource.Dispose(); + LazyPbkdf2Provider.Dispose(); + } + } + finally + { + base.Dispose(disposing); + } + } + + /// + /// Converts the value of the current to a secure bit field. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + /// + /// A secure bit field containing a representation of the current . + /// + protected sealed override ISecureMemory ToSecureMemory(IConcurrencyControlToken controlToken) + { + var secureMemory = new SecureMemory(SerializedLength); + + try + { + using (var plaintextMemory = new PinnedMemory(SerializedPlaintextLength, true)) + { + KeySource.Access(pinnedKeySourceMemory => + { + Array.Copy(pinnedKeySourceMemory, 0, plaintextMemory, KeySourceSecureMemoryIndex, KeySourceLengthInBytes); + }); + + plaintextMemory[AlgorithmSecureMemoryIndex] = Convert.ToByte(Algorithm); + plaintextMemory[DerivationModeSecureMemoryIndex] = Convert.ToByte(DerivationMode); + + using (var cipher = SecureMemoryEncryptionAlgorithm.ToCipher(RandomnessProvider)) + { + using (var initializationVector = new PinnedMemory(cipher.BlockSizeInBytes, true)) + { + RandomnessProvider.GetBytes(initializationVector); + + secureMemory.Access(pinnedMemory => + { + using (var ciphertext = cipher.Encrypt(plaintextMemory, SecureMemoryEncryptionKey, initializationVector)) + { + Array.Copy(ciphertext, 0, pinnedMemory, 0, SerializedLength); + } + }); + } + } + } + + return secureMemory; + } + catch + { + secureMemory.Dispose(); + throw new SecurityException("Key serialization failed."); + } + } + + /// + /// Finalizes static members of the class. + /// + [DebuggerHidden] + private static void FinalizeStaticMembers() => SecureMemoryEncryptionKey.Dispose(); + + /// + /// Substitutes the specified input word for a word derived from . + /// + /// + /// A word to be substituted. + /// + /// + /// The substitution word. + /// + [DebuggerHidden] + private static UInt32 SubstituteWord(UInt32 inputWord) + { + var inputBytes = BitConverter.GetBytes(inputWord); + var substitutionBytes = new Byte[4] { SubstitutionBox[inputBytes[2]], SubstitutionBox[inputBytes[1]], SubstitutionBox[inputBytes[3]], SubstitutionBox[inputBytes[0]] }; + return BitConverter.ToUInt32(substitutionBytes, 0); + } + + /// + /// Initializes an instance of an using the leading bytes of the key source. + /// + /// + /// An instance of an . + /// + [DebuggerHidden] + private Rfc2898DeriveBytes InitializePbkdf2Algorithm() + { + var algorithm = (Rfc2898DeriveBytes)null; + + KeySource.Access(memory => + { + var iterationSumBytes = memory.Take(Pbkdf2IterationSumLengthInBytes); + var saltBytes = memory.Skip(Pbkdf2IterationSumLengthInBytes).Take(Pbkdf2SaltLengthInBytes); + var passwordBytes = memory.Skip(Pbkdf2IterationSumLengthInBytes + Pbkdf2SaltLengthInBytes).Take(Pbkdf2PasswordLengthInBytes); + var iterationCount = Pbkdf2MinimumIterationCount; + + foreach (var iterationSumValue in iterationSumBytes) + { + iterationCount += iterationSumValue; + } + + algorithm = new Rfc2898DeriveBytes(passwordBytes.ToArray(), saltBytes.ToArray(), iterationCount); + }); + + return algorithm; + } + + /// + /// Gets the cryptographic algorithm for which a key is derived. + /// + public TAlgorithm Algorithm + { + get; + } + + /// + /// Gets the substitution bytes that are used to fulfill key derivation operations when is + /// equal to . + /// + /// + /// This property is exposed for testing. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal static IEnumerable SubstitutionBoxBytes => SubstitutionBox; + + /// + /// Gets the PBKDF2 algorithm provider for the current . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private Rfc2898DeriveBytes Pbkdf2Provider => LazyPbkdf2Provider.Value; + + /// + /// Represents the byte index of the algorithm byte within an unencrypted, serialized bit field. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal const Int32 AlgorithmSecureMemoryIndex = (KeySourceSecureMemoryIndex + KeySourceLengthInBytes); + + /// + /// Represents the default derivation mode for new keys. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal const CryptographicKeyDerivationMode DefaultDerivationMode = CryptographicKeyDerivationMode.Pbkdf2; + + /// + /// Represents the byte index of the derivation mode byte within an unencrypted, serialized bit field. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal const Int32 DerivationModeSecureMemoryIndex = (AlgorithmSecureMemoryIndex + AlgorithmLength); + + /// + /// Represents the number of bytes comprising . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal const Int32 KeySourceLengthInBytes = (KeySourceWordCount * sizeof(UInt32)); + + /// + /// Represents the byte index of the key source within an unencrypted, serialized bit field. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal const Int32 KeySourceSecureMemoryIndex = 0; + + /// + /// Represents the number of bytes comprising a post-encrypted, serialized representation of a + /// . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal const Int32 SerializedLength = (SerializedPlaintextLength + (SecureMemoryEncryptionAlgorithmBlockSizeInBytes * 2) - AlgorithmLength - DerivationModeLength); + + /// + /// Represents the number of bytes comprising a pre-encrypted, serialized representation of a + /// . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal const Int32 SerializedPlaintextLength = (KeySourceLengthInBytes + AlgorithmLength + DerivationModeLength); + + /// + /// Represents the key for the cryptographic key algorithm that is used to obscure serialized bit fields. + /// + /// + /// The author acknowledges that obscurity does not ensure security. Encrypting sensitive information with a known key does + /// not secure it. This is intended to stand up a barrier against unsophisticated attacks targeting users who have + /// mistakenly exposed their key source. This sequence is fairly arbitrary and can be modified if needed, but there are + /// probably few good reasons to. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal static readonly PinnedMemory SecureMemoryEncryptionKey = new PinnedMemory(new Byte[] + { + 0xaa, 0xf0, 0xcc, 0xff, 0x00, 0x33, 0x0f, 0x55, 0xff, 0xcc, 0xf0, 0xaa, 0x55, 0x0f, 0x33, 0x00 + }); + + /// + /// Represents the byte length of . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Int32 AlgorithmLength = sizeof(SymmetricAlgorithmSpecification); + + /// + /// Represents the byte length of . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Int32 DerivationModeLength = sizeof(CryptographicKeyDerivationMode); + + /// + /// Represents the exact number of 32-bit key words that are derived from . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Int32 KeySourceWordCount = 96; + + /// + /// Represents the number of key source bytes constituting the iteration sum for PBKDF2-derived keys. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Int32 Pbkdf2IterationSumLengthInBytes = (KeySourceLengthInBytes / 3); + + /// + /// Represents the number of key source bytes constituting the password for PBKDF2-derived keys. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Int32 Pbkdf2PasswordLengthInBytes = (KeySourceLengthInBytes / 3); + + /// + /// Represents the number of key source bytes constituting the salt for PBKDF2-derived keys. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Int32 Pbkdf2SaltLengthInBytes = (KeySourceLengthInBytes / 3); + + /// + /// Represents a finalizer for static members of the class. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static readonly StaticMemberFinalizer StaticMemberFinalizer = new StaticMemberFinalizer(FinalizeStaticMembers); + + /// + /// Represents the substitution bytes that are used to fulfill key derivation operations when + /// is equal to . + /// + /// + /// This sequence is deliberately and carefully balanced. Modifications can introduce severe security flaws and break the + /// class functionally. Do not modify without very careful consideration and extremely good reason. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static readonly Byte[] SubstitutionBox = + { + // IMPORTANT Read the remarks above. Inform senior members of the team if a pull request introduces changes here. + 0xff, 0xf0, 0xfd, 0xf2, 0xfb, 0xf4, 0xf9, 0xf6, 0xf7, 0xf8, 0xf5, 0xfa, 0xf3, 0xfc, 0xf1, 0xfe, + 0x0f, 0x00, 0x0d, 0x02, 0x0b, 0x04, 0x09, 0x06, 0x07, 0x08, 0x05, 0x0a, 0x03, 0x0c, 0x01, 0x0e, + 0x4f, 0x40, 0x4d, 0x42, 0x4b, 0x44, 0x49, 0x46, 0x47, 0x48, 0x45, 0x4a, 0x43, 0x4c, 0x41, 0x4e, + 0x2f, 0x20, 0x2d, 0x22, 0x2b, 0x24, 0x29, 0x26, 0x27, 0x28, 0x25, 0x2a, 0x23, 0x2c, 0x21, 0x2e, + 0xbf, 0xb0, 0xbd, 0xb2, 0xbb, 0xb4, 0xb9, 0xb6, 0xb7, 0xb8, 0xb5, 0xba, 0xb3, 0xbc, 0xb1, 0xbe, + 0x9f, 0x90, 0x9d, 0x92, 0x9b, 0x94, 0x99, 0x96, 0x97, 0x98, 0x95, 0x9a, 0x93, 0x9c, 0x91, 0x9e, + 0xdf, 0xd0, 0xdd, 0xd2, 0xdb, 0xd4, 0xd9, 0xd6, 0xd7, 0xd8, 0xd5, 0xda, 0xd3, 0xdc, 0xd1, 0xde, + 0x6f, 0x60, 0x6d, 0x62, 0x6b, 0x64, 0x69, 0x66, 0x67, 0x68, 0x65, 0x6a, 0x63, 0x6c, 0x61, 0x6e, + 0x7f, 0x70, 0x7d, 0x72, 0x7b, 0x74, 0x79, 0x76, 0x77, 0x78, 0x75, 0x7a, 0x73, 0x7c, 0x71, 0x7e, + 0xcf, 0xc0, 0xcd, 0xc2, 0xcb, 0xc4, 0xc9, 0xc6, 0xc7, 0xc8, 0xc5, 0xca, 0xc3, 0xcc, 0xc1, 0xce, + 0x8f, 0x80, 0x8d, 0x82, 0x8b, 0x84, 0x89, 0x86, 0x87, 0x88, 0x85, 0x8a, 0x83, 0x8c, 0x81, 0x8e, + 0xaf, 0xa0, 0xad, 0xa2, 0xab, 0xa4, 0xa9, 0xa6, 0xa7, 0xa8, 0xa5, 0xaa, 0xa3, 0xac, 0xa1, 0xae, + 0x3f, 0x30, 0x3d, 0x32, 0x3b, 0x34, 0x39, 0x36, 0x37, 0x38, 0x35, 0x3a, 0x33, 0x3c, 0x31, 0x3e, + 0x5f, 0x50, 0x5d, 0x52, 0x5b, 0x54, 0x59, 0x56, 0x57, 0x58, 0x55, 0x5a, 0x53, 0x5c, 0x51, 0x5e, + 0x1f, 0x10, 0x1d, 0x12, 0x1b, 0x14, 0x19, 0x16, 0x17, 0x18, 0x15, 0x1a, 0x13, 0x1c, 0x11, 0x1e, + 0xef, 0xe0, 0xed, 0xe2, 0xeb, 0xe4, 0xe9, 0xe6, 0xe7, 0xe8, 0xe5, 0xea, 0xe3, 0xec, 0xe1, 0xee + }; + + /// + /// Represents the number of key blocks for the specified algorithm. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly Int32 BlockCount; + + /// + /// Represents the number of 32-bit words contained within a key block. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly Int32 BlockWordCount; + + /// + /// Represents the mode used to derive key bits from the current . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly CryptographicKeyDerivationMode DerivationMode; + + /// + /// Represents the number of bytes contained within the derived key. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly Int32 DerivedKeyLength; + + /// + /// Represents a bit field that is used to derive key bits from the current . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly ISecureMemory KeySource; + + /// + /// Represents the lazily-initialized PBKDF2 algorithm provider for the current . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly Lazy LazyPbkdf2Provider; + } + + /// + /// Represents a cryptographic algorithm and source bits for a derived key, encapsulates key derivation operations and secures + /// key bits in memory. + /// + /// + /// is the default implementation of . + /// + public abstract class CryptographicKey : Instrument, ICryptographicKey + { + /// + /// Initializes a new instance of the class. + /// + [DebuggerHidden] + protected CryptographicKey() + : base(ConcurrencyControlMode.SingleThreadLock) + { + return; + } + + /// + /// Converts the value of the current to a secure bit field. + /// + /// + /// A secure bit field containing a representation of the current . + /// + /// + /// The object is disposed. + /// + [DebuggerHidden] + public ISecureMemory ToSecureMemory() + { + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + return ToSecureMemory(controlToken); + } + } + + /// + /// Generates private key source bytes using the specified, arbitrary length bit field. + /// + /// + /// A non-empty byte array from which the cryptographic key is derived. + /// + /// + /// The desired number of key source bytes. + /// + /// + /// The key source bytes. + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// is less than or equal to zero. + /// + [DebuggerHidden] + internal static PinnedMemory DeriveKeySourceBytesFromKeyMaterial(Byte[] keyMaterial, Int32 keySourceLengthInBytes) + { + var hashingProcessor = new HashingProcessor(RandomnessProvider); + + using (var saltBytes = new ReadOnlyPinnedMemory(hashingProcessor.CalculateHash(keyMaterial, PasswordSaltHashingAlgorithm))) + { + var pbkdf2Provider = new Rfc2898DeriveBytes(keyMaterial, saltBytes, Pbkdf2MinimumIterationCount); + return new PinnedMemory(pbkdf2Provider.GetBytes(keySourceLengthInBytes.RejectIf().IsLessThanOrEqualTo(0, nameof(keySourceLengthInBytes)))); + } + } + + /// + /// Generates private key source bytes using the specified password. + /// + /// + /// A Unicode password with length greater than or equal to thirteen characters from which the cryptographic key is derived. + /// + /// + /// The desired number of key source bytes. + /// + /// + /// The key source bytes. + /// + /// + /// is shorter than thirteen characters. + /// + /// + /// is . + /// + /// + /// is less than or equal to zero. + /// + /// + /// is disposed. + /// + [DebuggerHidden] + internal static PinnedMemory DeriveKeySourceBytesFromPassword(IPassword password, Int32 keySourceLengthInBytes) + { + _ = password.RejectIf().IsNull(nameof(password)).OrIf(argument => argument.GetCharacterLength() < MinimumPasswordLength, nameof(password), $"The specified password is shorter than {MinimumPasswordLength} characters."); + + using (var passwordBytes = new ReadOnlyPinnedMemory(Password.GetPasswordPlaintextBytes(password))) + { + return DeriveKeySourceBytesFromKeyMaterial(passwordBytes, keySourceLengthInBytes); + } + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Converts the value of the current to a secure bit field. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + /// + /// A secure bit field containing a representation of the current . + /// + protected abstract ISecureMemory ToSecureMemory(IConcurrencyControlToken controlToken); + + /// + /// Represents the minimum allowable length, in characters, of a password. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal const Int32 MinimumPasswordLength = 13; + + /// + /// Represents the hashing algorithm that is used to produce salt bytes from a password. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal const HashingAlgorithmSpecification PasswordSaltHashingAlgorithm = HashingAlgorithmSpecification.ShaTwo512; + + /// + /// Represents the minimum number of iterations for PBKDF2-derived keys. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal const Int32 Pbkdf2MinimumIterationCount = 16384; + + /// + /// Represents the cryptographic key algorithm that is used to obscure serialized bit fields. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal const SymmetricAlgorithmSpecification SecureMemoryEncryptionAlgorithm = SymmetricAlgorithmSpecification.Aes128Cbc; + + /// + /// Represents the block size, in bytes, for the cryptographic key algorithm that is used to obscure serialized bit fields. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal const Int32 SecureMemoryEncryptionAlgorithmBlockSizeInBytes = 16; + + /// + /// Represents the encoding that is used when evaluating passwords. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal static readonly Encoding PasswordEncoding = Encoding.Unicode; + + /// + /// Represents a static random number generator instance. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal static readonly RandomNumberGenerator RandomnessProvider = HardenedRandomNumberGenerator.Instance; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKeyDerivationMode.cs b/src/RapidField.SolidInstruments.Cryptography/CryptographicKeyDerivationMode.cs similarity index 88% rename from src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKeyDerivationMode.cs rename to src/RapidField.SolidInstruments.Cryptography/CryptographicKeyDerivationMode.cs index d5d3e0c6..458d23ff 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKeyDerivationMode.cs +++ b/src/RapidField.SolidInstruments.Cryptography/CryptographicKeyDerivationMode.cs @@ -4,12 +4,12 @@ using System; -namespace RapidField.SolidInstruments.Cryptography.Symmetric +namespace RapidField.SolidInstruments.Cryptography { /// - /// Defines options for deriving key bits from an . + /// Defines options for deriving key bits from an . /// - public enum SymmetricKeyDerivationMode : Byte + public enum CryptographicKeyDerivationMode : Byte { /// /// The derivation mode is not specified. @@ -30,8 +30,8 @@ public enum SymmetricKeyDerivationMode : Byte /// The key bit field is divided into blocks that are XORed sequentially, producing an output key of the desired length. /// /// - /// No key bits are discarded. Some distinct instances produce identical key outputs due to the - /// fact that patterned substitutions produce identical XOR outputs. + /// No key bits are discarded. Some distinct instances produce identical key + /// outputs due to the fact that patterned substitutions produce identical XOR outputs. /// XorLayering = 0x02, diff --git a/src/RapidField.SolidInstruments.Cryptography/ICryptographicKey.cs b/src/RapidField.SolidInstruments.Cryptography/ICryptographicKey.cs new file mode 100644 index 00000000..c4572e43 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/ICryptographicKey.cs @@ -0,0 +1,57 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.Cryptography +{ + /// + /// Represents a cryptographic algorithm and source bits for a derived key, encapsulates key derivation operations and secures + /// key bits in memory. + /// + /// + /// The type of the cryptographic algorithm for which a key is derived. + /// + public interface ICryptographicKey : ICryptographicKey + where TAlgorithm : struct, Enum + { + /// + /// Converts the current to cryptographic key plaintext with correct bit-length + /// for the encryption mode specified by . + /// + /// + /// The derived key. + /// + /// + /// The object is disposed. + /// + public ISecureMemory ToDerivedKeyBytes(); + + /// + /// Gets the symmetric-key algorithm for which a key is derived. + /// + public TAlgorithm Algorithm + { + get; + } + } + + /// + /// Represents a cryptographic algorithm and source bits for a derived key, encapsulates key derivation operations and secures + /// key bits in memory. + /// + public interface ICryptographicKey : IAsyncDisposable, IDisposable + { + /// + /// Converts the value of the current to a secure bit field. + /// + /// + /// A secure bit field containing a representation of the current . + /// + /// + /// The object is disposed. + /// + public ISecureMemory ToSecureMemory(); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/CascadingSymmetricKey.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/CascadingSymmetricKey.cs index da9d51fc..d2f4dfc6 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Symmetric/CascadingSymmetricKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Symmetric/CascadingSymmetricKey.cs @@ -3,7 +3,6 @@ // ================================================================================================================================= using RapidField.SolidInstruments.Collections; -using RapidField.SolidInstruments.Core; using RapidField.SolidInstruments.Core.ArgumentValidation; using RapidField.SolidInstruments.Core.Concurrency; using RapidField.SolidInstruments.Cryptography.Secrets; @@ -22,7 +21,7 @@ namespace RapidField.SolidInstruments.Cryptography.Symmetric /// /// is the default implementation of . /// - public sealed class CascadingSymmetricKey : Instrument, ICascadingSymmetricKey + public sealed class CascadingSymmetricKey : CryptographicKey, ICascadingSymmetricKey { /// /// Initializes a new instance of the class. @@ -34,12 +33,12 @@ public sealed class CascadingSymmetricKey : Instrument, ICascadingSymmetricKey /// The layered algorithm specifications. /// /// - /// is equal to -or- one or more - /// algorithm layers are equal to . + /// is equal to -or- one or + /// more algorithm layers are equal to . /// [DebuggerHidden] - private CascadingSymmetricKey(SymmetricKeyDerivationMode derivationMode, params SymmetricAlgorithmSpecification[] algorithms) - : base(ConcurrencyControlMode.SingleThreadLock) + private CascadingSymmetricKey(CryptographicKeyDerivationMode derivationMode, params SymmetricAlgorithmSpecification[] algorithms) + : base() { var keys = new SymmetricKey[algorithms.Length]; @@ -59,7 +58,7 @@ private CascadingSymmetricKey(SymmetricKeyDerivationMode derivationMode, params /// [DebuggerHidden] private CascadingSymmetricKey(ISymmetricKey[] keys) - : base(ConcurrencyControlMode.SingleThreadLock) + : base() { Keys = keys; } @@ -82,6 +81,7 @@ private CascadingSymmetricKey(ISymmetricKey[] keys) /// /// is disposed. /// + [DebuggerHidden] public static CascadingSymmetricKey FromPassword(IPassword password) => FromPassword(password, DefaultDerivationMode, DefaultFirstLayerAlgorithm, DefaultSecondLayerAlgorithm); /// @@ -91,7 +91,7 @@ private CascadingSymmetricKey(ISymmetricKey[] keys) /// A Unicode password with length greater than or equal to thirteen characters from which the symmetric key is derived. /// /// - /// The mode used to derive the generated keys. The default value is . + /// The mode used to derive the generated keys. The default value is . /// /// /// The algorithm for the first (inner) layer of encryption. The default value is @@ -111,13 +111,14 @@ private CascadingSymmetricKey(ISymmetricKey[] keys) /// is . /// /// - /// is equal to -or- one or more - /// algorithm layers are equal to . + /// is equal to -or- one or + /// more algorithm layers are equal to . /// /// /// is disposed. /// - public static CascadingSymmetricKey FromPassword(IPassword password, SymmetricKeyDerivationMode derivationMode, SymmetricAlgorithmSpecification firstLayerAlgorithm, SymmetricAlgorithmSpecification secondLayerAlgorithm) => FromPassword(password, derivationMode, new SymmetricAlgorithmSpecification[] { firstLayerAlgorithm, secondLayerAlgorithm }); + [DebuggerHidden] + public static CascadingSymmetricKey FromPassword(IPassword password, CryptographicKeyDerivationMode derivationMode, SymmetricAlgorithmSpecification firstLayerAlgorithm, SymmetricAlgorithmSpecification secondLayerAlgorithm) => FromPassword(password, derivationMode, new SymmetricAlgorithmSpecification[] { firstLayerAlgorithm, secondLayerAlgorithm }); /// /// Derives a new from the specified password. @@ -126,7 +127,7 @@ private CascadingSymmetricKey(ISymmetricKey[] keys) /// A Unicode password with length greater than or equal to thirteen characters from which the symmetric key is derived. /// /// - /// The mode used to derive the generated keys. The default value is . + /// The mode used to derive the generated keys. The default value is . /// /// /// The algorithm for the first (inner-most) layer of encryption. @@ -147,13 +148,14 @@ private CascadingSymmetricKey(ISymmetricKey[] keys) /// is . /// /// - /// is equal to -or- one or more - /// algorithm layers are equal to . + /// is equal to -or- one or + /// more algorithm layers are equal to . /// /// /// is disposed. /// - public static CascadingSymmetricKey FromPassword(IPassword password, SymmetricKeyDerivationMode derivationMode, SymmetricAlgorithmSpecification firstLayerAlgorithm, SymmetricAlgorithmSpecification secondLayerAlgorithm, SymmetricAlgorithmSpecification thirdLayerAlgorithm) => FromPassword(password, derivationMode, new SymmetricAlgorithmSpecification[] { firstLayerAlgorithm, secondLayerAlgorithm, thirdLayerAlgorithm }); + [DebuggerHidden] + public static CascadingSymmetricKey FromPassword(IPassword password, CryptographicKeyDerivationMode derivationMode, SymmetricAlgorithmSpecification firstLayerAlgorithm, SymmetricAlgorithmSpecification secondLayerAlgorithm, SymmetricAlgorithmSpecification thirdLayerAlgorithm) => FromPassword(password, derivationMode, new SymmetricAlgorithmSpecification[] { firstLayerAlgorithm, secondLayerAlgorithm, thirdLayerAlgorithm }); /// /// Derives a new from the specified password. @@ -162,7 +164,7 @@ private CascadingSymmetricKey(ISymmetricKey[] keys) /// A Unicode password with length greater than or equal to thirteen characters from which the symmetric key is derived. /// /// - /// The mode used to derive the generated keys. The default value is . + /// The mode used to derive the generated keys. The default value is . /// /// /// The algorithm for the first (inner-most) layer of encryption. @@ -186,13 +188,14 @@ private CascadingSymmetricKey(ISymmetricKey[] keys) /// is . /// /// - /// is equal to -or- one or more - /// algorithm layers are equal to . + /// is equal to -or- one or + /// more algorithm layers are equal to . /// /// /// is disposed. /// - public static CascadingSymmetricKey FromPassword(IPassword password, SymmetricKeyDerivationMode derivationMode, SymmetricAlgorithmSpecification firstLayerAlgorithm, SymmetricAlgorithmSpecification secondLayerAlgorithm, SymmetricAlgorithmSpecification thirdLayerAlgorithm, SymmetricAlgorithmSpecification fourthLayerAlgorithm) => FromPassword(password, derivationMode, new SymmetricAlgorithmSpecification[] { firstLayerAlgorithm, secondLayerAlgorithm, thirdLayerAlgorithm, fourthLayerAlgorithm }); + [DebuggerHidden] + public static CascadingSymmetricKey FromPassword(IPassword password, CryptographicKeyDerivationMode derivationMode, SymmetricAlgorithmSpecification firstLayerAlgorithm, SymmetricAlgorithmSpecification secondLayerAlgorithm, SymmetricAlgorithmSpecification thirdLayerAlgorithm, SymmetricAlgorithmSpecification fourthLayerAlgorithm) => FromPassword(password, derivationMode, new SymmetricAlgorithmSpecification[] { firstLayerAlgorithm, secondLayerAlgorithm, thirdLayerAlgorithm, fourthLayerAlgorithm }); /// /// Creates a new instance of a using the specified secure bit field. @@ -209,6 +212,7 @@ private CascadingSymmetricKey(ISymmetricKey[] keys) /// /// is . /// + [DebuggerHidden] public static CascadingSymmetricKey FromSecureMemory(ISecureMemory secureMemory) { secureMemory.RejectIf().IsNull(nameof(secureMemory)).OrIf(argument => argument.LengthInBytes != SerializedLength, nameof(secureMemory), "The specified memory field is invalid."); @@ -251,20 +255,21 @@ public static CascadingSymmetricKey FromSecureMemory(ISecureMemory secureMemory) /// Generates a new . /// /// - /// Keys generated by this method employ for key + /// Keys generated by this method employ for key /// derivation, for first (inner) layer transformation and /// for second (outer) layer transformation. /// /// /// A new . /// + [DebuggerHidden] public static CascadingSymmetricKey New() => new CascadingSymmetricKey(DefaultDerivationMode, DefaultFirstLayerAlgorithm, DefaultSecondLayerAlgorithm); /// /// Generates a new . /// /// - /// The mode used to derive the generated keys. The default value is . + /// The mode used to derive the generated keys. The default value is . /// /// /// The algorithm for the first (inner) layer of encryption. The default value is @@ -278,16 +283,17 @@ public static CascadingSymmetricKey FromSecureMemory(ISecureMemory secureMemory) /// A new . /// /// - /// is equal to -or- one or more - /// algorithm layers are equal to . + /// is equal to -or- one or + /// more algorithm layers are equal to . /// - public static CascadingSymmetricKey New(SymmetricKeyDerivationMode derivationMode, SymmetricAlgorithmSpecification firstLayerAlgorithm, SymmetricAlgorithmSpecification secondLayerAlgorithm) => new CascadingSymmetricKey(derivationMode, firstLayerAlgorithm, secondLayerAlgorithm); + [DebuggerHidden] + public static CascadingSymmetricKey New(CryptographicKeyDerivationMode derivationMode, SymmetricAlgorithmSpecification firstLayerAlgorithm, SymmetricAlgorithmSpecification secondLayerAlgorithm) => new CascadingSymmetricKey(derivationMode, firstLayerAlgorithm, secondLayerAlgorithm); /// /// Generates a new . /// /// - /// The mode used to derive the generated keys. The default value is . + /// The mode used to derive the generated keys. The default value is . /// /// /// The algorithm for the first (inner-most) layer of encryption. @@ -302,16 +308,17 @@ public static CascadingSymmetricKey FromSecureMemory(ISecureMemory secureMemory) /// A new . /// /// - /// is equal to -or- one or more - /// algorithm layers are equal to . + /// is equal to -or- one or + /// more algorithm layers are equal to . /// - public static CascadingSymmetricKey New(SymmetricKeyDerivationMode derivationMode, SymmetricAlgorithmSpecification firstLayerAlgorithm, SymmetricAlgorithmSpecification secondLayerAlgorithm, SymmetricAlgorithmSpecification thirdLayerAlgorithm) => new CascadingSymmetricKey(derivationMode, firstLayerAlgorithm, secondLayerAlgorithm, thirdLayerAlgorithm); + [DebuggerHidden] + public static CascadingSymmetricKey New(CryptographicKeyDerivationMode derivationMode, SymmetricAlgorithmSpecification firstLayerAlgorithm, SymmetricAlgorithmSpecification secondLayerAlgorithm, SymmetricAlgorithmSpecification thirdLayerAlgorithm) => new CascadingSymmetricKey(derivationMode, firstLayerAlgorithm, secondLayerAlgorithm, thirdLayerAlgorithm); /// /// Generates a new . /// /// - /// The mode used to derive the generated keys. The default value is . + /// The mode used to derive the generated keys. The default value is . /// /// /// The algorithm for the first (inner-most) layer of encryption. @@ -329,57 +336,82 @@ public static CascadingSymmetricKey FromSecureMemory(ISecureMemory secureMemory) /// A new . /// /// - /// is equal to -or- one or more - /// algorithm layers are equal to . + /// is equal to -or- one or + /// more algorithm layers are equal to . /// - public static CascadingSymmetricKey New(SymmetricKeyDerivationMode derivationMode, SymmetricAlgorithmSpecification firstLayerAlgorithm, SymmetricAlgorithmSpecification secondLayerAlgorithm, SymmetricAlgorithmSpecification thirdLayerAlgorithm, SymmetricAlgorithmSpecification fourthLayerAlgorithm) => new CascadingSymmetricKey(derivationMode, firstLayerAlgorithm, secondLayerAlgorithm, thirdLayerAlgorithm, fourthLayerAlgorithm); + [DebuggerHidden] + public static CascadingSymmetricKey New(CryptographicKeyDerivationMode derivationMode, SymmetricAlgorithmSpecification firstLayerAlgorithm, SymmetricAlgorithmSpecification secondLayerAlgorithm, SymmetricAlgorithmSpecification thirdLayerAlgorithm, SymmetricAlgorithmSpecification fourthLayerAlgorithm) => new CascadingSymmetricKey(derivationMode, firstLayerAlgorithm, secondLayerAlgorithm, thirdLayerAlgorithm, fourthLayerAlgorithm); + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) + { + try + { + if (disposing) + { + foreach (var key in Keys) + { + key.Dispose(); + } + } + } + finally + { + base.Dispose(disposing); + } + } /// /// Converts the value of the current to a secure bit field. /// + /// + /// A token that represents and manages contextual thread safety. + /// /// - /// A secure bit field containing a representation of the current . + /// A secure bit field containing a representation of the current . /// - public ISecureMemory ToSecureMemory() + protected sealed override ISecureMemory ToSecureMemory(IConcurrencyControlToken controlToken) { var secureMemory = new SecureMemory(SerializedLength); try { - using (var controlToken = StateControl.Enter()) + secureMemory.Access(pinnedMemory => { - secureMemory.Access(pinnedMemory => - { - var keyLength = SymmetricKey.SerializedLength; + var keyLength = SymmetricKey.SerializedLength; - for (var i = 0; i < MaximumDepth; i++) + for (var i = 0; i < MaximumDepth; i++) + { + if (i < Depth) { - if (i < Depth) + using (var keyMemory = Keys.ElementAt(i).ToSecureMemory()) { - using (var keyMemory = Keys.ElementAt(i).ToSecureMemory()) + keyMemory.Access(key => { - keyMemory.Access(key => - { - // Copy the key buffers out to the result buffer. - Array.Copy(key, 0, pinnedMemory, (keyLength * i), keyLength); - }); - } - - continue; + // Copy the key buffers out to the result buffer. + Array.Copy(key, 0, pinnedMemory, (keyLength * i), keyLength); + }); } - // Fill the unused segments with random bytes. - var randomBytes = new Byte[keyLength]; - HardenedRandomNumberGenerator.Instance.GetBytes(randomBytes); - Array.Copy(randomBytes, 0, pinnedMemory, (keyLength * i), keyLength); + continue; } - // Append the depth as a 16-bit integer. - Buffer.BlockCopy(BitConverter.GetBytes(Convert.ToUInt16(Depth)), 0, pinnedMemory, (SerializedLength - sizeof(UInt16)), sizeof(UInt16)); - }); + // Fill the unused segments with random bytes. + var randomBytes = new Byte[keyLength]; + HardenedRandomNumberGenerator.Instance.GetBytes(randomBytes); + Array.Copy(randomBytes, 0, pinnedMemory, (keyLength * i), keyLength); + } - return secureMemory; - } + // Append the depth as a 16-bit integer. + Buffer.BlockCopy(BitConverter.GetBytes(Convert.ToUInt16(Depth)), 0, pinnedMemory, (SerializedLength - sizeof(UInt16)), sizeof(UInt16)); + }); + + return secureMemory; } catch { @@ -388,30 +420,6 @@ public ISecureMemory ToSecureMemory() } } - /// - /// Releases all resources consumed by the current . - /// - /// - /// A value indicating whether or not managed resources should be released. - /// - protected override void Dispose(Boolean disposing) - { - try - { - if (disposing) - { - foreach (var key in Keys) - { - key.Dispose(); - } - } - } - finally - { - base.Dispose(disposing); - } - } - /// /// Derives a new from the specified password. /// @@ -419,7 +427,7 @@ protected override void Dispose(Boolean disposing) /// A Unicode password with length greater than or equal to thirteen characters from which the symmetric key is derived. /// /// - /// The mode used to derive the generated keys. The default value is . + /// The mode used to derive the generated keys. The default value is . /// /// /// An ordered array of algorithms. @@ -434,21 +442,21 @@ protected override void Dispose(Boolean disposing) /// is -or- is empty. /// /// - /// is equal to -or- one or more - /// algorithm layers are equal to . + /// is equal to -or- one or + /// more algorithm layers are equal to . /// /// /// is disposed. /// [DebuggerHidden] - private static CascadingSymmetricKey FromPassword(IPassword password, SymmetricKeyDerivationMode derivationMode, SymmetricAlgorithmSpecification[] algorithms) + private static CascadingSymmetricKey FromPassword(IPassword password, CryptographicKeyDerivationMode derivationMode, SymmetricAlgorithmSpecification[] algorithms) { var keyDepth = algorithms.RejectIf().IsNullOrEmpty(nameof(algorithms)).TargetArgument.Length; var singleKeySourceLengthInBytes = SymmetricKey.KeySourceLengthInBytes; var totalSourceLengthInBytes = singleKeySourceLengthInBytes * keyDepth; var keys = new ISymmetricKey[keyDepth]; - using (var keySourceBytes = SymmetricKey.DeriveKeySourceBytesFromPassword(password, totalSourceLengthInBytes)) + using (var keySourceBytes = CryptographicKey.DeriveKeySourceBytesFromPassword(password, totalSourceLengthInBytes)) { for (var i = 0; i < keyDepth; i++) { @@ -487,7 +495,7 @@ public IEnumerable Keys /// Represents the default derivation mode for new keys. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const SymmetricKeyDerivationMode DefaultDerivationMode = SymmetricKeyDerivationMode.Pbkdf2; + private const CryptographicKeyDerivationMode DefaultDerivationMode = CryptographicKeyDerivationMode.Pbkdf2; /// /// Represents the default inner-layer symmetric-key algorithm specification for new keys. diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/ICascadingSymmetricKey.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/ICascadingSymmetricKey.cs index 494ea294..49412e43 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Symmetric/ICascadingSymmetricKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Symmetric/ICascadingSymmetricKey.cs @@ -11,16 +11,8 @@ namespace RapidField.SolidInstruments.Cryptography.Symmetric /// Represents a series of instances that constitute instructions for applying cascading encryption /// and decryption. /// - public interface ICascadingSymmetricKey : IAsyncDisposable, IDisposable + public interface ICascadingSymmetricKey : ICryptographicKey { - /// - /// Converts the value of the current to a secure bit field. - /// - /// - /// A secure bit field containing a representation of the current . - /// - public ISecureMemory ToSecureMemory(); - /// /// Gets the number of layers of encryption that a resulting transform will apply. /// diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/ISymmetricKey.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/ISymmetricKey.cs index 61795e30..df1bb293 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Symmetric/ISymmetricKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Symmetric/ISymmetricKey.cs @@ -2,39 +2,13 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= -using System; - namespace RapidField.SolidInstruments.Cryptography.Symmetric { /// /// Represents a symmetric-key algorithm and source bits for a derived key, encapsulates key derivation operations and secures /// key bits in memory. /// - public interface ISymmetricKey : IAsyncDisposable, IDisposable + public interface ISymmetricKey : ICryptographicKey { - /// - /// Converts the current to symmetric-key plaintext with correct bit-length for the encryption - /// mode specified by . - /// - /// - /// The derived key. - /// - public ISecureMemory ToDerivedKeyBytes(); - - /// - /// Converts the value of the current to a secure bit field. - /// - /// - /// A secure bit field containing a representation of the current . - /// - public ISecureMemory ToSecureMemory(); - - /// - /// Gets the symmetric-key algorithm for which a key is derived. - /// - public SymmetricAlgorithmSpecification Algorithm - { - get; - } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKey.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKey.cs index 9aa32a9a..88d2818c 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKey.cs @@ -5,19 +5,10 @@ using RapidField.SolidInstruments.Collections; using RapidField.SolidInstruments.Core; using RapidField.SolidInstruments.Core.ArgumentValidation; -using RapidField.SolidInstruments.Core.Concurrency; -using RapidField.SolidInstruments.Core.Extensions; using RapidField.SolidInstruments.Cryptography.Extensions; -using RapidField.SolidInstruments.Cryptography.Hashing; using RapidField.SolidInstruments.Cryptography.Secrets; using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; -using System.Security; -using System.Security.Cryptography; -using System.Text; -using System.Threading; namespace RapidField.SolidInstruments.Cryptography.Symmetric { @@ -28,7 +19,7 @@ namespace RapidField.SolidInstruments.Cryptography.Symmetric /// /// is the default implementation of . /// - public sealed class SymmetricKey : Instrument, ISymmetricKey + public sealed class SymmetricKey : CryptographicKey, ISymmetricKey { /// /// Initializes a new instance of the class. @@ -44,25 +35,13 @@ public sealed class SymmetricKey : Instrument, ISymmetricKey /// /// /// is equal to or - /// is equal to . + /// is equal to . /// [DebuggerHidden] - private SymmetricKey(SymmetricAlgorithmSpecification algorithm, SymmetricKeyDerivationMode derivationMode, PinnedMemory keySource) - : base(ConcurrencyControlMode.SingleThreadLock) + private SymmetricKey(SymmetricAlgorithmSpecification algorithm, CryptographicKeyDerivationMode derivationMode, PinnedMemory keySource) + : base(algorithm, derivationMode, keySource, algorithm.ToKeyBitLength()) { - Algorithm = algorithm.RejectIf().IsEqualToValue(SymmetricAlgorithmSpecification.Unspecified, nameof(algorithm)); - DerivationMode = derivationMode.RejectIf().IsEqualToValue(SymmetricKeyDerivationMode.Unspecified, nameof(derivationMode)); - KeySource = new SecureMemory(KeySourceLengthInBytes); - LazyPbkdf2Provider = new Lazy(InitializePbkdf2Algorithm, LazyThreadSafetyMode.ExecutionAndPublication); - - // Gather information about the derived key. - var keyBitLength = algorithm.ToKeyBitLength(); - DerivedKeyLength = (keyBitLength / 8); - BlockWordCount = (keyBitLength / 32); - BlockCount = (KeySourceWordCount / BlockWordCount); - - // Copy in the key source bits. - KeySource.Access(memory => Array.Copy(keySource, memory, memory.Length)); + return; } /// @@ -83,6 +62,7 @@ private SymmetricKey(SymmetricAlgorithmSpecification algorithm, SymmetricKeyDeri /// /// is disposed. /// + [DebuggerHidden] public static SymmetricKey FromPassword(IPassword password) => FromPassword(password, DefaultAlgorithm); /// @@ -110,6 +90,7 @@ private SymmetricKey(SymmetricAlgorithmSpecification algorithm, SymmetricKeyDeri /// /// is disposed. /// + [DebuggerHidden] public static SymmetricKey FromPassword(IPassword password, SymmetricAlgorithmSpecification algorithm) => FromPassword(password, algorithm, DefaultDerivationMode); /// @@ -123,7 +104,7 @@ private SymmetricKey(SymmetricAlgorithmSpecification algorithm, SymmetricKeyDeri /// . /// /// - /// The mode used to derive the generated key. The default value is . + /// The mode used to derive the generated key. The default value is . /// /// /// A new . @@ -136,16 +117,17 @@ private SymmetricKey(SymmetricAlgorithmSpecification algorithm, SymmetricKeyDeri /// /// /// is equal to -or- - /// is equal to . + /// is equal to . /// /// /// is disposed. /// - public static SymmetricKey FromPassword(IPassword password, SymmetricAlgorithmSpecification algorithm, SymmetricKeyDerivationMode derivationMode) + [DebuggerHidden] + public static SymmetricKey FromPassword(IPassword password, SymmetricAlgorithmSpecification algorithm, CryptographicKeyDerivationMode derivationMode) { using (var keySource = DeriveKeySourceBytesFromPassword(password, KeySourceLengthInBytes)) { - return New(algorithm.RejectIf().IsEqualToValue(SymmetricAlgorithmSpecification.Unspecified, nameof(algorithm)), derivationMode.RejectIf().IsEqualToValue(SymmetricKeyDerivationMode.Unspecified, nameof(derivationMode)), keySource); + return New(algorithm.RejectIf().IsEqualToValue(SymmetricAlgorithmSpecification.Unspecified, nameof(algorithm)), derivationMode.RejectIf().IsEqualToValue(CryptographicKeyDerivationMode.Unspecified, nameof(derivationMode)), keySource); } } @@ -164,6 +146,7 @@ public static SymmetricKey FromPassword(IPassword password, SymmetricAlgorithmSp /// /// is . /// + [DebuggerHidden] public static SymmetricKey FromSecureMemory(ISecureMemory secureMemory) { secureMemory.RejectIf().IsNull(nameof(secureMemory)).OrIf(argument => argument.LengthInBytes != SerializedLength, nameof(secureMemory), "The specified memory field is invalid."); @@ -188,7 +171,7 @@ public static SymmetricKey FromSecureMemory(ISecureMemory secureMemory) { Array.Copy(plaintextBuffer, KeySourceSecureMemoryIndex, keySource, 0, KeySourceLengthInBytes); var algorithm = (SymmetricAlgorithmSpecification)plaintextBuffer[AlgorithmSecureMemoryIndex]; - var derivationMode = (SymmetricKeyDerivationMode)plaintextBuffer[DerivationModeSecureMemoryIndex]; + var derivationMode = (CryptographicKeyDerivationMode)plaintextBuffer[DerivationModeSecureMemoryIndex]; key = new SymmetricKey(algorithm, derivationMode, keySource); } } @@ -206,12 +189,13 @@ public static SymmetricKey FromSecureMemory(ISecureMemory secureMemory) /// Generates a new . /// /// - /// Keys generated by this method employ for key + /// Keys generated by this method employ for key /// derivation and for transformation. /// /// /// A new . /// + [DebuggerHidden] public static SymmetricKey New() => New(DefaultAlgorithm); /// @@ -227,6 +211,7 @@ public static SymmetricKey FromSecureMemory(ISecureMemory secureMemory) /// /// is equal to . /// + [DebuggerHidden] public static SymmetricKey New(SymmetricAlgorithmSpecification algorithm) => New(algorithm, DefaultDerivationMode); /// @@ -237,16 +222,17 @@ public static SymmetricKey FromSecureMemory(ISecureMemory secureMemory) /// . /// /// - /// The mode used to derive the generated key. The default value is . + /// The mode used to derive the generated key. The default value is . /// /// /// A new . /// /// /// is equal to -or- - /// is equal to . + /// is equal to . /// - public static SymmetricKey New(SymmetricAlgorithmSpecification algorithm, SymmetricKeyDerivationMode derivationMode) + [DebuggerHidden] + public static SymmetricKey New(SymmetricAlgorithmSpecification algorithm, CryptographicKeyDerivationMode derivationMode) { using (var keySource = new PinnedMemory(KeySourceLengthInBytes, true)) { @@ -255,237 +241,6 @@ public static SymmetricKey New(SymmetricAlgorithmSpecification algorithm, Symmet } } - /// - /// Converts the current to symmetric-key plaintext with correct bit-length for the encryption - /// mode specified by . - /// - /// - /// The derived key. - /// - [DebuggerHidden] - public ISecureMemory ToDerivedKeyBytes() - { - var result = new SecureMemory(DerivedKeyLength); - - try - { - using (var controlToken = StateControl.Enter()) - { - if (DerivationMode == SymmetricKeyDerivationMode.Pbkdf2) - { - try - { - result.Access(memory => - { - // Perform PBKDF2 key derivation. - var keyBytes = new Span(Pbkdf2Provider.GetBytes(DerivedKeyLength)); - keyBytes.CopyTo(memory); - keyBytes.Clear(); - }); - } - finally - { - Pbkdf2Provider.Reset(); - } - - return result; - } - - using (var sourceWords = new PinnedMemory(KeySourceWordCount, true)) - { - KeySource.Access(memory => - { - // Convert the source bit field to an array of 32-bit words. - Buffer.BlockCopy(memory, 0, sourceWords, 0, KeySourceLengthInBytes); - }); - - using (var transformedWords = new PinnedMemory(BlockWordCount, true)) - { - // Copy out the first block. If nothing further is done, this satisfies truncation mode. - Array.Copy(sourceWords, transformedWords, BlockWordCount); - - switch (DerivationMode) - { - case SymmetricKeyDerivationMode.Truncation: - - break; - - case SymmetricKeyDerivationMode.XorLayering: - - for (var i = 1; i < BlockCount; i++) - { - for (var j = 0; j < BlockWordCount; j++) - { - // Perform the XOR layering operation. - transformedWords[j] = (transformedWords[j] ^ sourceWords[(i * BlockWordCount) + j]); - } - } - - break; - - case SymmetricKeyDerivationMode.XorLayeringWithSubstitution: - - for (var i = 1; i < BlockCount; i++) - { - for (var j = 0; j < BlockWordCount; j++) - { - // Perform the XOR layering operation with substitution. - transformedWords[j] = (SubstituteWord(transformedWords[j]) ^ sourceWords[(i * BlockWordCount) + j]); - } - } - - break; - - default: - - throw new UnsupportedSpecificationException($"The specified key derivation mode, {DerivationMode}, is not supported."); - } - - result.Access(memory => - { - // Copy out the key bits. - var keyBytes = new Byte[DerivedKeyLength]; - Buffer.BlockCopy(transformedWords, 0, keyBytes, 0, DerivedKeyLength); - keyBytes.CopyTo(memory); - - for (var i = 0; i < DerivedKeyLength; i++) - { - keyBytes[i] = 0x00; - } - }); - } - } - - return result; - } - } - catch - { - result?.Dispose(); - throw new SecurityException("Key derivation failed."); - } - } - - /// - /// Converts the value of the current to a secure bit field. - /// - /// - /// A secure bit field containing a representation of the current . - /// - [DebuggerHidden] - public ISecureMemory ToSecureMemory() - { - var secureMemory = new SecureMemory(SerializedLength); - - try - { - using (var controlToken = StateControl.Enter()) - { - using (var plaintextMemory = new PinnedMemory(SerializedPlaintextLength, true)) - { - KeySource.Access(pinnedKeySourceMemory => - { - Array.Copy(pinnedKeySourceMemory, 0, plaintextMemory, KeySourceSecureMemoryIndex, KeySourceLengthInBytes); - }); - - plaintextMemory[AlgorithmSecureMemoryIndex] = (Byte)Algorithm; - plaintextMemory[DerivationModeSecureMemoryIndex] = (Byte)DerivationMode; - - using (var cipher = SecureMemoryEncryptionAlgorithm.ToCipher(RandomnessProvider)) - { - using (var initializationVector = new PinnedMemory(cipher.BlockSizeInBytes, true)) - { - RandomnessProvider.GetBytes(initializationVector); - - secureMemory.Access(pinnedMemory => - { - using (var ciphertext = cipher.Encrypt(plaintextMemory, SecureMemoryEncryptionKey, initializationVector)) - { - Array.Copy(ciphertext, 0, pinnedMemory, 0, SerializedLength); - } - }); - } - } - } - } - - return secureMemory; - } - catch - { - secureMemory.Dispose(); - throw new SecurityException("Key serialization failed."); - } - } - - /// - /// Generates private key source bytes using the specified, arbitrary length bit field. - /// - /// - /// A non-empty byte array from which the symmetric key is derived. - /// - /// - /// The desired number of key source bytes. - /// - /// - /// The key source bytes. - /// - /// - /// is empty. - /// - /// - /// is . - /// - /// - /// is less than or equal to zero. - /// - [DebuggerHidden] - internal static PinnedMemory DeriveKeySourceBytesFromKeyMaterial(Byte[] keyMaterial, Int32 keySourceLengthInBytes) - { - var hashingProcessor = new HashingProcessor(RandomnessProvider); - - using (var saltBytes = new ReadOnlyPinnedMemory(hashingProcessor.CalculateHash(keyMaterial, PasswordSaltHashingAlgorithm))) - { - var pbkdf2Provider = new Rfc2898DeriveBytes(keyMaterial, saltBytes, Pbkdf2MinimumIterationCount); - return new PinnedMemory(pbkdf2Provider.GetBytes(keySourceLengthInBytes.RejectIf().IsLessThanOrEqualTo(0, nameof(keySourceLengthInBytes)))); - } - } - - /// - /// Generates private key source bytes using the specified password. - /// - /// - /// A Unicode password with length greater than or equal to thirteen characters from which the symmetric key is derived. - /// - /// - /// The desired number of key source bytes. - /// - /// - /// The key source bytes. - /// - /// - /// is shorter than thirteen characters. - /// - /// - /// is . - /// - /// - /// is less than or equal to zero. - /// - /// - /// is disposed. - /// - [DebuggerHidden] - internal static PinnedMemory DeriveKeySourceBytesFromPassword(IPassword password, Int32 keySourceLengthInBytes) - { - _ = password.RejectIf().IsNull(nameof(password)).OrIf(argument => argument.GetCharacterLength() < MinimumPasswordLength, nameof(password), $"The specified password is shorter than {MinimumPasswordLength} characters."); - - using (var passwordBytes = new ReadOnlyPinnedMemory(Password.GetPasswordPlaintextBytes(password))) - { - return DeriveKeySourceBytesFromKeyMaterial(passwordBytes, keySourceLengthInBytes); - } - } - /// /// Derives a new from the specified, arbitrary length bit field. /// @@ -540,7 +295,7 @@ internal static PinnedMemory DeriveKeySourceBytesFromPassword(IPassword password /// . /// /// - /// The mode used to derive the generated key. The default value is . + /// The mode used to derive the generated key. The default value is . /// /// /// A new . @@ -553,14 +308,14 @@ internal static PinnedMemory DeriveKeySourceBytesFromPassword(IPassword password /// /// /// is equal to -or- - /// is equal to . + /// is equal to . /// [DebuggerHidden] - internal static SymmetricKey FromKeyMaterial(Byte[] keyMaterial, SymmetricAlgorithmSpecification algorithm, SymmetricKeyDerivationMode derivationMode) + internal static SymmetricKey FromKeyMaterial(Byte[] keyMaterial, SymmetricAlgorithmSpecification algorithm, CryptographicKeyDerivationMode derivationMode) { using (var keySource = DeriveKeySourceBytesFromKeyMaterial(keyMaterial, KeySourceLengthInBytes)) { - return New(algorithm.RejectIf().IsEqualToValue(SymmetricAlgorithmSpecification.Unspecified, nameof(algorithm)), derivationMode.RejectIf().IsEqualToValue(SymmetricKeyDerivationMode.Unspecified, nameof(derivationMode)), keySource); + return New(algorithm.RejectIf().IsEqualToValue(SymmetricAlgorithmSpecification.Unspecified, nameof(algorithm)), derivationMode.RejectIf().IsEqualToValue(CryptographicKeyDerivationMode.Unspecified, nameof(derivationMode)), keySource); } } @@ -572,7 +327,7 @@ internal static SymmetricKey FromKeyMaterial(Byte[] keyMaterial, SymmetricAlgori /// . /// /// - /// The mode used to derive the generated key. The default value is . + /// The mode used to derive the generated key. The default value is . /// /// /// A bit field comprising 384 bytes (3,072 bits) from which the private key is derived. @@ -585,10 +340,10 @@ internal static SymmetricKey FromKeyMaterial(Byte[] keyMaterial, SymmetricAlgori /// /// /// is equal to -or- - /// is equal to . + /// is equal to . /// [DebuggerHidden] - internal static SymmetricKey New(SymmetricAlgorithmSpecification algorithm, SymmetricKeyDerivationMode derivationMode, PinnedMemory keySource) => new SymmetricKey(algorithm, derivationMode, keySource.RejectIf(argument => argument.Length != KeySourceLengthInBytes, nameof(keySource), $"The key source is not {KeySourceLengthInBytes} bytes in length.")); + internal static SymmetricKey New(SymmetricAlgorithmSpecification algorithm, CryptographicKeyDerivationMode derivationMode, PinnedMemory keySource) => new SymmetricKey(algorithm, derivationMode, keySource.RejectIf(argument => argument.Length != KeySourceLengthInBytes, nameof(keySource), $"The key source is not {KeySourceLengthInBytes} bytes in length.")); /// /// Releases all resources consumed by the current . @@ -596,310 +351,12 @@ internal static SymmetricKey FromKeyMaterial(Byte[] keyMaterial, SymmetricAlgori /// /// A value indicating whether or not managed resources should be released. /// - protected override void Dispose(Boolean disposing) - { - try - { - if (disposing) - { - SecureMemoryEncryptionKey.Dispose(); - KeySource.Dispose(); - LazyPbkdf2Provider.Dispose(); - } - } - finally - { - base.Dispose(disposing); - } - } - - /// - /// Finalizes static members of the class. - /// - [DebuggerHidden] - private static void FinalizeStaticMembers() => SecureMemoryEncryptionKey.Dispose(); - - /// - /// Substitutes the specified input word for a word derived from . - /// - /// - /// A word to be substituted. - /// - /// - /// The substitution word. - /// - [DebuggerHidden] - private static UInt32 SubstituteWord(UInt32 inputWord) - { - var inputBytes = BitConverter.GetBytes(inputWord); - var substitutionBytes = new Byte[4] { SubstitutionBox[inputBytes[2]], SubstitutionBox[inputBytes[1]], SubstitutionBox[inputBytes[3]], SubstitutionBox[inputBytes[0]] }; - return BitConverter.ToUInt32(substitutionBytes, 0); - } - - /// - /// Initializes an instance of an using the leading bytes of the key source. - /// - /// - /// An instance of an . - /// - [DebuggerHidden] - private Rfc2898DeriveBytes InitializePbkdf2Algorithm() - { - var algorithm = (Rfc2898DeriveBytes)null; - - KeySource.Access(memory => - { - var iterationSumBytes = memory.Take(Pbkdf2IterationSumLengthInBytes); - var saltBytes = memory.Skip(Pbkdf2IterationSumLengthInBytes).Take(Pbkdf2SaltLengthInBytes); - var passwordBytes = memory.Skip(Pbkdf2IterationSumLengthInBytes + Pbkdf2SaltLengthInBytes).Take(Pbkdf2PasswordLengthInBytes); - var iterationCount = Pbkdf2MinimumIterationCount; - - foreach (var iterationSumValue in iterationSumBytes) - { - iterationCount += iterationSumValue; - } - - algorithm = new Rfc2898DeriveBytes(passwordBytes.ToArray(), saltBytes.ToArray(), iterationCount); - }); - - return algorithm; - } - - /// - /// Gets the symmetric-key algorithm for which a key is derived. - /// - public SymmetricAlgorithmSpecification Algorithm - { - get; - } - - /// - /// Gets the substitution bytes that are used to fulfill key derivation operations when is - /// equal to . - /// - /// - /// This property is exposed for testing. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - internal static IEnumerable SubstitutionBoxBytes => SubstitutionBox; - - /// - /// Gets the PBKDF2 algorithm provider for the current . - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private Rfc2898DeriveBytes Pbkdf2Provider => LazyPbkdf2Provider.Value; - - /// - /// Represents the number of bytes comprising . - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - internal const Int32 KeySourceLengthInBytes = (KeySourceWordCount * sizeof(UInt32)); - - /// - /// Represents the minimum allowable length, in characters, of a password. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - internal const Int32 MinimumPasswordLength = 13; - - /// - /// Represents the number of bytes comprising a post-encrypted, serialized representation of a . - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - internal const Int32 SerializedLength = (SerializedPlaintextLength + (SecureMemoryEncryptionAlgorithmBlockSizeInBytes * 2) - AlgorithmLength - DerivationModeLength); - - /// - /// Represents the encoding that is used when evaluating passwords. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - internal static readonly Encoding PasswordEncoding = Encoding.Unicode; - - /// - /// Represents a static random number generator instance. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - internal static readonly RandomNumberGenerator RandomnessProvider = HardenedRandomNumberGenerator.Instance; - - /// - /// Represents the byte length of . - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const Int32 AlgorithmLength = sizeof(SymmetricAlgorithmSpecification); - - /// - /// Represents the byte index of the algorithm byte within an unencrypted, serialized bit field. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const Int32 AlgorithmSecureMemoryIndex = (KeySourceSecureMemoryIndex + KeySourceLengthInBytes); + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); /// /// Represents the default symmetric-key algorithm specification for new keys. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] private const SymmetricAlgorithmSpecification DefaultAlgorithm = SymmetricAlgorithmSpecification.Aes256Cbc; - - /// - /// Represents the default derivation mode for new keys. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const SymmetricKeyDerivationMode DefaultDerivationMode = SymmetricKeyDerivationMode.Pbkdf2; - - /// - /// Represents the byte length of . - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const Int32 DerivationModeLength = sizeof(SymmetricKeyDerivationMode); - - /// - /// Represents the byte index of the derivation mode byte within an unencrypted, serialized bit field. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const Int32 DerivationModeSecureMemoryIndex = (AlgorithmSecureMemoryIndex + AlgorithmLength); - - /// - /// Represents the byte index of the key source within an unencrypted, serialized bit field. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const Int32 KeySourceSecureMemoryIndex = 0; - - /// - /// Represents the exact number of 32-bit key words that are derived from . - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const Int32 KeySourceWordCount = 96; - - /// - /// Represents the hashing algorithm that is used to produce salt bytes from a password. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const HashingAlgorithmSpecification PasswordSaltHashingAlgorithm = HashingAlgorithmSpecification.ShaTwo512; - - /// - /// Represents the number of key source bytes constituting the iteration sum for PBKDF2-derived keys. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const Int32 Pbkdf2IterationSumLengthInBytes = (KeySourceLengthInBytes / 3); - - /// - /// Represents the minimum number of iterations for PBKDF2-derived keys. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const Int32 Pbkdf2MinimumIterationCount = 16384; - - /// - /// Represents the number of key source bytes constituting the password for PBKDF2-derived keys. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const Int32 Pbkdf2PasswordLengthInBytes = (KeySourceLengthInBytes / 3); - - /// - /// Represents the number of key source bytes constituting the salt for PBKDF2-derived keys. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const Int32 Pbkdf2SaltLengthInBytes = (KeySourceLengthInBytes / 3); - - /// - /// Represents the symmetric-key algorithm that is used to obscure serialized bit fields. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const SymmetricAlgorithmSpecification SecureMemoryEncryptionAlgorithm = SymmetricAlgorithmSpecification.Aes128Cbc; - - /// - /// Represents the block size, in bytes, for the symmetric-key algorithm that is used to obscure serialized bit fields. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const Int32 SecureMemoryEncryptionAlgorithmBlockSizeInBytes = 16; - - /// - /// Represents the number of bytes comprising a pre-encrypted, serialized representation of a . - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const Int32 SerializedPlaintextLength = (KeySourceLengthInBytes + AlgorithmLength + DerivationModeLength); - - /// - /// Represents the key for the symmetric-key algorithm that is used to obscure serialized bit fields. - /// - /// - /// The author acknowledges that obscurity does not ensure security. Encrypting sensitive information with a known key does - /// not secure it. This is intended to stand up a barrier against unsophisticated attacks targeting users who have - /// mistakenly exposed their key source. This sequence is fairly arbitrary and can be modified if needed, but there are - /// probably few good reasons to. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private static readonly PinnedMemory SecureMemoryEncryptionKey = new PinnedMemory(new Byte[] - { - 0xaa, 0xf0, 0xcc, 0xff, 0x00, 0x33, 0x0f, 0x55, 0xff, 0xcc, 0xf0, 0xaa, 0x55, 0x0f, 0x33, 0x00 - }); - - /// - /// Represents a finalizer for static members of the class. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private static readonly StaticMemberFinalizer StaticMemberFinalizer = new StaticMemberFinalizer(FinalizeStaticMembers); - - /// - /// Represents the substitution bytes that are used to fulfill key derivation operations when - /// is equal to . - /// - /// - /// This sequence is deliberately and carefully balanced. Modifications can introduce severe security flaws and break the - /// class functionally. Do not modify without very careful consideration and extremely good reason. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private static readonly Byte[] SubstitutionBox = - { - // IMPORTANT Read the remarks above. Inform senior members of the team if a pull request introduces changes here. - 0xff, 0xf0, 0xfd, 0xf2, 0xfb, 0xf4, 0xf9, 0xf6, 0xf7, 0xf8, 0xf5, 0xfa, 0xf3, 0xfc, 0xf1, 0xfe, - 0x0f, 0x00, 0x0d, 0x02, 0x0b, 0x04, 0x09, 0x06, 0x07, 0x08, 0x05, 0x0a, 0x03, 0x0c, 0x01, 0x0e, - 0x4f, 0x40, 0x4d, 0x42, 0x4b, 0x44, 0x49, 0x46, 0x47, 0x48, 0x45, 0x4a, 0x43, 0x4c, 0x41, 0x4e, - 0x2f, 0x20, 0x2d, 0x22, 0x2b, 0x24, 0x29, 0x26, 0x27, 0x28, 0x25, 0x2a, 0x23, 0x2c, 0x21, 0x2e, - 0xbf, 0xb0, 0xbd, 0xb2, 0xbb, 0xb4, 0xb9, 0xb6, 0xb7, 0xb8, 0xb5, 0xba, 0xb3, 0xbc, 0xb1, 0xbe, - 0x9f, 0x90, 0x9d, 0x92, 0x9b, 0x94, 0x99, 0x96, 0x97, 0x98, 0x95, 0x9a, 0x93, 0x9c, 0x91, 0x9e, - 0xdf, 0xd0, 0xdd, 0xd2, 0xdb, 0xd4, 0xd9, 0xd6, 0xd7, 0xd8, 0xd5, 0xda, 0xd3, 0xdc, 0xd1, 0xde, - 0x6f, 0x60, 0x6d, 0x62, 0x6b, 0x64, 0x69, 0x66, 0x67, 0x68, 0x65, 0x6a, 0x63, 0x6c, 0x61, 0x6e, - 0x7f, 0x70, 0x7d, 0x72, 0x7b, 0x74, 0x79, 0x76, 0x77, 0x78, 0x75, 0x7a, 0x73, 0x7c, 0x71, 0x7e, - 0xcf, 0xc0, 0xcd, 0xc2, 0xcb, 0xc4, 0xc9, 0xc6, 0xc7, 0xc8, 0xc5, 0xca, 0xc3, 0xcc, 0xc1, 0xce, - 0x8f, 0x80, 0x8d, 0x82, 0x8b, 0x84, 0x89, 0x86, 0x87, 0x88, 0x85, 0x8a, 0x83, 0x8c, 0x81, 0x8e, - 0xaf, 0xa0, 0xad, 0xa2, 0xab, 0xa4, 0xa9, 0xa6, 0xa7, 0xa8, 0xa5, 0xaa, 0xa3, 0xac, 0xa1, 0xae, - 0x3f, 0x30, 0x3d, 0x32, 0x3b, 0x34, 0x39, 0x36, 0x37, 0x38, 0x35, 0x3a, 0x33, 0x3c, 0x31, 0x3e, - 0x5f, 0x50, 0x5d, 0x52, 0x5b, 0x54, 0x59, 0x56, 0x57, 0x58, 0x55, 0x5a, 0x53, 0x5c, 0x51, 0x5e, - 0x1f, 0x10, 0x1d, 0x12, 0x1b, 0x14, 0x19, 0x16, 0x17, 0x18, 0x15, 0x1a, 0x13, 0x1c, 0x11, 0x1e, - 0xef, 0xe0, 0xed, 0xe2, 0xeb, 0xe4, 0xe9, 0xe6, 0xe7, 0xe8, 0xe5, 0xea, 0xe3, 0xec, 0xe1, 0xee - }; - - /// - /// Represents the number of key blocks for the specified algorithm. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly Int32 BlockCount; - - /// - /// Represents the number of 32-bit words contained within a key block. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly Int32 BlockWordCount; - - /// - /// Represents the mode used to derive key bits from the current . - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly SymmetricKeyDerivationMode DerivationMode; - - /// - /// Represents the number of bytes contained within the derived key. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly Int32 DerivedKeyLength; - - /// - /// Represents a bit field that is used to derive key bits from the current . - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly ISecureMemory KeySource; - - /// - /// Represents the lazily-initialized PBKDF2 algorithm provider for the current . - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly Lazy LazyPbkdf2Provider; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/CascadingSymmetricKeyTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/CascadingSymmetricKeyTests.cs index ad58c859..e17b63a9 100644 --- a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/CascadingSymmetricKeyTests.cs +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/CascadingSymmetricKeyTests.cs @@ -58,7 +58,7 @@ public void FromPassword_ShouldBeRepeatable_UsingFourAlgorithms() var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Ecb; var thirdLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Cbc; var fourthLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Ecb; - var derivationMode = SymmetricKeyDerivationMode.XorLayeringWithSubstitution; + var derivationMode = CryptographicKeyDerivationMode.XorLayeringWithSubstitution; var plaintextObject = "䆟`ಮ䷆ʘ‣⦸⏹ⰄͶa✰ṁ亡Zᨖ0༂⽔9㗰"; using var password = Password.FromUnicodeString(randomnessProvider.GetString(SymmetricKey.MinimumPasswordLength, true, true, true, true, true, true, false)); @@ -92,7 +92,7 @@ public void FromPassword_ShouldBeRepeatable_UsingThreeAlgorithms() var firstLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Cbc; var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Ecb; var thirdLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Cbc; - var derivationMode = SymmetricKeyDerivationMode.XorLayeringWithSubstitution; + var derivationMode = CryptographicKeyDerivationMode.XorLayeringWithSubstitution; var plaintextObject = "䆟`ಮ䷆ʘ‣⦸⏹ⰄͶa✰ṁ亡Zᨖ0༂⽔9㗰"; using var password = Password.FromUnicodeString(randomnessProvider.GetString(SymmetricKey.MinimumPasswordLength, true, true, true, true, true, true, false)); @@ -125,7 +125,7 @@ public void FromPassword_ShouldBeRepeatable_UsingTwoAlgorithms() var processor = new SymmetricStringProcessor(randomnessProvider); var firstLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Cbc; var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Ecb; - var derivationMode = SymmetricKeyDerivationMode.XorLayeringWithSubstitution; + var derivationMode = CryptographicKeyDerivationMode.XorLayeringWithSubstitution; var plaintextObject = "䆟`ಮ䷆ʘ‣⦸⏹ⰄͶa✰ṁ亡Zᨖ0༂⽔9㗰"; using var password = Password.FromUnicodeString(randomnessProvider.GetString(SymmetricKey.MinimumPasswordLength, true, true, true, true, true, true, false)); @@ -153,7 +153,7 @@ public void FromPassword_ShouldBeRepeatable_UsingTwoAlgorithms() public void New_ShouldRaiseArgumentOutOfRangeException_ForUnspecifiedAlgorithm() { // Arrange. - var derivationMode = SymmetricKeyDerivationMode.Truncation; + var derivationMode = CryptographicKeyDerivationMode.Truncation; var firstLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Cbc; var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Cbc; var thirdLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Cbc; @@ -176,7 +176,7 @@ public void New_ShouldRaiseArgumentOutOfRangeException_ForUnspecifiedAlgorithm() public void New_ShouldRaiseArgumentOutOfRangeException_ForUnspecifiedDerivationMode() { // Arrange. - var derivationMode = SymmetricKeyDerivationMode.Unspecified; + var derivationMode = CryptographicKeyDerivationMode.Unspecified; var firstLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Cbc; var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Cbc; var thirdLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Cbc; @@ -199,7 +199,7 @@ public void New_ShouldRaiseArgumentOutOfRangeException_ForUnspecifiedDerivationM public void New_ShouldReturnValidKey_ForValidArguments() { // Arrange. - var derivationMode = SymmetricKeyDerivationMode.Truncation; + var derivationMode = CryptographicKeyDerivationMode.Truncation; var firstLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Cbc; var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Cbc; var thirdLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Cbc; @@ -243,7 +243,7 @@ public void StressTest_ShouldProduceDesiredResults() public void ToSecureMemory_ShouldBeReversible_WithFourLayers() { // Arrange. - var derivationMode = SymmetricKeyDerivationMode.Truncation; + var derivationMode = CryptographicKeyDerivationMode.Truncation; var firstLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Cbc; var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Cbc; var thirdLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Cbc; @@ -260,7 +260,7 @@ public void ToSecureMemory_ShouldBeReversible_WithFourLayers() public void ToSecureMemory_ShouldBeReversible_WithThreeLayers() { // Arrange. - var derivationMode = SymmetricKeyDerivationMode.Truncation; + var derivationMode = CryptographicKeyDerivationMode.Truncation; var firstLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Cbc; var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Cbc; var thirdLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Cbc; @@ -276,7 +276,7 @@ public void ToSecureMemory_ShouldBeReversible_WithThreeLayers() public void ToSecureMemory_ShouldBeReversible_WithTwoLayers() { // Arrange. - var derivationMode = SymmetricKeyDerivationMode.Truncation; + var derivationMode = CryptographicKeyDerivationMode.Truncation; var firstLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Cbc; var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Cbc; @@ -292,7 +292,7 @@ public void ToSecureMemory_ShouldReturnValidResult() { // Arrange. var cascadingKeyLengthInBytes = 1666; - var derivationMode = SymmetricKeyDerivationMode.Truncation; + var derivationMode = CryptographicKeyDerivationMode.Truncation; var firstLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Cbc; var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Cbc; var thirdLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Cbc; diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/SymmetricKeyTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/SymmetricKeyTests.cs index af5170b1..57c0c4a6 100644 --- a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/SymmetricKeyTests.cs +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/SymmetricKeyTests.cs @@ -25,7 +25,7 @@ public void FromPassword_ShouldBeRepeatable() // Arrange. var processor = new SymmetricStringProcessor(randomnessProvider); var algorithm = SymmetricAlgorithmSpecification.Aes128Cbc; - var derivationMode = SymmetricKeyDerivationMode.XorLayering; + var derivationMode = CryptographicKeyDerivationMode.XorLayering; var plaintextObject = "䆟`ಮ䷆ʘ‣⦸⏹ⰄͶa✰ṁ亡Zᨖ0༂⽔9㗰"; using var password = Password.FromUnicodeString(randomnessProvider.GetString(SymmetricKey.MinimumPasswordLength, true, true, true, true, true, true, false)); @@ -158,7 +158,7 @@ public void New_ShouldRaiseArgumentOutOfRangeException_ForInvalidLengthKeySource { // Arrange. var algorithm = SymmetricAlgorithmSpecification.Aes256Cbc; - var derivationMode = SymmetricKeyDerivationMode.Truncation; + var derivationMode = CryptographicKeyDerivationMode.Truncation; var keyLength = 3; using (var keySource = new PinnedMemory(keyLength)) @@ -182,7 +182,7 @@ public void New_ShouldRaiseArgumentOutOfRangeException_ForUnspecifiedAlgorithm() { // Arrange. var algorithm = SymmetricAlgorithmSpecification.Unspecified; - var derivationMode = SymmetricKeyDerivationMode.Truncation; + var derivationMode = CryptographicKeyDerivationMode.Truncation; // Act. var action = new Action(() => @@ -202,7 +202,7 @@ public void New_ShouldRaiseArgumentOutOfRangeException_ForUnspecifiedDerivationM { // Arrange. var algorithm = SymmetricAlgorithmSpecification.Aes256Cbc; - var derivationMode = SymmetricKeyDerivationMode.Unspecified; + var derivationMode = CryptographicKeyDerivationMode.Unspecified; // Act. var action = new Action(() => diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/SymmetricProcessorTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/SymmetricProcessorTests.cs index 3ef356d2..a6f30cc7 100644 --- a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/SymmetricProcessorTests.cs +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/SymmetricProcessorTests.cs @@ -19,7 +19,7 @@ public class SymmetricProcessorTests public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithFourLayers_ForMixedAlgorithms_InTruncationMode() { // Arrange. - var derivationMode = SymmetricKeyDerivationMode.Truncation; + var derivationMode = CryptographicKeyDerivationMode.Truncation; var firstLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Cbc; var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Ecb; var thirdLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Ecb; @@ -33,7 +33,7 @@ public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithFourLayers public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithFourLayers_ForMixedAlgorithms_InXorLayeringMode() { // Arrange. - var derivationMode = SymmetricKeyDerivationMode.XorLayering; + var derivationMode = CryptographicKeyDerivationMode.XorLayering; var firstLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Cbc; var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Ecb; var thirdLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Ecb; @@ -47,7 +47,7 @@ public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithFourLayers public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithFourLayers_ForMixedAlgorithms_InXorLayeringWithSubstitutionMode() { // Arrange. - var derivationMode = SymmetricKeyDerivationMode.XorLayeringWithSubstitution; + var derivationMode = CryptographicKeyDerivationMode.XorLayeringWithSubstitution; var firstLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Cbc; var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Ecb; var thirdLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Ecb; @@ -61,7 +61,7 @@ public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithFourLayers public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithFourLayers_ForRepeatedCbc_InTruncationMode() { // Arrange. - var derivationMode = SymmetricKeyDerivationMode.Truncation; + var derivationMode = CryptographicKeyDerivationMode.Truncation; var firstLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Cbc; var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Cbc; var thirdLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Cbc; @@ -75,7 +75,7 @@ public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithFourLayers public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithFourLayers_ForRepeatedCbc_InXorLayeringMode() { // Arrange. - var derivationMode = SymmetricKeyDerivationMode.XorLayering; + var derivationMode = CryptographicKeyDerivationMode.XorLayering; var firstLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Cbc; var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Cbc; var thirdLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Cbc; @@ -89,7 +89,7 @@ public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithFourLayers public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithFourLayers_ForRepeatedCbc_InXorLayeringWithSubstitutionMode() { // Arrange. - var derivationMode = SymmetricKeyDerivationMode.XorLayeringWithSubstitution; + var derivationMode = CryptographicKeyDerivationMode.XorLayeringWithSubstitution; var firstLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Cbc; var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Cbc; var thirdLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Cbc; @@ -103,7 +103,7 @@ public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithFourLayers public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithFourLayers_ForRepeatedEcb_InTruncationMode() { // Arrange. - var derivationMode = SymmetricKeyDerivationMode.Truncation; + var derivationMode = CryptographicKeyDerivationMode.Truncation; var firstLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Ecb; var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Ecb; var thirdLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Ecb; @@ -117,7 +117,7 @@ public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithFourLayers public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithFourLayers_ForRepeatedEcb_InXorLayeringMode() { // Arrange. - var derivationMode = SymmetricKeyDerivationMode.XorLayering; + var derivationMode = CryptographicKeyDerivationMode.XorLayering; var firstLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Ecb; var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Ecb; var thirdLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Ecb; @@ -131,7 +131,7 @@ public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithFourLayers public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithFourLayers_ForRepeatedEcb_InXorLayeringWithSubstitutionMode() { // Arrange. - var derivationMode = SymmetricKeyDerivationMode.XorLayeringWithSubstitution; + var derivationMode = CryptographicKeyDerivationMode.XorLayeringWithSubstitution; var firstLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Ecb; var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Ecb; var thirdLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Ecb; @@ -145,7 +145,7 @@ public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithFourLayers public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithThreeLayers_ForMixedAlgorithms_InTruncationMode() { // Arrange. - var derivationMode = SymmetricKeyDerivationMode.Truncation; + var derivationMode = CryptographicKeyDerivationMode.Truncation; var firstLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Cbc; var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Ecb; var thirdLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Ecb; @@ -158,7 +158,7 @@ public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithThreeLayer public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithThreeLayers_ForMixedAlgorithms_InXorLayeringMode() { // Arrange. - var derivationMode = SymmetricKeyDerivationMode.XorLayering; + var derivationMode = CryptographicKeyDerivationMode.XorLayering; var firstLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Cbc; var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Ecb; var thirdLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Ecb; @@ -171,7 +171,7 @@ public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithThreeLayer public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithThreeLayers_ForMixedAlgorithms_InXorLayeringWithSubstitutionMode() { // Arrange. - var derivationMode = SymmetricKeyDerivationMode.XorLayeringWithSubstitution; + var derivationMode = CryptographicKeyDerivationMode.XorLayeringWithSubstitution; var firstLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Cbc; var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Ecb; var thirdLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Ecb; @@ -184,7 +184,7 @@ public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithThreeLayer public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithThreeLayers_ForRepeatedCbc_InTruncationMode() { // Arrange. - var derivationMode = SymmetricKeyDerivationMode.Truncation; + var derivationMode = CryptographicKeyDerivationMode.Truncation; var firstLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Cbc; var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Cbc; var thirdLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Cbc; @@ -197,7 +197,7 @@ public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithThreeLayer public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithThreeLayers_ForRepeatedCbc_InXorLayeringMode() { // Arrange. - var derivationMode = SymmetricKeyDerivationMode.XorLayering; + var derivationMode = CryptographicKeyDerivationMode.XorLayering; var firstLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Cbc; var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Cbc; var thirdLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Cbc; @@ -210,7 +210,7 @@ public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithThreeLayer public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithThreeLayers_ForRepeatedCbc_InXorLayeringWithSubstitutionMode() { // Arrange. - var derivationMode = SymmetricKeyDerivationMode.XorLayeringWithSubstitution; + var derivationMode = CryptographicKeyDerivationMode.XorLayeringWithSubstitution; var firstLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Cbc; var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Cbc; var thirdLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Cbc; @@ -223,7 +223,7 @@ public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithThreeLayer public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithThreeLayers_ForRepeatedEcb_InTruncationMode() { // Arrange. - var derivationMode = SymmetricKeyDerivationMode.Truncation; + var derivationMode = CryptographicKeyDerivationMode.Truncation; var firstLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Ecb; var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Ecb; var thirdLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Ecb; @@ -236,7 +236,7 @@ public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithThreeLayer public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithThreeLayers_ForRepeatedEcb_InXorLayeringMode() { // Arrange. - var derivationMode = SymmetricKeyDerivationMode.XorLayering; + var derivationMode = CryptographicKeyDerivationMode.XorLayering; var firstLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Ecb; var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Ecb; var thirdLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Ecb; @@ -249,7 +249,7 @@ public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithThreeLayer public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithThreeLayers_ForRepeatedEcb_InXorLayeringWithSubstitutionMode() { // Arrange. - var derivationMode = SymmetricKeyDerivationMode.XorLayeringWithSubstitution; + var derivationMode = CryptographicKeyDerivationMode.XorLayeringWithSubstitution; var firstLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Ecb; var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Ecb; var thirdLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Ecb; @@ -262,7 +262,7 @@ public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithThreeLayer public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithTwoLayers_ForMixedAlgorithms_InTruncationMode() { // Arrange. - var derivationMode = SymmetricKeyDerivationMode.Truncation; + var derivationMode = CryptographicKeyDerivationMode.Truncation; var firstLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Cbc; var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Ecb; @@ -274,7 +274,7 @@ public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithTwoLayers_ public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithTwoLayers_ForMixedAlgorithms_InXorLayeringMode() { // Arrange. - var derivationMode = SymmetricKeyDerivationMode.XorLayering; + var derivationMode = CryptographicKeyDerivationMode.XorLayering; var firstLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Cbc; var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Ecb; @@ -286,7 +286,7 @@ public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithTwoLayers_ public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithTwoLayers_ForMixedAlgorithms_InXorLayeringWithSubstitutionMode() { // Arrange. - var derivationMode = SymmetricKeyDerivationMode.XorLayeringWithSubstitution; + var derivationMode = CryptographicKeyDerivationMode.XorLayeringWithSubstitution; var firstLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Cbc; var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Ecb; @@ -298,7 +298,7 @@ public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithTwoLayers_ public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithTwoLayers_ForRepeatedCbc_InTruncationMode() { // Arrange. - var derivationMode = SymmetricKeyDerivationMode.Truncation; + var derivationMode = CryptographicKeyDerivationMode.Truncation; var firstLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Cbc; var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Cbc; @@ -310,7 +310,7 @@ public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithTwoLayers_ public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithTwoLayers_ForRepeatedCbc_InXorLayeringMode() { // Arrange. - var derivationMode = SymmetricKeyDerivationMode.XorLayering; + var derivationMode = CryptographicKeyDerivationMode.XorLayering; var firstLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Cbc; var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Cbc; @@ -322,7 +322,7 @@ public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithTwoLayers_ public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithTwoLayers_ForRepeatedCbc_InXorLayeringWithSubstitutionMode() { // Arrange. - var derivationMode = SymmetricKeyDerivationMode.XorLayeringWithSubstitution; + var derivationMode = CryptographicKeyDerivationMode.XorLayeringWithSubstitution; var firstLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Cbc; var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Cbc; @@ -334,7 +334,7 @@ public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithTwoLayers_ public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithTwoLayers_ForRepeatedEcb_InTruncationMode() { // Arrange. - var derivationMode = SymmetricKeyDerivationMode.Truncation; + var derivationMode = CryptographicKeyDerivationMode.Truncation; var firstLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Ecb; var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Ecb; @@ -346,7 +346,7 @@ public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithTwoLayers_ public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithTwoLayers_ForRepeatedEcb_InXorLayeringMode() { // Arrange. - var derivationMode = SymmetricKeyDerivationMode.XorLayering; + var derivationMode = CryptographicKeyDerivationMode.XorLayering; var firstLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Ecb; var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Ecb; @@ -358,7 +358,7 @@ public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithTwoLayers_ public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithTwoLayers_ForRepeatedEcb_InXorLayeringWithSubstitutionMode() { // Arrange. - var derivationMode = SymmetricKeyDerivationMode.XorLayeringWithSubstitution; + var derivationMode = CryptographicKeyDerivationMode.XorLayeringWithSubstitution; var firstLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Ecb; var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Ecb; @@ -371,7 +371,7 @@ public void Encrypt_ShouldBeReversible_UsingSymmetricKey_ForAes128Cbc_InTruncati { // Arrange. var algorithm = SymmetricAlgorithmSpecification.Aes128Cbc; - var derivationMode = SymmetricKeyDerivationMode.Truncation; + var derivationMode = CryptographicKeyDerivationMode.Truncation; // Assert. Encrypt_ShouldBeReversible_UsingSymmetricKey(algorithm, derivationMode); @@ -382,7 +382,7 @@ public void Encrypt_ShouldBeReversible_UsingSymmetricKey_ForAes128Cbc_InXorLayer { // Arrange. var algorithm = SymmetricAlgorithmSpecification.Aes128Cbc; - var derivationMode = SymmetricKeyDerivationMode.XorLayering; + var derivationMode = CryptographicKeyDerivationMode.XorLayering; // Assert. Encrypt_ShouldBeReversible_UsingSymmetricKey(algorithm, derivationMode); @@ -393,7 +393,7 @@ public void Encrypt_ShouldBeReversible_UsingSymmetricKey_ForAes128Cbc_InXorLayer { // Arrange. var algorithm = SymmetricAlgorithmSpecification.Aes128Cbc; - var derivationMode = SymmetricKeyDerivationMode.XorLayeringWithSubstitution; + var derivationMode = CryptographicKeyDerivationMode.XorLayeringWithSubstitution; // Assert. Encrypt_ShouldBeReversible_UsingSymmetricKey(algorithm, derivationMode); @@ -404,7 +404,7 @@ public void Encrypt_ShouldBeReversible_UsingSymmetricKey_ForAes256Ecb_InTruncati { // Arrange. var algorithm = SymmetricAlgorithmSpecification.Aes256Ecb; - var derivationMode = SymmetricKeyDerivationMode.Truncation; + var derivationMode = CryptographicKeyDerivationMode.Truncation; // Assert. Encrypt_ShouldBeReversible_UsingSymmetricKey(algorithm, derivationMode); @@ -415,7 +415,7 @@ public void Encrypt_ShouldBeReversible_UsingSymmetricKey_ForAes256Ecb_InXorLayer { // Arrange. var algorithm = SymmetricAlgorithmSpecification.Aes256Ecb; - var derivationMode = SymmetricKeyDerivationMode.XorLayering; + var derivationMode = CryptographicKeyDerivationMode.XorLayering; // Assert. Encrypt_ShouldBeReversible_UsingSymmetricKey(algorithm, derivationMode); @@ -426,7 +426,7 @@ public void Encrypt_ShouldBeReversible_UsingSymmetricKey_ForAes256Ecb_InXorLayer { // Arrange. var algorithm = SymmetricAlgorithmSpecification.Aes256Ecb; - var derivationMode = SymmetricKeyDerivationMode.XorLayeringWithSubstitution; + var derivationMode = CryptographicKeyDerivationMode.XorLayeringWithSubstitution; // Assert. Encrypt_ShouldBeReversible_UsingSymmetricKey(algorithm, derivationMode); @@ -498,7 +498,7 @@ private static void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey(Cascad } } - private static void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithFourLayers(SymmetricKeyDerivationMode derivationMode, SymmetricAlgorithmSpecification firstLayerAlgorithm, SymmetricAlgorithmSpecification secondLayerAlgorithm, SymmetricAlgorithmSpecification thirdLayerAlgorithm, SymmetricAlgorithmSpecification fourthLayerAlgorithm) + private static void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithFourLayers(CryptographicKeyDerivationMode derivationMode, SymmetricAlgorithmSpecification firstLayerAlgorithm, SymmetricAlgorithmSpecification secondLayerAlgorithm, SymmetricAlgorithmSpecification thirdLayerAlgorithm, SymmetricAlgorithmSpecification fourthLayerAlgorithm) { using (var key = CascadingSymmetricKey.New(derivationMode, firstLayerAlgorithm, secondLayerAlgorithm, thirdLayerAlgorithm, fourthLayerAlgorithm)) { @@ -506,7 +506,7 @@ private static void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithFo } } - private static void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithThreeLayers(SymmetricKeyDerivationMode derivationMode, SymmetricAlgorithmSpecification firstLayerAlgorithm, SymmetricAlgorithmSpecification secondLayerAlgorithm, SymmetricAlgorithmSpecification thirdLayerAlgorithm) + private static void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithThreeLayers(CryptographicKeyDerivationMode derivationMode, SymmetricAlgorithmSpecification firstLayerAlgorithm, SymmetricAlgorithmSpecification secondLayerAlgorithm, SymmetricAlgorithmSpecification thirdLayerAlgorithm) { using (var key = CascadingSymmetricKey.New(derivationMode, firstLayerAlgorithm, secondLayerAlgorithm, thirdLayerAlgorithm)) { @@ -514,7 +514,7 @@ private static void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithTh } } - private static void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithTwoLayers(SymmetricKeyDerivationMode derivationMode, SymmetricAlgorithmSpecification firstLayerAlgorithm, SymmetricAlgorithmSpecification secondLayerAlgorithm) + private static void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithTwoLayers(CryptographicKeyDerivationMode derivationMode, SymmetricAlgorithmSpecification firstLayerAlgorithm, SymmetricAlgorithmSpecification secondLayerAlgorithm) { using (var key = CascadingSymmetricKey.New(derivationMode, firstLayerAlgorithm, secondLayerAlgorithm)) { @@ -522,7 +522,7 @@ private static void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithTw } } - private static void Encrypt_ShouldBeReversible_UsingSymmetricKey(SymmetricAlgorithmSpecification algorithm, SymmetricKeyDerivationMode derivationMode) + private static void Encrypt_ShouldBeReversible_UsingSymmetricKey(SymmetricAlgorithmSpecification algorithm, CryptographicKeyDerivationMode derivationMode) { using (var randomnessProvider = RandomNumberGenerator.Create()) { diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/SymmetricStringProcessorTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/SymmetricStringProcessorTests.cs index ae96d8bd..a5f667cb 100644 --- a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/SymmetricStringProcessorTests.cs +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Symmetric/SymmetricStringProcessorTests.cs @@ -17,7 +17,7 @@ public class SymmetricStringProcessorTests public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithFourLayers() { // Arrange. - var derivationMode = SymmetricKeyDerivationMode.XorLayering; + var derivationMode = CryptographicKeyDerivationMode.XorLayering; var firstLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Cbc; var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Cbc; var thirdLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Cbc; @@ -34,7 +34,7 @@ public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithFourLayers public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithThreeLayers() { // Arrange. - var derivationMode = SymmetricKeyDerivationMode.XorLayering; + var derivationMode = CryptographicKeyDerivationMode.XorLayering; var firstLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Cbc; var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Cbc; var thirdLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Cbc; @@ -50,7 +50,7 @@ public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithThreeLayer public void Encrypt_ShouldBeReversible_UsingCascadingSymmetricKey_WithTwoLayers() { // Arrange. - var derivationMode = SymmetricKeyDerivationMode.XorLayering; + var derivationMode = CryptographicKeyDerivationMode.XorLayering; var firstLayerAlgorithm = SymmetricAlgorithmSpecification.Aes128Cbc; var secondLayerAlgorithm = SymmetricAlgorithmSpecification.Aes256Cbc; From 8a1a8eec9e2773401e2b142aad2d18aca3876bd8 Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Thu, 25 Jun 2020 23:50:22 -0500 Subject: [PATCH 41/55] Introduce myriad elliptic curve crypto types. --- en-US_User.dic | 1 + .../Asymmetric/AsymmetricKey.cs | 71 ------ .../Asymmetric/AsymmetricKeyPair.cs | 223 ++++++++++++++++-- ...ecification.cs => AsymmetricKeyPurpose.cs} | 18 +- .../DigitalSignatureAlgorithmSpecification.cs | 49 ++++ .../DigitalSignature/DigitalSignatureKey.cs | 53 ----- .../DigitalSignatureKeyPair.cs | 43 ++-- .../DigitalSignaturePrivateKey.cs | 125 ++++++++-- .../DigitalSignaturePublicKey.cs | 187 ++++++++++++++- .../DigitalSignature/Ecdsa/EcdsaKeyPair.cs | 75 ++++-- .../DigitalSignature/Ecdsa/EcdsaPrivateKey.cs | 42 ++-- .../DigitalSignature/Ecdsa/EcdsaPublicKey.cs | 42 ++-- .../IDigitalSignatureKeyPair.cs | 2 +- .../IDigitalSignaturePrivateKey.cs | 2 +- .../IDigitalSignaturePublicKey.cs | 7 + .../Asymmetric/IAsymmetricKey.cs | 43 +++- .../Asymmetric/IAsymmetricKeyPair.cs | 32 ++- .../Asymmetric/IAsymmetricPrivateKey.cs | 8 +- .../Asymmetric/IAsymmetricPublicKey.cs | 12 + .../KeyExchange/Ecdh/EcdhKeyPair.cs | 75 ++++-- .../KeyExchange/Ecdh/EcdhPrivateKey.cs | 42 ++-- .../KeyExchange/Ecdh/EcdhPublicKey.cs | 42 ++-- .../KeyExchange/IKeyExchangeKeyPair.cs | 2 +- .../KeyExchange/IKeyExchangePrivateKey.cs | 2 +- .../KeyExchange/IKeyExchangePublicKey.cs | 7 + .../KeyExchangeAlgorithmSpecification.cs | 49 ++++ .../Asymmetric/KeyExchange/KeyExchangeKey.cs | 53 ----- .../KeyExchange/KeyExchangeKeyPair.cs | 42 ++-- .../KeyExchange/KeyExchangePrivateKey.cs | 123 ++++++++-- .../KeyExchange/KeyExchangePublicKey.cs | 188 ++++++++++++++- .../CryptographicKey.cs | 33 ++- ...gnatureAlgorithmSpecificationExtensions.cs | 83 +++++++ ...xchangeAlgorithmSpecificationExtensions.cs | 83 +++++++ .../ICryptographicKey.cs | 5 +- .../Symmetric/CascadingSymmetricKey.cs | 2 +- .../Symmetric/SymmetricKey.cs | 6 + .../AlgorithmSpecificationExtensionsTests.cs | 116 +++++++++ ...dInstruments.Cryptography.UnitTests.csproj | 3 + 38 files changed, 1580 insertions(+), 411 deletions(-) delete mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricKey.cs rename src/RapidField.SolidInstruments.Cryptography/Asymmetric/{AsymmetricAlgorithmSpecification.cs => AsymmetricKeyPurpose.cs} (54%) create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignatureAlgorithmSpecification.cs delete mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignatureKey.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangeAlgorithmSpecification.cs delete mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangeKey.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Extensions/DigitalSignatureAlgorithmSpecificationExtensions.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Extensions/KeyExchangeAlgorithmSpecificationExtensions.cs create mode 100644 test/RapidField.SolidInstruments.Cryptography.UnitTests/Extensions/AlgorithmSpecificationExtensionsTests.cs diff --git a/en-US_User.dic b/en-US_User.dic index c4685697..711e9e4d 100644 --- a/en-US_User.dic +++ b/en-US_User.dic @@ -17,6 +17,7 @@ backporting baz boolean bp +brainpool buildmetadata carte cbcm diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricKey.cs deleted file mode 100644 index 65c633dc..00000000 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricKey.cs +++ /dev/null @@ -1,71 +0,0 @@ -// ================================================================================================================================= -// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. -// ================================================================================================================================= - -using RapidField.SolidInstruments.Core; -using RapidField.SolidInstruments.Core.ArgumentValidation; -using System; - -namespace RapidField.SolidInstruments.Cryptography.Asymmetric -{ - /// - /// Represents an asymmetric-key algorithm and the key bits for one key in an asymmetric key pair. - /// - /// - /// is the default implementation of . - /// - public abstract class AsymmetricKey : Instrument, IAsymmetricKey - { - /// - /// Initializes a new instance of the class. - /// - /// - /// The asymmetric-key algorithm for which the key is used. - /// - /// - /// is equal to . - /// - protected AsymmetricKey(AsymmetricAlgorithmSpecification algorithm) - : base() - { - Algorithm = algorithm.RejectIf().IsEqualToValue(AsymmetricAlgorithmSpecification.Unspecified, nameof(algorithm)); - } - - /// - /// Converts the value of the current to a secure bit field. - /// - /// - /// A secure bit field containing a representation of the current . - /// - public ISecureMemory ToSecureMemory() => throw new NotImplementedException(); - - /// - /// Releases all resources consumed by the current . - /// - /// - /// A value indicating whether or not managed resources should be released. - /// - protected override void Dispose(Boolean disposing) - { - try - { - if (disposing) - { - throw new NotImplementedException(); - } - } - finally - { - base.Dispose(disposing); - } - } - - /// - /// Gets the asymmetric-key algorithm for which the key is used. - /// - public AsymmetricAlgorithmSpecification Algorithm - { - get; - } - } -} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricKeyPair.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricKeyPair.cs index 7ca8a5b5..634aab99 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricKeyPair.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricKeyPair.cs @@ -7,6 +7,8 @@ using RapidField.SolidInstruments.Core.Extensions; using System; using System.Diagnostics; +using System.Security; +using System.Security.Cryptography; using System.Threading; namespace RapidField.SolidInstruments.Cryptography.Asymmetric @@ -15,9 +17,15 @@ namespace RapidField.SolidInstruments.Cryptography.Asymmetric /// Represents an asymmetric-key algorithm and the key bits for an asymmetric key pair. /// /// - /// is the default implementation of - /// . + /// is the default implementation of + /// . /// + /// + /// The type of the asymmetric-key algorithm for which the key pair is used. + /// + /// + /// The type of the asymmetric algorithm provider that facilitates cryptographic operations for the key pair. + /// /// /// A shared key type from which both the private and public key types derive. /// @@ -27,29 +35,41 @@ namespace RapidField.SolidInstruments.Cryptography.Asymmetric /// /// The type of the public key. /// - public abstract class AsymmetricKeyPair : AsymmetricKeyPair - where TKey : class, IAsymmetricKey - where TPrivateKey : class, TKey, IAsymmetricPrivateKey + public abstract class AsymmetricKeyPair : AsymmetricKeyPair + where TAlgorithm : struct, Enum + where TProvider : AsymmetricAlgorithm + where TKey : class, ICryptographicKey + where TPrivateKey : class, TKey, IAsymmetricPrivateKey where TPublicKey : class, TKey, IAsymmetricPublicKey { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the + /// class. /// + /// + /// The globally unique identifier for the key pair. + /// /// /// The asymmetric-key algorithm for which the key is used. /// + /// + /// The length of time for which the paired keys are valid. + /// /// - /// is equal to . + /// is equal to -or- is equal to the + /// default/unspecified value -or- is less than eight seconds. /// - protected AsymmetricKeyPair(AsymmetricAlgorithmSpecification algorithm) - : base(algorithm) + protected AsymmetricKeyPair(Guid identifier, TAlgorithm algorithm, TimeSpan keyLifespanDuration) + : base(identifier, algorithm, keyLifespanDuration) { LazyPrivateKey = new Lazy(InitializePrivateKey, LazyThreadSafetyMode.ExecutionAndPublication); + LazyProvider = new Lazy(InitializeProvider, LazyThreadSafetyMode.ExecutionAndPublication); LazyPublicKey = new Lazy(InitializePublicKey, LazyThreadSafetyMode.ExecutionAndPublication); } /// - /// Releases all resources consumed by the current . + /// Releases all resources consumed by the current + /// . /// /// /// A value indicating whether or not managed resources should be released. @@ -60,6 +80,7 @@ protected override void Dispose(Boolean disposing) { if (disposing) { + LazyProvider?.Dispose(); LazyPrivateKey?.Dispose(); LazyPublicKey?.Dispose(); } @@ -73,11 +94,85 @@ protected override void Dispose(Boolean disposing) /// /// Initializes the private key. /// + /// + /// The asymmetric-key algorithm for which the key pair is used. + /// + /// + /// The algorithm provider that facilitates cryptographic operations for the key pair. + /// /// /// The private key. /// + protected abstract TPrivateKey InitializePrivateKey(TAlgorithm algorithm, TProvider provider); + + /// + /// Initializes the algorithm provider that facilitates cryptographic operations for the key pair. + /// + /// + /// The asymmetric-key algorithm for which the key pair is used. + /// + /// + /// The algorithm provider that facilitates cryptographic operations for the key pair. + /// + protected abstract TProvider InitializeProvider(TAlgorithm algorithm); + + /// + /// Initializes the private key. + /// + /// + /// The asymmetric-key algorithm for which the key pair is used. + /// + /// + /// The algorithm provider that facilitates cryptographic operations for the key pair. + /// + /// + /// The private key. + /// + protected abstract TPublicKey InitializePublicKey(TAlgorithm algorithm, TProvider provider); + + /// + /// Initializes the private key. + /// + /// + /// The private key. + /// + /// + /// An exception was raised while attempting to initialize the key. + /// + [DebuggerHidden] + private TPrivateKey InitializePrivateKey() + { + try + { + return InitializePrivateKey(Algorithm, Provider); + } + catch (Exception exception) + { + throw new SecurityException("An exception was raised while attempting to initialize the private key.", exception); + } + } + + /// + /// Initializes the algorithm provider that facilitates cryptographic operations for the key pair. + /// + /// + /// The private key. + /// + /// + /// An exception was raised while attempting to initialize the provider. + /// [DebuggerHidden] - private TPrivateKey InitializePrivateKey() => throw new NotImplementedException(); + private TProvider InitializeProvider() + { + try + { + return InitializeProvider(Algorithm); + } + catch (Exception exception) + { + throw new SecurityException("An exception was raised while attempting to initialize the provider.", exception); + } + } /// /// Initializes the public key. @@ -85,8 +180,21 @@ protected override void Dispose(Boolean disposing) /// /// The public key. /// + /// + /// An exception was raised while attempting to initialize the key. + /// [DebuggerHidden] - private TPublicKey InitializePublicKey() => throw new NotImplementedException(); + private TPublicKey InitializePublicKey() + { + try + { + return InitializePublicKey(Algorithm, Provider); + } + catch (Exception exception) + { + throw new SecurityException("An exception was raised while attempting to initialize the public key.", exception); + } + } /// /// Gets the private key. @@ -98,12 +206,25 @@ protected override void Dispose(Boolean disposing) /// public TPublicKey PublicKey => LazyPublicKey.Value; + /// + /// Gets the asymmetric algorithm provider that facilitates cryptographic operations for the key pair. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal TProvider Provider => LazyProvider.Value; + /// /// Represents the lazily-initialized private key. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Lazy LazyPrivateKey; + /// + /// Represents the lazily-initialized asymmetric algorithm provider that facilitates cryptographic operations for the key + /// pair. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly Lazy LazyProvider; + /// /// Represents the lazily-initialized public key. /// @@ -111,6 +232,57 @@ protected override void Dispose(Boolean disposing) private readonly Lazy LazyPublicKey; } + /// + /// Represents an asymmetric-key algorithm and the key bits for an asymmetric key pair. + /// + /// + /// The type of the asymmetric-key algorithm for which the key pair is used. + /// + /// + /// is the default implementation of . + /// + public abstract class AsymmetricKeyPair : AsymmetricKeyPair, IAsymmetricKeyPair + where TAlgorithm : struct, Enum + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The globally unique identifier for the key pair. + /// + /// + /// The asymmetric-key algorithm for which the key is used. + /// + /// + /// The length of time for which the paired keys are valid. + /// + /// + /// is equal to -or- is equal to the + /// default/unspecified value -or- is less than eight seconds. + /// + protected AsymmetricKeyPair(Guid identifier, TAlgorithm algorithm, TimeSpan keyLifespanDuration) + : base(identifier, keyLifespanDuration) + { + Algorithm = algorithm.RejectIf().IsEqualToValue(default, nameof(algorithm)); + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Gets the asymmetric-key algorithm for which the key pair is used. + /// + public TAlgorithm Algorithm + { + get; + } + } + /// /// Represents an asymmetric-key algorithm and the key bits for an asymmetric key pair. /// @@ -122,16 +294,21 @@ public abstract class AsymmetricKeyPair : Instrument, IAsymmetricKeyPair /// /// Initializes a new instance of the class. /// - /// - /// The asymmetric-key algorithm for which the key is used. + /// + /// The globally unique identifier for the key pair. + /// + /// + /// The length of time for which the paired keys are valid. /// /// - /// is equal to . + /// is equal to -or- is + /// less than eight seconds. /// - protected AsymmetricKeyPair(AsymmetricAlgorithmSpecification algorithm) + protected AsymmetricKeyPair(Guid identifier, TimeSpan keyLifespanDuration) : base() { - Algorithm = algorithm.RejectIf().IsEqualToValue(AsymmetricAlgorithmSpecification.Unspecified, nameof(algorithm)); + Identifier = identifier.RejectIf().IsEqualToValue(Guid.Empty, nameof(identifier)); + KeyLifespanDuration = keyLifespanDuration.RejectIf().IsLessThan(CryptographicKey.MinimumLifespanDuration, nameof(keyLifespanDuration)); } /// @@ -143,9 +320,17 @@ protected AsymmetricKeyPair(AsymmetricAlgorithmSpecification algorithm) protected override void Dispose(Boolean disposing) => base.Dispose(disposing); /// - /// Gets the asymmetric-key algorithm for which the key pair is used. + /// Gets the globally unique identifier for the current . + /// + public Guid Identifier + { + get; + } + + /// + /// Gets the length of time for which the paired keys are valid. /// - public AsymmetricAlgorithmSpecification Algorithm + protected internal TimeSpan KeyLifespanDuration { get; } diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricAlgorithmSpecification.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricKeyPurpose.cs similarity index 54% rename from src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricAlgorithmSpecification.cs rename to src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricKeyPurpose.cs index 92649794..e66d9c0b 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricAlgorithmSpecification.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricKeyPurpose.cs @@ -7,13 +7,23 @@ namespace RapidField.SolidInstruments.Cryptography.Asymmetric { /// - /// Defines distinct combinations of asymmetric key encryption algorithms, key bit-lengths and operational modes. + /// Defines the purpose of an . /// - public enum AsymmetricAlgorithmSpecification : Byte + public enum AsymmetricKeyPurpose : Byte { /// - /// The asymmetric algorithm is not specified. + /// The asymmetric key purpose is not specified. /// - Unspecified = 0x00 + Unspecified = 0x00, + + /// + /// The key is used for digitally signing data. + /// + DigitalSignature = 0x01, + + /// + /// The key is used for securely exchanging symmetric keys. + /// + KeyExchange = 0x02 } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignatureAlgorithmSpecification.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignatureAlgorithmSpecification.cs new file mode 100644 index 00000000..b941b2cd --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignatureAlgorithmSpecification.cs @@ -0,0 +1,49 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature +{ + /// + /// Defines distinct combinations of asymmetric digital signature algorithms, key bit-lengths and operational modes. + /// + public enum DigitalSignatureAlgorithmSpecification : Byte + { + /// + /// The asymmetric algorithm is not specified. + /// + Unspecified = 0x00, + + /// + /// Specifies ECDSA using the brainpoolP256r1 curve. + /// + EcdsaBrainpoolP256r1 = 0x01, + + /// + /// Specifies ECDSA using the brainpoolP384r1 curve. + /// + EcdsaBrainpoolP384r1 = 0x02, + + /// + /// Specifies ECDSA using the brainpoolP512r1 curve. + /// + EcdsaBrainpoolP512r1 = 0x03, + + /// + /// Specifies ECDSA using the nistP256 curve. + /// + EcdsaNistP256 = 0x04, + + /// + /// Specifies ECDSA using the nistP384 curve. + /// + EcdsaNistP384 = 0x05, + + /// + /// Specifies ECDSA using the nistP521 curve. + /// + EcdsaNistP521 = 0x06 + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignatureKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignatureKey.cs deleted file mode 100644 index 117035a3..00000000 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignatureKey.cs +++ /dev/null @@ -1,53 +0,0 @@ -// ================================================================================================================================= -// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. -// ================================================================================================================================= - -using System; - -namespace RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature -{ - /// - /// Represents an asymmetric digital signature algorithm and the key bits for one key in an asymmetric key pair. - /// - /// - /// is the default implementation of . - /// - public abstract class DigitalSignatureKey : AsymmetricKey, IDigitalSignatureKey - { - /// - /// Initializes a new instance of the class. - /// - /// - /// The asymmetric-key algorithm for which the key is used. - /// - /// - /// is equal to . - /// - protected DigitalSignatureKey(AsymmetricAlgorithmSpecification algorithm) - : base(algorithm) - { - return; - } - - /// - /// Releases all resources consumed by the current . - /// - /// - /// A value indicating whether or not managed resources should be released. - /// - protected override void Dispose(Boolean disposing) - { - try - { - if (disposing) - { - throw new NotImplementedException(); - } - } - finally - { - base.Dispose(disposing); - } - } - } -} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignatureKeyPair.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignatureKeyPair.cs index d89be1c7..62742bf8 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignatureKeyPair.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignatureKeyPair.cs @@ -3,6 +3,7 @@ // ================================================================================================================================= using System; +using System.Security.Cryptography; namespace RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature { @@ -10,53 +11,53 @@ namespace RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature /// Represents an asymmetric digital signature algorithm and the key bits for an asymmetric key pair. /// /// - /// is the default implementation of + /// is the default implementation of /// . /// + /// + /// The type of the asymmetric algorithm provider that facilitates cryptographic operations for the key pair. + /// /// /// The type of the private key. /// /// /// The type of the public key. /// - public abstract class DigitalSignatureKeyPair : AsymmetricKeyPair, IDigitalSignatureKeyPair + public abstract class DigitalSignatureKeyPair : AsymmetricKeyPair, IDigitalSignatureKeyPair + where TProvider : AsymmetricAlgorithm where TPrivateKey : DigitalSignaturePrivateKey where TPublicKey : DigitalSignaturePublicKey { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// + /// + /// The globally unique identifier for the key pair. + /// /// /// The asymmetric-key algorithm for which the key is used. /// + /// + /// The length of time for which the paired keys are valid. + /// /// - /// is equal to . + /// is equal to -or- is equal to + /// -or- is less + /// than eight seconds. /// - protected DigitalSignatureKeyPair(AsymmetricAlgorithmSpecification algorithm) - : base(algorithm) + protected DigitalSignatureKeyPair(Guid identifier, DigitalSignatureAlgorithmSpecification algorithm, TimeSpan keyLifespanDuration) + : base(identifier, algorithm, keyLifespanDuration) { return; } /// - /// Releases all resources consumed by the current . + /// Releases all resources consumed by the current + /// . /// /// /// A value indicating whether or not managed resources should be released. /// - protected override void Dispose(Boolean disposing) - { - try - { - if (disposing) - { - throw new NotImplementedException(); - } - } - finally - { - base.Dispose(disposing); - } - } + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignaturePrivateKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignaturePrivateKey.cs index b1ce42a7..b9f50f63 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignaturePrivateKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignaturePrivateKey.cs @@ -2,7 +2,14 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= +using RapidField.SolidInstruments.Collections; +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Core.Extensions; +using RapidField.SolidInstruments.Cryptography.Extensions; using System; +using System.Diagnostics; +using System.Security.Cryptography; namespace RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature { @@ -12,42 +19,128 @@ namespace RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature /// /// is the default implementation of . /// - public abstract class DigitalSignaturePrivateKey : DigitalSignatureKey, IDigitalSignaturePrivateKey + public abstract class DigitalSignaturePrivateKey : CryptographicKey, IDigitalSignaturePrivateKey { /// /// Initializes a new instance of the class. /// + /// + /// The globally unique identifier for the key pair to which the key belongs. + /// /// /// The asymmetric-key algorithm for which the key is used. /// + /// + /// The mode used to derive the output key. + /// + /// + /// A bit field that is used to derive key bits. + /// + /// + /// contains too many elements. + /// + /// + /// is . + /// /// - /// is equal to . + /// is equal to -or- is equal + /// to . /// - protected DigitalSignaturePrivateKey(AsymmetricAlgorithmSpecification algorithm) - : base(algorithm) + protected DigitalSignaturePrivateKey(Guid keyPairIdentifier, DigitalSignatureAlgorithmSpecification algorithm, CryptographicKeyDerivationMode derivationMode, PinnedMemory keySource) + : this(keyPairIdentifier, algorithm, derivationMode, keySource, DefaultLifespanDuration) { return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The globally unique identifier for the key pair to which the key belongs. + /// + /// + /// The asymmetric-key algorithm for which the key is used. + /// + /// + /// The mode used to derive the output key. + /// + /// + /// A bit field that is used to derive key bits. + /// + /// + /// The length of time for which the key is valid. The default value is ninety (90) days. + /// + /// + /// contains too many elements. + /// + /// + /// is . + /// + /// + /// is equal to -or- is equal + /// to -or- is less + /// than eight seconds. + /// + protected DigitalSignaturePrivateKey(Guid keyPairIdentifier, DigitalSignatureAlgorithmSpecification algorithm, CryptographicKeyDerivationMode derivationMode, PinnedMemory keySource, TimeSpan lifespanDuration) + : base(algorithm, derivationMode, keySource, algorithm.ToPrivateKeyBitLength()) + { + Curve = algorithm.ToCurve(); + ExpirationTimeStamp = TimeStamp.Current.Add(lifespanDuration.RejectIf().IsLessThan(MinimumLifespanDuration, nameof(lifespanDuration))); + KeyPairIdentifier = keyPairIdentifier.RejectIf().IsEqualToValue(Guid.Empty, nameof(keyPairIdentifier)); + Purpose = AsymmetricKeyPurpose.DigitalSignature; + } + + /// + /// Converts the value of the current to its equivalent string representation. + /// + /// + /// A string representation of the current . + /// + public override String ToString() => $"{{ \"{nameof(Purpose)}\": {Purpose}, \"{nameof(Algorithm)}\": {Algorithm}, \"{nameof(KeyPairIdentifier)}\": {KeyPairIdentifier.ToSerializedString()} }}"; + /// /// Releases all resources consumed by the current . /// /// /// A value indicating whether or not managed resources should be released. /// - protected override void Dispose(Boolean disposing) + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Gets the date and time when the current expires and is no longer valid for + /// use. + /// + public DateTime ExpirationTimeStamp + { + get; + } + + /// + /// Gets a value indicating whether or not the current is expired. + /// + public Boolean IsExpired => TimeStamp.Current >= ExpirationTimeStamp; + + /// + /// Gets the globally unique identifier for the key pair to which the current + /// belongs. + /// + public Guid KeyPairIdentifier { - try - { - if (disposing) - { - throw new NotImplementedException(); - } - } - finally - { - base.Dispose(disposing); - } + get; } + + /// + /// Gets a value that specifies what the current is used for. + /// + public AsymmetricKeyPurpose Purpose + { + get; + } + + /// + /// Represents an elliptic curve matching . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal readonly ECCurve Curve; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignaturePublicKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignaturePublicKey.cs index cdcee530..7dc5c858 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignaturePublicKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignaturePublicKey.cs @@ -2,7 +2,14 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= +using RapidField.SolidInstruments.Collections; +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Core.Concurrency; +using RapidField.SolidInstruments.Core.Extensions; +using RapidField.SolidInstruments.Cryptography.Extensions; using System; +using System.Diagnostics; namespace RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature { @@ -12,23 +19,111 @@ namespace RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature /// /// is the default implementation of . /// - public abstract class DigitalSignaturePublicKey : DigitalSignatureKey, IDigitalSignaturePublicKey + public abstract class DigitalSignaturePublicKey : CryptographicKey, IDigitalSignaturePublicKey { /// /// Initializes a new instance of the class. /// + /// + /// The globally unique identifier for the key pair to which the key belongs. + /// /// /// The asymmetric-key algorithm for which the key is used. /// + /// + /// The plaintext key bits for the public key. + /// + /// + /// is empty. + /// + /// + /// The length of is invalid for . + /// + /// + /// is . + /// /// - /// is equal to . + /// is equal to -or- is equal + /// to . /// - protected DigitalSignaturePublicKey(AsymmetricAlgorithmSpecification algorithm) - : base(algorithm) + protected DigitalSignaturePublicKey(Guid keyPairIdentifier, DigitalSignatureAlgorithmSpecification algorithm, Byte[] keyMemory) + : this(keyPairIdentifier, algorithm, keyMemory, DefaultLifespanDuration) { return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The globally unique identifier for the key pair to which the key belongs. + /// + /// + /// The asymmetric-key algorithm for which the key is used. + /// + /// + /// The plaintext key bits for the public key. + /// + /// + /// The length of time for which the key is valid. The default value is ninety (90) days. + /// + /// + /// is empty. + /// + /// + /// The length of is invalid for . + /// + /// + /// is . + /// + /// + /// is equal to -or- is equal + /// to -or- is less + /// than eight seconds. + /// + protected DigitalSignaturePublicKey(Guid keyPairIdentifier, DigitalSignatureAlgorithmSpecification algorithm, Byte[] keyMemory, TimeSpan lifespanDuration) + : base() + { + Algorithm = algorithm.RejectIf().IsEqualToValue(DigitalSignatureAlgorithmSpecification.Unspecified, nameof(algorithm)); + ExpirationTimeStamp = TimeStamp.Current.Add(lifespanDuration.RejectIf().IsLessThan(MinimumLifespanDuration, nameof(lifespanDuration))); + KeyMemory = new PinnedMemory(keyMemory.RejectIf().IsNullOrEmpty(nameof(keyMemory)).OrIf(argument => argument.Length != (algorithm.ToPublicKeyBitLength() / 8), nameof(keyMemory), $"The length of the specified key is invalid for the specified algorithm, \"{algorithm}\"."), true); + KeyPairIdentifier = keyPairIdentifier.RejectIf().IsEqualToValue(Guid.Empty, nameof(keyPairIdentifier)); + Purpose = AsymmetricKeyPurpose.DigitalSignature; + } + + /// + /// Converts the current to its textual Base64 representation. + /// + /// + /// A Base64 string representation of the byte collection. + /// + /// + /// The object is disposed. + /// + public String ToBase64String() + { + RejectIfDisposed(); + var result = (String)null; + + using (var secureMemory = ToSecureMemory()) + { + secureMemory.Access(memory => + { + result = memory.ToBase64String(); + }); + } + + return result; + } + + /// + /// Converts the value of the current to its equivalent string representation. + /// + /// + /// A string representation of the current . + /// + public override String ToString() => $"{{ \"{nameof(Purpose)}\": {Purpose}, \"{nameof(Algorithm)}\": {Algorithm}, \"{nameof(KeyPairIdentifier)}\": {KeyPairIdentifier.ToSerializedString()} }}"; + /// /// Releases all resources consumed by the current . /// @@ -41,7 +136,7 @@ protected override void Dispose(Boolean disposing) { if (disposing) { - throw new NotImplementedException(); + KeyMemory?.Dispose(); } } finally @@ -49,5 +144,87 @@ protected override void Dispose(Boolean disposing) base.Dispose(disposing); } } + + /// + /// Converts the value of the current to a secure bit field. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + /// + /// A secure bit field containing a representation of the current . + /// + protected sealed override ISecureMemory ToSecureMemory(IConcurrencyControlToken controlToken) + { + var purposeLength = sizeof(AsymmetricKeyPurpose); + var purposeStartIndex = 0; + var algorithmLength = sizeof(DigitalSignatureAlgorithmSpecification); + var algorithmStartIndex = purposeStartIndex + purposeLength; + var keyMemoryLength = KeyMemory.LengthInBytes; + var keyMemoryStartIndex = algorithmStartIndex + algorithmLength; + var secureMemoryLength = purposeLength + algorithmLength + keyMemoryLength; + var secureMemory = new SecureMemory(secureMemoryLength); + + try + { + secureMemory.Access(memory => + { + memory[purposeStartIndex] = Convert.ToByte(Purpose); + memory[algorithmStartIndex] = Convert.ToByte(Algorithm); + Array.Copy(KeyMemory, 0, memory, keyMemoryStartIndex, keyMemoryLength); + }); + + return secureMemory; + } + catch + { + secureMemory.Dispose(); + throw; + } + } + + /// + /// Gets the asymmetric-key algorithm for which the key is used. + /// + public DigitalSignatureAlgorithmSpecification Algorithm + { + get; + } + + /// + /// Gets the date and time when the current expires and is no longer valid for use. + /// + public DateTime ExpirationTimeStamp + { + get; + } + + /// + /// Gets a value indicating whether or not the current is expired. + /// + public Boolean IsExpired => TimeStamp.Current >= ExpirationTimeStamp; + + /// + /// Gets the globally unique identifier for the key pair to which the current + /// belongs. + /// + public Guid KeyPairIdentifier + { + get; + } + + /// + /// Gets a value that specifies what the current is used for. + /// + public AsymmetricKeyPurpose Purpose + { + get; + } + + /// + /// Represents the plaintext key bits for the current . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly PinnedMemory KeyMemory; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/Ecdsa/EcdsaKeyPair.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/Ecdsa/EcdsaKeyPair.cs index a76210fb..ae46d75b 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/Ecdsa/EcdsaKeyPair.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/Ecdsa/EcdsaKeyPair.cs @@ -2,15 +2,18 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= +using RapidField.SolidInstruments.Collections; +using RapidField.SolidInstruments.Cryptography.Extensions; using System; using System.Diagnostics; +using System.Security.Cryptography; namespace RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature.Ecdsa { /// /// Represents an asymmetric, elliptic curve digital signature algorithm and the key bits for an asymmetric key pair. /// - public sealed class EcdsaKeyPair : DigitalSignatureKeyPair + public sealed class EcdsaKeyPair : DigitalSignatureKeyPair { /// /// Initializes a new instance of the class. @@ -19,11 +22,11 @@ public sealed class EcdsaKeyPair : DigitalSignatureKeyPair /// - /// is equal to . + /// is equal to . /// [DebuggerHidden] - internal EcdsaKeyPair(AsymmetricAlgorithmSpecification algorithm) - : base(algorithm) + internal EcdsaKeyPair(DigitalSignatureAlgorithmSpecification algorithm) + : base(Guid.NewGuid(), algorithm, DefaultKeyLifespanDuration) { return; } @@ -34,19 +37,59 @@ internal EcdsaKeyPair(AsymmetricAlgorithmSpecification algorithm) /// /// A value indicating whether or not managed resources should be released. /// - protected override void Dispose(Boolean disposing) + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Initializes the private key. + /// + /// + /// The asymmetric-key algorithm for which the key pair is used. + /// + /// + /// The algorithm provider that facilitates cryptographic operations for the key pair. + /// + /// + /// The private key. + /// + protected sealed override EcdsaPrivateKey InitializePrivateKey(DigitalSignatureAlgorithmSpecification algorithm, ECDsa provider) + { + using var keySource = new PinnedMemory(provider.ExportECPrivateKey()); + return new EcdsaPrivateKey(Identifier, algorithm, keySource, KeyLifespanDuration); + } + + /// + /// Initializes the algorithm provider that facilitates cryptographic operations for the key pair. + /// + /// + /// The asymmetric-key algorithm for which the key pair is used. + /// + /// + /// The algorithm provider that facilitates cryptographic operations for the key pair. + /// + protected sealed override ECDsa InitializeProvider(DigitalSignatureAlgorithmSpecification algorithm) => ECDsa.Create(algorithm.ToCurve()); + + /// + /// Initializes the private key. + /// + /// + /// The asymmetric-key algorithm for which the key pair is used. + /// + /// + /// The algorithm provider that facilitates cryptographic operations for the key pair. + /// + /// + /// The private key. + /// + protected sealed override EcdsaPublicKey InitializePublicKey(DigitalSignatureAlgorithmSpecification algorithm, ECDsa provider) { - try - { - if (disposing) - { - throw new NotImplementedException(); - } - } - finally - { - base.Dispose(disposing); - } + var keyMemory = provider.ExportSubjectPublicKeyInfo(); + return new EcdsaPublicKey(Identifier, algorithm, keyMemory, KeyLifespanDuration); } + + /// + /// Represents the default length of time for which paired keys are valid. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal static readonly TimeSpan DefaultKeyLifespanDuration = TimeSpan.FromDays(366); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/Ecdsa/EcdsaPrivateKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/Ecdsa/EcdsaPrivateKey.cs index 0465dddc..044f4061 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/Ecdsa/EcdsaPrivateKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/Ecdsa/EcdsaPrivateKey.cs @@ -2,6 +2,7 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= +using RapidField.SolidInstruments.Collections; using System; using System.Diagnostics; @@ -15,15 +16,29 @@ public sealed class EcdsaPrivateKey : DigitalSignaturePrivateKey /// /// Initializes a new instance of the class. /// + /// + /// The globally unique identifier for the key pair to which the key belongs. + /// /// /// The asymmetric-key algorithm for which the key is used. /// + /// + /// A bit field that is used to derive key bits. + /// + /// + /// The length of time for which the key is valid. + /// + /// + /// is . + /// /// - /// is equal to . + /// is equal to -or- is equal + /// to -or- is less + /// than eight seconds. /// [DebuggerHidden] - internal EcdsaPrivateKey(AsymmetricAlgorithmSpecification algorithm) - : base(algorithm) + internal EcdsaPrivateKey(Guid keyPairIdentifier, DigitalSignatureAlgorithmSpecification algorithm, PinnedMemory keySource, TimeSpan lifespanDuration) + : base(keyPairIdentifier, algorithm, KeyDerivationMode, keySource, lifespanDuration) { return; } @@ -34,19 +49,12 @@ internal EcdsaPrivateKey(AsymmetricAlgorithmSpecification algorithm) /// /// A value indicating whether or not managed resources should be released. /// - protected override void Dispose(Boolean disposing) - { - try - { - if (disposing) - { - throw new NotImplementedException(); - } - } - finally - { - base.Dispose(disposing); - } - } + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Represents the key derivation mode for instances. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal const CryptographicKeyDerivationMode KeyDerivationMode = CryptographicKeyDerivationMode.Truncation; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/Ecdsa/EcdsaPublicKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/Ecdsa/EcdsaPublicKey.cs index f9eeb112..28641e15 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/Ecdsa/EcdsaPublicKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/Ecdsa/EcdsaPublicKey.cs @@ -2,6 +2,7 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= +using RapidField.SolidInstruments.Core; using System; using System.Diagnostics; @@ -15,15 +16,35 @@ public sealed class EcdsaPublicKey : DigitalSignaturePublicKey /// /// Initializes a new instance of the class. /// + /// + /// The globally unique identifier for the key pair to which the key belongs. + /// /// /// The asymmetric-key algorithm for which the key is used. /// + /// + /// The plaintext key bits for the public key. + /// + /// + /// The length of time for which the key is valid. + /// + /// + /// is empty. + /// + /// + /// The length of is invalid for . + /// + /// + /// is . + /// /// - /// is equal to . + /// is equal to -or- is equal + /// to -or- is less + /// than eight seconds. /// [DebuggerHidden] - internal EcdsaPublicKey(AsymmetricAlgorithmSpecification algorithm) - : base(algorithm) + internal EcdsaPublicKey(Guid keyPairIdentifier, DigitalSignatureAlgorithmSpecification algorithm, Byte[] keyMemory, TimeSpan lifespanDuration) + : base(keyPairIdentifier, algorithm, keyMemory, lifespanDuration) { return; } @@ -34,19 +55,6 @@ internal EcdsaPublicKey(AsymmetricAlgorithmSpecification algorithm) /// /// A value indicating whether or not managed resources should be released. /// - protected override void Dispose(Boolean disposing) - { - try - { - if (disposing) - { - throw new NotImplementedException(); - } - } - finally - { - base.Dispose(disposing); - } - } + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/IDigitalSignatureKeyPair.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/IDigitalSignatureKeyPair.cs index ed96d428..c478a7b1 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/IDigitalSignatureKeyPair.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/IDigitalSignatureKeyPair.cs @@ -13,7 +13,7 @@ namespace RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature /// /// The type of the public key. /// - public interface IDigitalSignatureKeyPair : IAsymmetricKeyPair, IDigitalSignatureKeyPair + public interface IDigitalSignatureKeyPair : IAsymmetricKeyPair, IDigitalSignatureKeyPair where TPrivateKey : DigitalSignaturePrivateKey where TPublicKey : DigitalSignaturePublicKey { diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/IDigitalSignaturePrivateKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/IDigitalSignaturePrivateKey.cs index 9796c879..d1be8fae 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/IDigitalSignaturePrivateKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/IDigitalSignaturePrivateKey.cs @@ -7,7 +7,7 @@ namespace RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature /// /// Represents an asymmetric digital signature algorithm and the private key bits for an asymmetric key pair. /// - public interface IDigitalSignaturePrivateKey : IAsymmetricPrivateKey, IDigitalSignatureKey + public interface IDigitalSignaturePrivateKey : IAsymmetricPrivateKey, IDigitalSignatureKey { } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/IDigitalSignaturePublicKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/IDigitalSignaturePublicKey.cs index de4cd227..133ab8cf 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/IDigitalSignaturePublicKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/IDigitalSignaturePublicKey.cs @@ -9,5 +9,12 @@ namespace RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature /// public interface IDigitalSignaturePublicKey : IAsymmetricPublicKey, IDigitalSignatureKey { + /// + /// Gets the asymmetric-key algorithm for which the key is used. + /// + public DigitalSignatureAlgorithmSpecification Algorithm + { + get; + } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricKey.cs index bb54391f..2c7742ad 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricKey.cs @@ -9,20 +9,47 @@ namespace RapidField.SolidInstruments.Cryptography.Asymmetric /// /// Represents an asymmetric-key algorithm and the key bits for one key in an asymmetric key pair. /// - public interface IAsymmetricKey : IAsyncDisposable, IDisposable + /// + /// The type of the asymmetric algorithm for which a key is derived. + /// + public interface IAsymmetricKey : IAsymmetricKey, ICryptographicKey + where TAlgorithm : struct, Enum + { + } + + /// + /// Represents an asymmetric-key algorithm and the key bits for one key in an asymmetric key pair. + /// + public interface IAsymmetricKey : ICryptographicKey { /// - /// Converts the value of the current to a secure bit field. + /// Gets the date and time when the current expires and is no longer valid for use. /// - /// - /// A secure bit field containing a representation of the current . - /// - public ISecureMemory ToSecureMemory(); + public DateTime ExpirationTimeStamp + { + get; + } + + /// + /// Gets a value indicating whether or not the current is expired. + /// + public Boolean IsExpired + { + get; + } + + /// + /// Gets the globally unique identifier for the key pair to which the current belongs. + /// + public Guid KeyPairIdentifier + { + get; + } /// - /// Gets the asymmetric-key algorithm for which the key is used. + /// Gets a value that specifies what the current is used for. /// - public AsymmetricAlgorithmSpecification Algorithm + public AsymmetricKeyPurpose Purpose { get; } diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricKeyPair.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricKeyPair.cs index 6702bc1c..b4326ae8 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricKeyPair.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricKeyPair.cs @@ -9,6 +9,9 @@ namespace RapidField.SolidInstruments.Cryptography.Asymmetric /// /// Represents an asymmetric-key algorithm and the key bits for an asymmetric key pair. /// + /// + /// The type of the asymmetric-key algorithm for which the key pair is used. + /// /// /// A shared key type from which both the private and public key types derive. /// @@ -18,9 +21,10 @@ namespace RapidField.SolidInstruments.Cryptography.Asymmetric /// /// The type of the public key. /// - public interface IAsymmetricKeyPair : IAsymmetricKeyPair - where TKey : class, IAsymmetricKey - where TPrivateKey : class, TKey, IAsymmetricPrivateKey + public interface IAsymmetricKeyPair : IAsymmetricKeyPair + where TAlgorithm : struct, Enum + where TKey : class, ICryptographicKey + where TPrivateKey : class, TKey, IAsymmetricPrivateKey where TPublicKey : class, TKey, IAsymmetricPublicKey { /// @@ -43,12 +47,30 @@ public TPublicKey PublicKey /// /// Represents an asymmetric-key algorithm and the key bits for an asymmetric key pair. /// - public interface IAsymmetricKeyPair : IAsyncDisposable, IDisposable + /// + /// The type of the asymmetric-key algorithm for which the key pair is used. + /// + public interface IAsymmetricKeyPair : IAsymmetricKeyPair + where TAlgorithm : struct, Enum { /// /// Gets the asymmetric-key algorithm for which the key pair is used. /// - public AsymmetricAlgorithmSpecification Algorithm + public TAlgorithm Algorithm + { + get; + } + } + + /// + /// Represents an asymmetric-key algorithm and the key bits for an asymmetric key pair. + /// + public interface IAsymmetricKeyPair : IAsyncDisposable, IDisposable + { + /// + /// Gets the globally unique identifier for the current . + /// + public Guid Identifier { get; } diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricPrivateKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricPrivateKey.cs index f9700e86..311b55e3 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricPrivateKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricPrivateKey.cs @@ -2,12 +2,18 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= +using System; + namespace RapidField.SolidInstruments.Cryptography.Asymmetric { /// /// Represents an asymmetric-key algorithm and the private key bits for an asymmetric key pair. /// - public interface IAsymmetricPrivateKey : IAsymmetricKey + /// + /// The type of the asymmetric algorithm for which a key is derived. + /// + public interface IAsymmetricPrivateKey : IAsymmetricKey + where TAlgorithm : struct, Enum { } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricPublicKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricPublicKey.cs index 36bbfc1d..63a6c3be 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricPublicKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricPublicKey.cs @@ -2,6 +2,8 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= +using System; + namespace RapidField.SolidInstruments.Cryptography.Asymmetric { /// @@ -9,5 +11,15 @@ namespace RapidField.SolidInstruments.Cryptography.Asymmetric /// public interface IAsymmetricPublicKey : IAsymmetricKey { + /// + /// Converts the current to its textual Base64 representation. + /// + /// + /// A Base64 string representation of the byte collection. + /// + /// + /// The object is disposed. + /// + public String ToBase64String(); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhKeyPair.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhKeyPair.cs index 8668bd9e..9402695b 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhKeyPair.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhKeyPair.cs @@ -2,15 +2,18 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= +using RapidField.SolidInstruments.Collections; +using RapidField.SolidInstruments.Cryptography.Extensions; using System; using System.Diagnostics; +using System.Security.Cryptography; namespace RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange.Ecdh { /// /// Represents an asymmetric, elliptic curve Diffie-Hellman key exchange algorithm and the key bits for an asymmetric key pair. /// - public sealed class EcdhKeyPair : KeyExchangeKeyPair + public sealed class EcdhKeyPair : KeyExchangeKeyPair { /// /// Initializes a new instance of the class. @@ -19,11 +22,11 @@ public sealed class EcdhKeyPair : KeyExchangeKeyPair /// - /// is equal to . + /// is equal to . /// [DebuggerHidden] - internal EcdhKeyPair(AsymmetricAlgorithmSpecification algorithm) - : base(algorithm) + internal EcdhKeyPair(KeyExchangeAlgorithmSpecification algorithm) + : base(Guid.NewGuid(), algorithm, DefaultKeyLifespanDuration) { return; } @@ -34,19 +37,59 @@ internal EcdhKeyPair(AsymmetricAlgorithmSpecification algorithm) /// /// A value indicating whether or not managed resources should be released. /// - protected override void Dispose(Boolean disposing) + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Initializes the private key. + /// + /// + /// The asymmetric-key algorithm for which the key pair is used. + /// + /// + /// The algorithm provider that facilitates cryptographic operations for the key pair. + /// + /// + /// The private key. + /// + protected sealed override EcdhPrivateKey InitializePrivateKey(KeyExchangeAlgorithmSpecification algorithm, ECDiffieHellman provider) + { + using var keySource = new PinnedMemory(provider.ExportECPrivateKey()); + return new EcdhPrivateKey(Identifier, algorithm, keySource, KeyLifespanDuration); + } + + /// + /// Initializes the algorithm provider that facilitates cryptographic operations for the key pair. + /// + /// + /// The asymmetric-key algorithm for which the key pair is used. + /// + /// + /// The algorithm provider that facilitates cryptographic operations for the key pair. + /// + protected sealed override ECDiffieHellman InitializeProvider(KeyExchangeAlgorithmSpecification algorithm) => ECDiffieHellman.Create(algorithm.ToCurve()); + + /// + /// Initializes the private key. + /// + /// + /// The asymmetric-key algorithm for which the key pair is used. + /// + /// + /// The algorithm provider that facilitates cryptographic operations for the key pair. + /// + /// + /// The private key. + /// + protected sealed override EcdhPublicKey InitializePublicKey(KeyExchangeAlgorithmSpecification algorithm, ECDiffieHellman provider) { - try - { - if (disposing) - { - throw new NotImplementedException(); - } - } - finally - { - base.Dispose(disposing); - } + var keyMemory = provider.ExportSubjectPublicKeyInfo(); + return new EcdhPublicKey(Identifier, algorithm, keyMemory, KeyLifespanDuration); } + + /// + /// Represents the default length of time for which paired keys are valid. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal static readonly TimeSpan DefaultKeyLifespanDuration = TimeSpan.FromHours(8); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhPrivateKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhPrivateKey.cs index 91ece4c3..7e51d5da 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhPrivateKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhPrivateKey.cs @@ -2,6 +2,7 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= +using RapidField.SolidInstruments.Collections; using System; using System.Diagnostics; @@ -16,15 +17,29 @@ public sealed class EcdhPrivateKey : KeyExchangePrivateKey /// /// Initializes a new instance of the class. /// + /// + /// The globally unique identifier for the key pair to which the key belongs. + /// /// /// The asymmetric-key algorithm for which the key is used. /// + /// + /// A bit field that is used to derive key bits. + /// + /// + /// The length of time for which the key is valid. + /// + /// + /// is . + /// /// - /// is equal to . + /// is equal to -or- is equal + /// to -or- is less than + /// eight seconds. /// [DebuggerHidden] - internal EcdhPrivateKey(AsymmetricAlgorithmSpecification algorithm) - : base(algorithm) + internal EcdhPrivateKey(Guid keyPairIdentifier, KeyExchangeAlgorithmSpecification algorithm, PinnedMemory keySource, TimeSpan lifespanDuration) + : base(keyPairIdentifier, algorithm, KeyDerivationMode, keySource, lifespanDuration) { return; } @@ -35,19 +50,12 @@ internal EcdhPrivateKey(AsymmetricAlgorithmSpecification algorithm) /// /// A value indicating whether or not managed resources should be released. /// - protected override void Dispose(Boolean disposing) - { - try - { - if (disposing) - { - throw new NotImplementedException(); - } - } - finally - { - base.Dispose(disposing); - } - } + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Represents the key derivation mode for instances. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal const CryptographicKeyDerivationMode KeyDerivationMode = CryptographicKeyDerivationMode.Truncation; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhPublicKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhPublicKey.cs index b580cded..730e8b8f 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhPublicKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhPublicKey.cs @@ -2,6 +2,7 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= +using RapidField.SolidInstruments.Core; using System; using System.Diagnostics; @@ -16,15 +17,35 @@ public sealed class EcdhPublicKey : KeyExchangePublicKey /// /// Initializes a new instance of the class. /// + /// + /// The globally unique identifier for the key pair to which the key belongs. + /// /// /// The asymmetric-key algorithm for which the key is used. /// + /// + /// The plaintext key bits for the public key. + /// + /// + /// The length of time for which the key is valid. + /// + /// + /// is empty. + /// + /// + /// The length of is invalid for . + /// + /// + /// is . + /// /// - /// is equal to . + /// is equal to -or- is equal + /// to -or- is less than + /// eight seconds. /// [DebuggerHidden] - internal EcdhPublicKey(AsymmetricAlgorithmSpecification algorithm) - : base(algorithm) + internal EcdhPublicKey(Guid keyPairIdentifier, KeyExchangeAlgorithmSpecification algorithm, Byte[] keyMemory, TimeSpan lifespanDuration) + : base(keyPairIdentifier, algorithm, keyMemory, lifespanDuration) { return; } @@ -35,19 +56,6 @@ internal EcdhPublicKey(AsymmetricAlgorithmSpecification algorithm) /// /// A value indicating whether or not managed resources should be released. /// - protected override void Dispose(Boolean disposing) - { - try - { - if (disposing) - { - throw new NotImplementedException(); - } - } - finally - { - base.Dispose(disposing); - } - } + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/IKeyExchangeKeyPair.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/IKeyExchangeKeyPair.cs index 62fe6e59..a18a8a11 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/IKeyExchangeKeyPair.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/IKeyExchangeKeyPair.cs @@ -13,7 +13,7 @@ namespace RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange /// /// The type of the public key. /// - public interface IKeyExchangeKeyPair : IAsymmetricKeyPair, IKeyExchangeKeyPair + public interface IKeyExchangeKeyPair : IAsymmetricKeyPair, IKeyExchangeKeyPair where TPrivateKey : KeyExchangePrivateKey where TPublicKey : KeyExchangePublicKey { diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/IKeyExchangePrivateKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/IKeyExchangePrivateKey.cs index 09489bf9..1e84d74a 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/IKeyExchangePrivateKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/IKeyExchangePrivateKey.cs @@ -7,7 +7,7 @@ namespace RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange /// /// Represents an asymmetric key exchange algorithm and the private key bits for an asymmetric key pair. /// - public interface IKeyExchangePrivateKey : IAsymmetricPrivateKey, IKeyExchangeKey + public interface IKeyExchangePrivateKey : IAsymmetricPrivateKey, IKeyExchangeKey { } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/IKeyExchangePublicKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/IKeyExchangePublicKey.cs index f11b314d..7601dc7e 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/IKeyExchangePublicKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/IKeyExchangePublicKey.cs @@ -9,5 +9,12 @@ namespace RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange /// public interface IKeyExchangePublicKey : IAsymmetricPublicKey, IKeyExchangeKey { + /// + /// Gets the asymmetric-key algorithm for which the key is used. + /// + public KeyExchangeAlgorithmSpecification Algorithm + { + get; + } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangeAlgorithmSpecification.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangeAlgorithmSpecification.cs new file mode 100644 index 00000000..6edc8a4d --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangeAlgorithmSpecification.cs @@ -0,0 +1,49 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange +{ + /// + /// Defines distinct combinations of asymmetric key exchange algorithms, key bit-lengths and operational modes. + /// + public enum KeyExchangeAlgorithmSpecification : Byte + { + /// + /// The asymmetric algorithm is not specified. + /// + Unspecified = 0x00, + + /// + /// Specifies ECDH using the brainpoolP256r1 curve. + /// + EcdhBrainpoolP256r1 = 0x01, + + /// + /// Specifies ECDH using the brainpoolP384r1 curve. + /// + EcdhBrainpoolP384r1 = 0x02, + + /// + /// Specifies ECDH using the brainpoolP512r1 curve. + /// + EcdhBrainpoolP512r1 = 0x03, + + /// + /// Specifies ECDH using the nistP256 curve. + /// + EcdhNistP256 = 0x04, + + /// + /// Specifies ECDH using the nistP384 curve. + /// + EcdhNistP384 = 0x05, + + /// + /// Specifies ECDH using the nistP521 curve. + /// + EcdhNistP521 = 0x06 + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangeKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangeKey.cs deleted file mode 100644 index 2f940bb8..00000000 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangeKey.cs +++ /dev/null @@ -1,53 +0,0 @@ -// ================================================================================================================================= -// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. -// ================================================================================================================================= - -using System; - -namespace RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange -{ - /// - /// Represents an asymmetric key exchange algorithm and the key bits for one key in an asymmetric key pair. - /// - /// - /// is the default implementation of . - /// - public abstract class KeyExchangeKey : AsymmetricKey, IKeyExchangeKey - { - /// - /// Initializes a new instance of the class. - /// - /// - /// The asymmetric-key algorithm for which the key is used. - /// - /// - /// is equal to . - /// - protected KeyExchangeKey(AsymmetricAlgorithmSpecification algorithm) - : base(algorithm) - { - return; - } - - /// - /// Releases all resources consumed by the current . - /// - /// - /// A value indicating whether or not managed resources should be released. - /// - protected override void Dispose(Boolean disposing) - { - try - { - if (disposing) - { - throw new NotImplementedException(); - } - } - finally - { - base.Dispose(disposing); - } - } - } -} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangeKeyPair.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangeKeyPair.cs index 2bc5a474..0ef2e5f5 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangeKeyPair.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangeKeyPair.cs @@ -3,6 +3,7 @@ // ================================================================================================================================= using System; +using System.Security.Cryptography; namespace RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange { @@ -10,53 +11,52 @@ namespace RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange /// Represents an asymmetric key exchange algorithm and the key bits for an asymmetric key pair. /// /// - /// is the default implementation of + /// is the default implementation of /// . /// + /// + /// The type of the asymmetric algorithm provider that facilitates cryptographic operations for the key pair. + /// /// /// The type of the private key. /// /// /// The type of the public key. /// - public abstract class KeyExchangeKeyPair : AsymmetricKeyPair, IKeyExchangeKeyPair + public abstract class KeyExchangeKeyPair : AsymmetricKeyPair, IKeyExchangeKeyPair + where TProvider : AsymmetricAlgorithm where TPrivateKey : KeyExchangePrivateKey where TPublicKey : KeyExchangePublicKey { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// + /// + /// The globally unique identifier for the key pair. + /// /// /// The asymmetric-key algorithm for which the key is used. /// + /// + /// The length of time for which the paired keys are valid. + /// /// - /// is equal to . + /// is equal to -or- is equal to + /// -or- is less than + /// eight seconds. /// - protected KeyExchangeKeyPair(AsymmetricAlgorithmSpecification algorithm) - : base(algorithm) + protected KeyExchangeKeyPair(Guid identifier, KeyExchangeAlgorithmSpecification algorithm, TimeSpan keyLifespanDuration) + : base(identifier, algorithm, keyLifespanDuration) { return; } /// - /// Releases all resources consumed by the current . + /// Releases all resources consumed by the current . /// /// /// A value indicating whether or not managed resources should be released. /// - protected override void Dispose(Boolean disposing) - { - try - { - if (disposing) - { - throw new NotImplementedException(); - } - } - finally - { - base.Dispose(disposing); - } - } + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangePrivateKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangePrivateKey.cs index 8e30d55c..e8081488 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangePrivateKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangePrivateKey.cs @@ -2,7 +2,14 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= +using RapidField.SolidInstruments.Collections; +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Core.Extensions; +using RapidField.SolidInstruments.Cryptography.Extensions; using System; +using System.Diagnostics; +using System.Security.Cryptography; namespace RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange { @@ -12,42 +19,126 @@ namespace RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange /// /// is the default implementation of . /// - public abstract class KeyExchangePrivateKey : KeyExchangeKey, IKeyExchangePrivateKey + public abstract class KeyExchangePrivateKey : CryptographicKey, IKeyExchangePrivateKey { /// /// Initializes a new instance of the class. /// + /// + /// The globally unique identifier for the key pair to which the key belongs. + /// /// /// The asymmetric-key algorithm for which the key is used. /// + /// + /// The mode used to derive the output key. + /// + /// + /// A bit field that is used to derive key bits. + /// + /// + /// contains too many elements. + /// + /// + /// is . + /// /// - /// is equal to . + /// is equal to -or- is equal + /// to . /// - protected KeyExchangePrivateKey(AsymmetricAlgorithmSpecification algorithm) - : base(algorithm) + protected KeyExchangePrivateKey(Guid keyPairIdentifier, KeyExchangeAlgorithmSpecification algorithm, CryptographicKeyDerivationMode derivationMode, PinnedMemory keySource) + : this(keyPairIdentifier, algorithm, derivationMode, keySource, DefaultLifespanDuration) { return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The globally unique identifier for the key pair to which the key belongs. + /// + /// + /// The asymmetric-key algorithm for which the key is used. + /// + /// + /// The mode used to derive the output key. + /// + /// + /// A bit field that is used to derive key bits. + /// + /// + /// The length of time for which the key is valid. The default value is ninety (90) days. + /// + /// + /// contains too many elements. + /// + /// + /// is . + /// + /// + /// is equal to -or- is equal + /// to -or- is less than + /// eight seconds. + /// + protected KeyExchangePrivateKey(Guid keyPairIdentifier, KeyExchangeAlgorithmSpecification algorithm, CryptographicKeyDerivationMode derivationMode, PinnedMemory keySource, TimeSpan lifespanDuration) + : base(algorithm, derivationMode, keySource, algorithm.ToPrivateKeyBitLength()) + { + Curve = algorithm.ToCurve(); + ExpirationTimeStamp = TimeStamp.Current.Add(lifespanDuration.RejectIf().IsLessThan(MinimumLifespanDuration, nameof(lifespanDuration))); + KeyPairIdentifier = keyPairIdentifier.RejectIf().IsEqualToValue(Guid.Empty, nameof(keyPairIdentifier)); + Purpose = AsymmetricKeyPurpose.KeyExchange; + } + + /// + /// Converts the value of the current to its equivalent string representation. + /// + /// + /// A string representation of the current . + /// + public override String ToString() => $"{{ \"{nameof(Purpose)}\": {Purpose}, \"{nameof(Algorithm)}\": {Algorithm}, \"{nameof(KeyPairIdentifier)}\": {KeyPairIdentifier.ToSerializedString()} }}"; + /// /// Releases all resources consumed by the current . /// /// /// A value indicating whether or not managed resources should be released. /// - protected override void Dispose(Boolean disposing) + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Gets the date and time when the current expires and is no longer valid for use. + /// + public DateTime ExpirationTimeStamp + { + get; + } + + /// + /// Gets a value indicating whether or not the current is expired. + /// + public Boolean IsExpired => TimeStamp.Current >= ExpirationTimeStamp; + + /// + /// Gets the globally unique identifier for the key pair to which the current belongs. + /// + public Guid KeyPairIdentifier { - try - { - if (disposing) - { - throw new NotImplementedException(); - } - } - finally - { - base.Dispose(disposing); - } + get; } + + /// + /// Gets a value that specifies what the current is used for. + /// + public AsymmetricKeyPurpose Purpose + { + get; + } + + /// + /// Represents an elliptic curve matching . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal readonly ECCurve Curve; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangePublicKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangePublicKey.cs index dd3527d1..291faa5c 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangePublicKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangePublicKey.cs @@ -2,7 +2,14 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= +using RapidField.SolidInstruments.Collections; +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Core.Concurrency; +using RapidField.SolidInstruments.Core.Extensions; +using RapidField.SolidInstruments.Cryptography.Extensions; using System; +using System.Diagnostics; namespace RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange { @@ -12,25 +19,113 @@ namespace RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange /// /// is the default implementation of . /// - public abstract class KeyExchangePublicKey : KeyExchangeKey, IKeyExchangePublicKey + public abstract class KeyExchangePublicKey : CryptographicKey, IKeyExchangePublicKey { /// /// Initializes a new instance of the class. /// + /// + /// The globally unique identifier for the key pair to which the key belongs. + /// /// /// The asymmetric-key algorithm for which the key is used. /// + /// + /// The plaintext key bits for the public key. + /// + /// + /// is empty. + /// + /// + /// The length of is invalid for . + /// + /// + /// is . + /// /// - /// is equal to . + /// is equal to -or- is equal + /// to . /// - protected KeyExchangePublicKey(AsymmetricAlgorithmSpecification algorithm) - : base(algorithm) + protected KeyExchangePublicKey(Guid keyPairIdentifier, KeyExchangeAlgorithmSpecification algorithm, Byte[] keyMemory) + : this(keyPairIdentifier, algorithm, keyMemory, DefaultLifespanDuration) { return; } /// - /// Releases all resources consumed by the current . + /// Initializes a new instance of the class. + /// + /// + /// The globally unique identifier for the key pair to which the key belongs. + /// + /// + /// The asymmetric-key algorithm for which the key is used. + /// + /// + /// The plaintext key bits for the public key. + /// + /// + /// The length of time for which the key is valid. The default value is ninety (90) days. + /// + /// + /// is empty. + /// + /// + /// The length of is invalid for . + /// + /// + /// is . + /// + /// + /// is equal to -or- is equal + /// to -or- is less than + /// eight seconds. + /// + protected KeyExchangePublicKey(Guid keyPairIdentifier, KeyExchangeAlgorithmSpecification algorithm, Byte[] keyMemory, TimeSpan lifespanDuration) + : base() + { + Algorithm = algorithm.RejectIf().IsEqualToValue(KeyExchangeAlgorithmSpecification.Unspecified, nameof(algorithm)); + ExpirationTimeStamp = TimeStamp.Current.Add(lifespanDuration.RejectIf().IsLessThan(MinimumLifespanDuration, nameof(lifespanDuration))); + KeyMemory = new PinnedMemory(keyMemory.RejectIf().IsNullOrEmpty(nameof(keyMemory)).OrIf(argument => argument.Length != (algorithm.ToPublicKeyBitLength() / 8), nameof(keyMemory), $"The length of the specified key is invalid for the specified algorithm, \"{algorithm}\"."), true); + KeyPairIdentifier = keyPairIdentifier.RejectIf().IsEqualToValue(Guid.Empty, nameof(keyPairIdentifier)); + Purpose = AsymmetricKeyPurpose.KeyExchange; + } + + /// + /// Converts the current to its textual Base64 representation. + /// + /// + /// A Base64 string representation of the byte collection. + /// + /// + /// The object is disposed. + /// + public String ToBase64String() + { + RejectIfDisposed(); + var result = (String)null; + + using (var secureMemory = ToSecureMemory()) + { + secureMemory.Access(memory => + { + result = memory.ToBase64String(); + }); + } + + return result; + } + + /// + /// Converts the value of the current to its equivalent string representation. + /// + /// + /// A string representation of the current . + /// + public override String ToString() => $"{{ \"{nameof(Purpose)}\": {Purpose}, \"{nameof(Algorithm)}\": {Algorithm}, \"{nameof(KeyPairIdentifier)}\": {KeyPairIdentifier.ToSerializedString()} }}"; + + /// + /// Releases all resources consumed by the current . /// /// /// A value indicating whether or not managed resources should be released. @@ -41,7 +136,7 @@ protected override void Dispose(Boolean disposing) { if (disposing) { - throw new NotImplementedException(); + KeyMemory?.Dispose(); } } finally @@ -49,5 +144,86 @@ protected override void Dispose(Boolean disposing) base.Dispose(disposing); } } + + /// + /// Converts the value of the current to a secure bit field. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + /// + /// A secure bit field containing a representation of the current . + /// + protected sealed override ISecureMemory ToSecureMemory(IConcurrencyControlToken controlToken) + { + var purposeLength = sizeof(AsymmetricKeyPurpose); + var purposeStartIndex = 0; + var algorithmLength = sizeof(KeyExchangeAlgorithmSpecification); + var algorithmStartIndex = purposeStartIndex + purposeLength; + var keyMemoryLength = KeyMemory.LengthInBytes; + var keyMemoryStartIndex = algorithmStartIndex + algorithmLength; + var secureMemoryLength = purposeLength + algorithmLength + keyMemoryLength; + var secureMemory = new SecureMemory(secureMemoryLength); + + try + { + secureMemory.Access(memory => + { + memory[purposeStartIndex] = Convert.ToByte(Purpose); + memory[algorithmStartIndex] = Convert.ToByte(Algorithm); + Array.Copy(KeyMemory, 0, memory, keyMemoryStartIndex, keyMemoryLength); + }); + + return secureMemory; + } + catch + { + secureMemory.Dispose(); + throw; + } + } + + /// + /// Gets the asymmetric-key algorithm for which the key is used. + /// + public KeyExchangeAlgorithmSpecification Algorithm + { + get; + } + + /// + /// Gets the date and time when the current expires and is no longer valid for use. + /// + public DateTime ExpirationTimeStamp + { + get; + } + + /// + /// Gets a value indicating whether or not the current is expired. + /// + public Boolean IsExpired => TimeStamp.Current >= ExpirationTimeStamp; + + /// + /// Gets the globally unique identifier for the key pair to which the current belongs. + /// + public Guid KeyPairIdentifier + { + get; + } + + /// + /// Gets a value that specifies what the current is used for. + /// + public AsymmetricKeyPurpose Purpose + { + get; + } + + /// + /// Represents the plaintext key bits for the current . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly PinnedMemory KeyMemory; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/CryptographicKey.cs b/src/RapidField.SolidInstruments.Cryptography/CryptographicKey.cs index 3329b418..2269900c 100644 --- a/src/RapidField.SolidInstruments.Cryptography/CryptographicKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/CryptographicKey.cs @@ -50,6 +50,12 @@ public abstract class CryptographicKey : CryptographicKey, ICryptogr /// /// The length of the derived key, in bits. /// + /// + /// contains too many elements. + /// + /// + /// is . + /// /// /// is equal to the default/unspecified value -or- /// is less than or equal to zero. @@ -69,7 +75,7 @@ protected CryptographicKey(TAlgorithm algorithm, CryptographicKeyDerivationMode BlockCount = (KeySourceWordCount / BlockWordCount); // Copy in the key source bits. - KeySource.Access(memory => Array.Copy(keySource, memory, memory.Length)); + KeySource.Access(memory => Array.Copy(keySource.RejectIf().IsNull(nameof(keySource)).OrIf(argument => argument.Length > KeySourceLengthInBytes, nameof(keySource), "The specified key source contains too many elements.").TargetArgument, memory, keySource.Length)); } /// @@ -188,6 +194,14 @@ public ISecureMemory ToDerivedKeyBytes() } } + /// + /// Converts the value of the current to its equivalent string representation. + /// + /// + /// A string representation of the current . + /// + public override String ToString() => $"{{ \"{nameof(Algorithm)}\": {Algorithm} }}"; + /// /// Releases all resources consumed by the current . /// @@ -218,7 +232,7 @@ protected override void Dispose(Boolean disposing) /// A token that represents and manages contextual thread safety. /// /// - /// A secure bit field containing a representation of the current . + /// A secure bit field containing a representation of the current . /// protected sealed override ISecureMemory ToSecureMemory(IConcurrencyControlToken controlToken) { @@ -507,8 +521,7 @@ public TAlgorithm Algorithm } /// - /// Represents a cryptographic algorithm and source bits for a derived key, encapsulates key derivation operations and secures - /// key bits in memory. + /// Represents a cryptographic algorithm and key bits. /// /// /// is the default implementation of . @@ -661,6 +674,18 @@ internal static PinnedMemory DeriveKeySourceBytesFromPassword(IPassword password [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal const Int32 SecureMemoryEncryptionAlgorithmBlockSizeInBytes = 16; + /// + /// Represents the default lifespan for a cryptographic key. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal static readonly TimeSpan DefaultLifespanDuration = TimeSpan.FromDays(90); + + /// + /// Represents the minimum allowable lifespan for a cryptographic key. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal static readonly TimeSpan MinimumLifespanDuration = TimeSpan.FromSeconds(8); + /// /// Represents the encoding that is used when evaluating passwords. /// diff --git a/src/RapidField.SolidInstruments.Cryptography/Extensions/DigitalSignatureAlgorithmSpecificationExtensions.cs b/src/RapidField.SolidInstruments.Cryptography/Extensions/DigitalSignatureAlgorithmSpecificationExtensions.cs new file mode 100644 index 00000000..9e58b219 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Extensions/DigitalSignatureAlgorithmSpecificationExtensions.cs @@ -0,0 +1,83 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature; +using System; +using System.Diagnostics; +using System.Security.Cryptography; + +namespace RapidField.SolidInstruments.Cryptography.Extensions +{ + /// + /// Extends the enumeration with cryptographic features. + /// + internal static class DigitalSignatureAlgorithmSpecificationExtensions + { + /// + /// Returns a new matching the current . + /// + /// + /// The current . + /// + /// + /// A new matching the current . + /// + [DebuggerHidden] + internal static ECCurve ToCurve(this DigitalSignatureAlgorithmSpecification target) => target switch + { + DigitalSignatureAlgorithmSpecification.Unspecified => default, + DigitalSignatureAlgorithmSpecification.EcdsaBrainpoolP256r1 => ECCurve.NamedCurves.brainpoolP256r1, + DigitalSignatureAlgorithmSpecification.EcdsaBrainpoolP384r1 => ECCurve.NamedCurves.brainpoolP384r1, + DigitalSignatureAlgorithmSpecification.EcdsaBrainpoolP512r1 => ECCurve.NamedCurves.brainpoolP512r1, + DigitalSignatureAlgorithmSpecification.EcdsaNistP256 => ECCurve.NamedCurves.nistP256, + DigitalSignatureAlgorithmSpecification.EcdsaNistP384 => ECCurve.NamedCurves.nistP384, + DigitalSignatureAlgorithmSpecification.EcdsaNistP521 => ECCurve.NamedCurves.nistP521, + _ => throw new ArgumentException($"{target} is not a supported {nameof(DigitalSignatureAlgorithmSpecification)}.", nameof(target)) + }; + + /// + /// Returns the private key bit-length component of the current . + /// + /// + /// The current . + /// + /// + /// The bit-length of the private key for the current . + /// + [DebuggerHidden] + internal static Int32 ToPrivateKeyBitLength(this DigitalSignatureAlgorithmSpecification target) => target switch + { + DigitalSignatureAlgorithmSpecification.Unspecified => default, + DigitalSignatureAlgorithmSpecification.EcdsaBrainpoolP256r1 => 976, + DigitalSignatureAlgorithmSpecification.EcdsaBrainpoolP384r1 => 1368, + DigitalSignatureAlgorithmSpecification.EcdsaBrainpoolP512r1 => 1768, + DigitalSignatureAlgorithmSpecification.EcdsaNistP256 => 968, + DigitalSignatureAlgorithmSpecification.EcdsaNistP384 => 1336, + DigitalSignatureAlgorithmSpecification.EcdsaNistP521 => 1784, + _ => throw new ArgumentException($"{target} is not a supported {nameof(DigitalSignatureAlgorithmSpecification)}.", nameof(target)) + }; + + /// + /// Returns the public key bit-length component of the current . + /// + /// + /// The current . + /// + /// + /// The bit-length of the public key for the current . + /// + [DebuggerHidden] + internal static Int32 ToPublicKeyBitLength(this DigitalSignatureAlgorithmSpecification target) => target switch + { + DigitalSignatureAlgorithmSpecification.Unspecified => default, + DigitalSignatureAlgorithmSpecification.EcdsaBrainpoolP256r1 => 736, + DigitalSignatureAlgorithmSpecification.EcdsaBrainpoolP384r1 => 992, + DigitalSignatureAlgorithmSpecification.EcdsaBrainpoolP512r1 => 1264, + DigitalSignatureAlgorithmSpecification.EcdsaNistP256 => 728, + DigitalSignatureAlgorithmSpecification.EcdsaNistP384 => 960, + DigitalSignatureAlgorithmSpecification.EcdsaNistP521 => 1264, + _ => throw new ArgumentException($"{target} is not a supported {nameof(DigitalSignatureAlgorithmSpecification)}.", nameof(target)) + }; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Extensions/KeyExchangeAlgorithmSpecificationExtensions.cs b/src/RapidField.SolidInstruments.Cryptography/Extensions/KeyExchangeAlgorithmSpecificationExtensions.cs new file mode 100644 index 00000000..1dcd5173 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Extensions/KeyExchangeAlgorithmSpecificationExtensions.cs @@ -0,0 +1,83 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange; +using System; +using System.Diagnostics; +using System.Security.Cryptography; + +namespace RapidField.SolidInstruments.Cryptography.Extensions +{ + /// + /// Extends the enumeration with cryptographic features. + /// + internal static class KeyExchangeAlgorithmSpecificationExtensions + { + /// + /// Returns a new matching the current . + /// + /// + /// The current . + /// + /// + /// A new matching the current . + /// + [DebuggerHidden] + internal static ECCurve ToCurve(this KeyExchangeAlgorithmSpecification target) => target switch + { + KeyExchangeAlgorithmSpecification.Unspecified => default, + KeyExchangeAlgorithmSpecification.EcdhBrainpoolP256r1 => ECCurve.NamedCurves.brainpoolP256r1, + KeyExchangeAlgorithmSpecification.EcdhBrainpoolP384r1 => ECCurve.NamedCurves.brainpoolP384r1, + KeyExchangeAlgorithmSpecification.EcdhBrainpoolP512r1 => ECCurve.NamedCurves.brainpoolP512r1, + KeyExchangeAlgorithmSpecification.EcdhNistP256 => ECCurve.NamedCurves.nistP256, + KeyExchangeAlgorithmSpecification.EcdhNistP384 => ECCurve.NamedCurves.nistP384, + KeyExchangeAlgorithmSpecification.EcdhNistP521 => ECCurve.NamedCurves.nistP521, + _ => throw new ArgumentException($"{target} is not a supported {nameof(KeyExchangeAlgorithmSpecification)}.", nameof(target)) + }; + + /// + /// Returns the private key bit-length component of the current . + /// + /// + /// The current . + /// + /// + /// The bit-length of the private key for the current . + /// + [DebuggerHidden] + internal static Int32 ToPrivateKeyBitLength(this KeyExchangeAlgorithmSpecification target) => target switch + { + KeyExchangeAlgorithmSpecification.Unspecified => default, + KeyExchangeAlgorithmSpecification.EcdhBrainpoolP256r1 => 976, + KeyExchangeAlgorithmSpecification.EcdhBrainpoolP384r1 => 1368, + KeyExchangeAlgorithmSpecification.EcdhBrainpoolP512r1 => 1768, + KeyExchangeAlgorithmSpecification.EcdhNistP256 => 968, + KeyExchangeAlgorithmSpecification.EcdhNistP384 => 1336, + KeyExchangeAlgorithmSpecification.EcdhNistP521 => 1784, + _ => throw new ArgumentException($"{target} is not a supported {nameof(KeyExchangeAlgorithmSpecification)}.", nameof(target)) + }; + + /// + /// Returns the public key bit-length component of the current . + /// + /// + /// The current . + /// + /// + /// The bit-length of the public key for the current . + /// + [DebuggerHidden] + internal static Int32 ToPublicKeyBitLength(this KeyExchangeAlgorithmSpecification target) => target switch + { + KeyExchangeAlgorithmSpecification.Unspecified => default, + KeyExchangeAlgorithmSpecification.EcdhBrainpoolP256r1 => 576, + KeyExchangeAlgorithmSpecification.EcdhBrainpoolP384r1 => 832, + KeyExchangeAlgorithmSpecification.EcdhBrainpoolP512r1 => 1088, + KeyExchangeAlgorithmSpecification.EcdhNistP256 => 576, + KeyExchangeAlgorithmSpecification.EcdhNistP384 => 832, + KeyExchangeAlgorithmSpecification.EcdhNistP521 => 1120, + _ => throw new ArgumentException($"{target} is not a supported {nameof(KeyExchangeAlgorithmSpecification)}.", nameof(target)) + }; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/ICryptographicKey.cs b/src/RapidField.SolidInstruments.Cryptography/ICryptographicKey.cs index c4572e43..8e213d69 100644 --- a/src/RapidField.SolidInstruments.Cryptography/ICryptographicKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/ICryptographicKey.cs @@ -29,7 +29,7 @@ public interface ICryptographicKey : ICryptographicKey public ISecureMemory ToDerivedKeyBytes(); /// - /// Gets the symmetric-key algorithm for which a key is derived. + /// Gets the cryptographic algorithm for which a key is derived. /// public TAlgorithm Algorithm { @@ -38,8 +38,7 @@ public TAlgorithm Algorithm } /// - /// Represents a cryptographic algorithm and source bits for a derived key, encapsulates key derivation operations and secures - /// key bits in memory. + /// Represents a cryptographic algorithm and key bits. /// public interface ICryptographicKey : IAsyncDisposable, IDisposable { diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/CascadingSymmetricKey.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/CascadingSymmetricKey.cs index d2f4dfc6..2b99dcf6 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Symmetric/CascadingSymmetricKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Symmetric/CascadingSymmetricKey.cs @@ -456,7 +456,7 @@ private static CascadingSymmetricKey FromPassword(IPassword password, Cryptograp var totalSourceLengthInBytes = singleKeySourceLengthInBytes * keyDepth; var keys = new ISymmetricKey[keyDepth]; - using (var keySourceBytes = CryptographicKey.DeriveKeySourceBytesFromPassword(password, totalSourceLengthInBytes)) + using (var keySourceBytes = DeriveKeySourceBytesFromPassword(password, totalSourceLengthInBytes)) { for (var i = 0; i < keyDepth; i++) { diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKey.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKey.cs index 88d2818c..086630fa 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKey.cs @@ -33,6 +33,12 @@ public sealed class SymmetricKey : CryptographicKey /// A bit field that is used to derive key bits. /// + /// + /// contains too many elements. + /// + /// + /// is . + /// /// /// is equal to or /// is equal to . diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Extensions/AlgorithmSpecificationExtensionsTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Extensions/AlgorithmSpecificationExtensionsTests.cs new file mode 100644 index 00000000..4990320b --- /dev/null +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Extensions/AlgorithmSpecificationExtensionsTests.cs @@ -0,0 +1,116 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature; +using RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange; +using RapidField.SolidInstruments.Cryptography.Extensions; +using System.Security.Cryptography; + +namespace RapidField.SolidInstruments.Cryptography.UnitTests.Extensions +{ + [TestClass] + public class AlgorithmSpecificationExtensionsTests + { + [TestMethod] + public void ExtensionMappingOutput_ShouldMatchRelevantDotNetAnalogs_ForBrainpoolP256r1() + { + // Arrange. + var curve = ECCurve.NamedCurves.brainpoolP256r1; + var keyExchangeSpecification = KeyExchangeAlgorithmSpecification.EcdhBrainpoolP256r1; + var digitalSignatureSpecification = DigitalSignatureAlgorithmSpecification.EcdsaBrainpoolP256r1; + + // Assert. + ExtensionMappingOutput_ShouldMatchRelevantDotNetAnalogs_ForEllipticCurveAlgorithms(curve, keyExchangeSpecification, digitalSignatureSpecification); + } + + [TestMethod] + public void ExtensionMappingOutput_ShouldMatchRelevantDotNetAnalogs_ForBrainpoolP384r1() + { + // Arrange. + var curve = ECCurve.NamedCurves.brainpoolP384r1; + var keyExchangeSpecification = KeyExchangeAlgorithmSpecification.EcdhBrainpoolP384r1; + var digitalSignatureSpecification = DigitalSignatureAlgorithmSpecification.EcdsaBrainpoolP384r1; + + // Assert. + ExtensionMappingOutput_ShouldMatchRelevantDotNetAnalogs_ForEllipticCurveAlgorithms(curve, keyExchangeSpecification, digitalSignatureSpecification); + } + + [TestMethod] + public void ExtensionMappingOutput_ShouldMatchRelevantDotNetAnalogs_ForBrainpoolP512r1() + { + // Arrange. + var curve = ECCurve.NamedCurves.brainpoolP512r1; + var keyExchangeSpecification = KeyExchangeAlgorithmSpecification.EcdhBrainpoolP512r1; + var digitalSignatureSpecification = DigitalSignatureAlgorithmSpecification.EcdsaBrainpoolP512r1; + + // Assert. + ExtensionMappingOutput_ShouldMatchRelevantDotNetAnalogs_ForEllipticCurveAlgorithms(curve, keyExchangeSpecification, digitalSignatureSpecification); + } + + [TestMethod] + public void ExtensionMappingOutput_ShouldMatchRelevantDotNetAnalogs_ForNistP256() + { + // Arrange. + var curve = ECCurve.NamedCurves.nistP256; + var keyExchangeSpecification = KeyExchangeAlgorithmSpecification.EcdhNistP256; + var digitalSignatureSpecification = DigitalSignatureAlgorithmSpecification.EcdsaNistP256; + + // Assert. + ExtensionMappingOutput_ShouldMatchRelevantDotNetAnalogs_ForEllipticCurveAlgorithms(curve, keyExchangeSpecification, digitalSignatureSpecification); + } + + [TestMethod] + public void ExtensionMappingOutput_ShouldMatchRelevantDotNetAnalogs_ForNistP384() + { + // Arrange. + var curve = ECCurve.NamedCurves.nistP384; + var keyExchangeSpecification = KeyExchangeAlgorithmSpecification.EcdhNistP384; + var digitalSignatureSpecification = DigitalSignatureAlgorithmSpecification.EcdsaNistP384; + + // Assert. + ExtensionMappingOutput_ShouldMatchRelevantDotNetAnalogs_ForEllipticCurveAlgorithms(curve, keyExchangeSpecification, digitalSignatureSpecification); + } + + [TestMethod] + public void ExtensionMappingOutput_ShouldMatchRelevantDotNetAnalogs_ForNistP521() + { + // Arrange. + var curve = ECCurve.NamedCurves.nistP521; + var keyExchangeSpecification = KeyExchangeAlgorithmSpecification.EcdhNistP521; + var digitalSignatureSpecification = DigitalSignatureAlgorithmSpecification.EcdsaNistP521; + + // Assert. + ExtensionMappingOutput_ShouldMatchRelevantDotNetAnalogs_ForEllipticCurveAlgorithms(curve, keyExchangeSpecification, digitalSignatureSpecification); + } + + private static void ExtensionMappingOutput_ShouldMatchRelevantDotNetAnalogs_ForEllipticCurveAlgorithms(ECCurve curve, KeyExchangeAlgorithmSpecification keyExchangeSpecification, DigitalSignatureAlgorithmSpecification digitalSignatureSpecification) + { + // Arrange. + using var ecdhAlgorithm = ECDiffieHellman.Create(curve); + using var ecdsaAlgorithm = ECDsa.Create(curve); + var expectedEcdhPrivateKeyLengthInBytes = ecdhAlgorithm.ExportECPrivateKey().Length; + var expectedEcdhPublicKeyLengthInBytes = ecdhAlgorithm.PublicKey.ToByteArray().Length; + var expectedEcdsaPrivateKeyLengthInBytes = ecdsaAlgorithm.ExportECPrivateKey().Length; + var expectedEcdsaPublicKeyLengthInBytes = ecdsaAlgorithm.ExportSubjectPublicKeyInfo().Length; + + // Act. + var actualEcdhCurve = keyExchangeSpecification.ToCurve(); + var actualEcdsaCurve = digitalSignatureSpecification.ToCurve(); + var actualEcdhPrivateKeyLengthInBytes = keyExchangeSpecification.ToPrivateKeyBitLength() / 8; + var actualEcdhPublicKeyLengthInBytes = keyExchangeSpecification.ToPublicKeyBitLength() / 8; + var actualEcdsaPrivateKeyLengthInBytes = digitalSignatureSpecification.ToPrivateKeyBitLength() / 8; + var actualEcdsaPublicKeyLengthInBytes = digitalSignatureSpecification.ToPublicKeyBitLength() / 8; + + // Assert. + actualEcdhCurve.Oid.FriendlyName.Should().Be(curve.Oid.FriendlyName); + actualEcdsaCurve.Oid.FriendlyName.Should().Be(curve.Oid.FriendlyName); + actualEcdhPrivateKeyLengthInBytes.Should().Be(expectedEcdhPrivateKeyLengthInBytes); + actualEcdhPublicKeyLengthInBytes.Should().Be(expectedEcdhPublicKeyLengthInBytes); + actualEcdsaPrivateKeyLengthInBytes.Should().Be(expectedEcdsaPrivateKeyLengthInBytes); + actualEcdsaPublicKeyLengthInBytes.Should().Be(expectedEcdsaPublicKeyLengthInBytes); + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/RapidField.SolidInstruments.Cryptography.UnitTests.csproj b/test/RapidField.SolidInstruments.Cryptography.UnitTests/RapidField.SolidInstruments.Cryptography.UnitTests.csproj index df887ac5..849c3dfa 100644 --- a/test/RapidField.SolidInstruments.Cryptography.UnitTests/RapidField.SolidInstruments.Cryptography.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/RapidField.SolidInstruments.Cryptography.UnitTests.csproj @@ -43,4 +43,7 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in + + + \ No newline at end of file From 0f40472c3065350617283ffaa60d1291fc15e09b Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Tue, 30 Jun 2020 21:53:15 -0500 Subject: [PATCH 42/55] Add AsymmetricPublicKeyModel. --- ...ymmetricPublicKeyCompositionInformation.cs | 125 ++++++++++ .../Asymmetric/AsymmetricPublicKeyModel.cs | 226 ++++++++++++++++++ .../DigitalSignaturePublicKey.cs | 95 +++++++- .../Asymmetric/IAsymmetricPublicKey.cs | 13 +- .../Asymmetric/IAsymmetricPublicKeyModel.cs | 85 +++++++ .../KeyExchange/Ecdh/EcdhKeyPair.cs | 55 +++++ .../KeyExchange/IKeyExchangeKeyPair.cs | 27 +++ .../KeyExchange/KeyExchangeKeyPair.cs | 70 ++++++ .../KeyExchange/KeyExchangePublicKey.cs | 95 +++++++- .../Hashing/HashTree.cs | 2 +- .../TreeNodeTests.cs | 2 +- 11 files changed, 768 insertions(+), 27 deletions(-) create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricPublicKeyCompositionInformation.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricPublicKeyModel.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricPublicKeyModel.cs diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricPublicKeyCompositionInformation.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricPublicKeyCompositionInformation.cs new file mode 100644 index 00000000..f8863b66 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricPublicKeyCompositionInformation.cs @@ -0,0 +1,125 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature; +using RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange; +using System; +using System.Diagnostics; + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric +{ + /// + /// Represents information about the length and location of different pieces of information within raw, exported + /// bytes. + /// + internal sealed class AsymmetricPublicKeyCompositionInformation + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The full key from which composition information is gathered. + /// + /// + /// The purpose of the evaluated key. + /// + /// + /// The length of is invalid. + /// + /// + /// is equal to . + /// + [DebuggerHidden] + internal AsymmetricPublicKeyCompositionInformation(Memory publicKey, AsymmetricKeyPurpose purpose) + : this(purpose) + { + KeyMemoryLengthInBytes = publicKey.Length - PurposeLengthInBytes - AlgorithmLengthInBytes; + KeyMemoryStartIndex = AlgorithmStartIndex + AlgorithmLengthInBytes; + + if (KeyMemoryLengthInBytes <= 0) + { + throw new ArgumentException("The length of the specified public key is invalid.", nameof(publicKey)); + } + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The full key from which composition information is gathered. + /// + /// + /// The purpose of the evaluated key. + /// + /// + /// is less than or equal to zero -or- is equal to + /// . + /// + [DebuggerHidden] + internal AsymmetricPublicKeyCompositionInformation(Int32 keyMemoryLengthInBytes, AsymmetricKeyPurpose purpose) + : this(purpose) + { + KeyMemoryLengthInBytes = keyMemoryLengthInBytes.RejectIf().IsLessThanOrEqualTo(0, nameof(keyMemoryLengthInBytes)); + KeyMemoryStartIndex = AlgorithmStartIndex + AlgorithmLengthInBytes; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The purpose of the evaluated key. + /// + /// + /// is equal to . + /// + [DebuggerHidden] + private AsymmetricPublicKeyCompositionInformation(AsymmetricKeyPurpose purpose) + { + AlgorithmLengthInBytes = purpose.RejectIf().IsEqualToValue(AsymmetricKeyPurpose.Unspecified, nameof(purpose)) == AsymmetricKeyPurpose.DigitalSignature ? sizeof(DigitalSignatureAlgorithmSpecification) : sizeof(KeyExchangeAlgorithmSpecification); + } + + /// + /// Gets the length, in bytes, of the entire key. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal Int32 TotalLengthInBytes => PurposeLengthInBytes + AlgorithmLengthInBytes + KeyMemoryLengthInBytes; + + /// + /// Represents the zero-based starting index of the algorithm information within a key. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal const Int32 AlgorithmStartIndex = PurposeStartIndex + PurposeLengthInBytes; + + /// + /// Represents the length, in bytes, of the purpose information within a key. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal const Int32 PurposeLengthInBytes = sizeof(AsymmetricKeyPurpose); + + /// + /// Represents the zero-based starting index of the purpose information within a key. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal const Int32 PurposeStartIndex = 0; + + /// + /// Represents the length, in bytes, of the algorithm information within a key. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal readonly Int32 AlgorithmLengthInBytes; + + /// + /// Represents the length, in bytes, of the contiguous raw key bytes within a key. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal readonly Int32 KeyMemoryLengthInBytes; + + /// + /// Represents the zero-based starting index of the contiguous raw key bytes within a key. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal readonly Int32 KeyMemoryStartIndex; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricPublicKeyModel.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricPublicKeyModel.cs new file mode 100644 index 00000000..a4ec8c91 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricPublicKeyModel.cs @@ -0,0 +1,226 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Core.Extensions; +using RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature; +using RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange; +using System; +using System.Diagnostics; +using System.Runtime.Serialization; +using System.Security; + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric +{ + /// + /// Represents a serializable asymmetric-key algorithm and the public key bits for an asymmetric key pair. + /// + /// + /// is the default implementation of . + /// + [DataContract] + public sealed class AsymmetricPublicKeyModel : Model, IAsymmetricPublicKeyModel + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A model from which to copy information to create a new model. + /// + /// + /// The model key is empty. + /// + /// + /// is -or- the associated key is . + /// + public AsymmetricPublicKeyModel(IAsymmetricPublicKeyModel model) + : this(model.RejectIf().IsNull(nameof(model)).TargetArgument.Identifier, model.Key, model.ExpirationTimeStamp) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier for the model. + /// + /// + /// A textual, Base64-encoded representation of the public key. + /// + /// + /// The date and time when the associated key expires and is no longer valid for use. + /// + /// + /// is empty. + /// + /// + /// is . + /// + [DebuggerHidden] + internal AsymmetricPublicKeyModel(Guid identifier, String key, DateTime expirationTimeStamp) + : base(identifier) + { + ExpirationTimeStamp = expirationTimeStamp; + Key = key.RejectIf().IsNullOrEmpty(nameof(key)); + } + + /// + /// Extracts the digital signature algorithm specification from , if the key is purposed for digital + /// signature. + /// + /// + /// A value that specifies the distinct algorithm for the key. + /// + /// + /// The key is not purposed for digital signature. + /// + /// + /// is invalid. + /// + [DebuggerHidden] + DigitalSignatureAlgorithmSpecification IAsymmetricPublicKeyModel.ExtractDigitalSignatureAlgorithm() + { + try + { + var keyPurpose = KeyPurpose; + + return keyPurpose switch + { + AsymmetricKeyPurpose.DigitalSignature => DigitalSignaturePublicKey.ExtractAlgorithm(Key), + AsymmetricKeyPurpose.KeyExchange => throw new InvalidOperationException("The key is not purposed for digital signature."), + _ => throw new UnsupportedSpecificationException($"The specified asymmetric key purpose, {keyPurpose}, is not supported."), + }; + } + catch (Exception exception) + { + throw new SecurityException("An exception was raised while extracting the algorithm from the key model. The key is invalid.", exception); + } + } + + /// + /// Extracts the key exchange algorithm specification from , if the key is purposed for key exchange. + /// + /// + /// A value that specifies the distinct algorithm for the key. + /// + /// + /// The key is not purposed for key exchange. + /// + /// + /// is invalid. + /// + [DebuggerHidden] + KeyExchangeAlgorithmSpecification IAsymmetricPublicKeyModel.ExtractKeyExchangeAlgorithm() + { + try + { + var keyPurpose = KeyPurpose; + + return keyPurpose switch + { + AsymmetricKeyPurpose.DigitalSignature => throw new InvalidOperationException("The key is not purposed for key exchange."), + AsymmetricKeyPurpose.KeyExchange => KeyExchangePublicKey.ExtractAlgorithm(Key), + _ => throw new UnsupportedSpecificationException($"The specified asymmetric key purpose, {keyPurpose}, is not supported."), + }; + } + catch (Exception exception) + { + throw new SecurityException("An exception was raised while extracting the algorithm from the key model. The key is invalid.", exception); + } + } + + /// + /// Extracts the key memory bytes from . + /// + /// + /// The public key memory bytes. + /// + /// + /// is invalid. + /// + [DebuggerHidden] + Span IAsymmetricPublicKeyModel.ExtractKeyMemory() + { + try + { + var keyPurpose = KeyPurpose; + + return keyPurpose switch + { + AsymmetricKeyPurpose.DigitalSignature => DigitalSignaturePublicKey.ExtractKeyMemory(Key), + AsymmetricKeyPurpose.KeyExchange => KeyExchangePublicKey.ExtractKeyMemory(Key), + _ => throw new UnsupportedSpecificationException($"The specified asymmetric key purpose, {keyPurpose}, is not supported."), + }; + } + catch (Exception exception) + { + throw new SecurityException("An exception was raised while extracting the key memory from the key model. The key is invalid.", exception); + } + } + + /// + /// Extracts the purpose of the key from . + /// + /// + /// A value that specifies the purpose of the asymmetric key. + /// + /// + /// is invalid. + /// + [DebuggerHidden] + AsymmetricKeyPurpose IAsymmetricPublicKeyModel.ExtractKeyPurpose() => KeyPurpose; + + /// + /// Converts the value of the current to its equivalent string representation. + /// + /// + /// A string representation of the current . + /// + public override String ToString() => $"{{ \"{nameof(Identifier)}\": {Identifier.ToSerializedString()}, \"{nameof(ExpirationTimeStamp)}\": {ExpirationTimeStamp.ToSerializedString()} }}"; + + /// + /// Gets or sets the date and time when the associated key expires and is no longer valid for use. + /// + [DataMember] + public DateTime ExpirationTimeStamp + { + get; + set; + } + + /// + /// Gets or sets a textual, Base64-encoded representation of the public key. + /// + [DataMember] + public String Key + { + get; + set; + } + + /// + /// Gets a value that specifies the purpose of the asymmetric key. + /// + /// + /// is invalid. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private AsymmetricKeyPurpose KeyPurpose + { + get + { + try + { + return (AsymmetricKeyPurpose)Convert.FromBase64String(Key)[AsymmetricPublicKeyCompositionInformation.PurposeStartIndex]; + } + catch (Exception exception) + { + throw new SecurityException("An exception was raised while extracting the key purpose from the key model. The key is invalid.", exception); + } + } + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignaturePublicKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignaturePublicKey.cs index 7dc5c858..786d5d91 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignaturePublicKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignaturePublicKey.cs @@ -95,7 +95,7 @@ protected DigitalSignaturePublicKey(Guid keyPairIdentifier, DigitalSignatureAlgo /// Converts the current to its textual Base64 representation. /// /// - /// A Base64 string representation of the byte collection. + /// A Base64 string representation of the public key. /// /// /// The object is disposed. @@ -116,6 +116,17 @@ public String ToBase64String() return result; } + /// + /// Converts the current to a serializable model. + /// + /// + /// A serializable model representation of the public key. + /// + /// + /// The object is disposed. + /// + public AsymmetricPublicKeyModel ToModel() => new AsymmetricPublicKeyModel(KeyPairIdentifier, ToBase64String(), ExpirationTimeStamp); + /// /// Converts the value of the current to its equivalent string representation. /// @@ -124,6 +135,72 @@ public String ToBase64String() /// public override String ToString() => $"{{ \"{nameof(Purpose)}\": {Purpose}, \"{nameof(Algorithm)}\": {Algorithm}, \"{nameof(KeyPairIdentifier)}\": {KeyPairIdentifier.ToSerializedString()} }}"; + /// + /// Extracts the key memory bytes from the specified, Base64-encoded public key. + /// + /// + /// A Base64 string representation of the public key. + /// + /// + /// The public key memory bytes. + /// + /// + /// is empty. + /// + /// + /// The length of is invalid. + /// + /// + /// is . + /// + /// + /// is not a valid Base64 string. + /// + [DebuggerHidden] + internal static DigitalSignatureAlgorithmSpecification ExtractAlgorithm(String publicKey) + { + var publicKeyMemory = new Memory(Convert.FromBase64String(publicKey.RejectIf().IsNullOrEmpty(nameof(publicKey)))); + var keyCompositionInformation = new AsymmetricPublicKeyCompositionInformation(publicKeyMemory, AsymmetricKeyPurpose.DigitalSignature); + var algorithmBytes = publicKeyMemory.Slice(AsymmetricPublicKeyCompositionInformation.AlgorithmStartIndex, keyCompositionInformation.AlgorithmLengthInBytes).ToArray(); + + return algorithmBytes.Length switch + { + 1 => (DigitalSignatureAlgorithmSpecification)algorithmBytes[0], + 2 => (DigitalSignatureAlgorithmSpecification)BitConverter.ToInt16(algorithmBytes), + 4 => (DigitalSignatureAlgorithmSpecification)BitConverter.ToInt32(algorithmBytes), + _ => throw new UnsupportedSpecificationException($"The bit length of {nameof(DigitalSignatureAlgorithmSpecification)} is invalid."), + }; + } + + /// + /// Extracts the key memory bytes from the specified, Base64-encoded public key. + /// + /// + /// A Base64 string representation of the public key. + /// + /// + /// The public key memory bytes. + /// + /// + /// is empty. + /// + /// + /// The length of is invalid. + /// + /// + /// is . + /// + /// + /// is not a valid Base64 string. + /// + [DebuggerHidden] + internal static Span ExtractKeyMemory(String publicKey) + { + var publicKeyMemory = new Memory(Convert.FromBase64String(publicKey.RejectIf().IsNullOrEmpty(nameof(publicKey)))); + var keyCompositionInformation = new AsymmetricPublicKeyCompositionInformation(publicKeyMemory, AsymmetricKeyPurpose.DigitalSignature); + return publicKeyMemory.Slice(keyCompositionInformation.KeyMemoryStartIndex, keyCompositionInformation.KeyMemoryLengthInBytes).Span; + } + /// /// Releases all resources consumed by the current . /// @@ -156,22 +233,16 @@ protected override void Dispose(Boolean disposing) /// protected sealed override ISecureMemory ToSecureMemory(IConcurrencyControlToken controlToken) { - var purposeLength = sizeof(AsymmetricKeyPurpose); - var purposeStartIndex = 0; - var algorithmLength = sizeof(DigitalSignatureAlgorithmSpecification); - var algorithmStartIndex = purposeStartIndex + purposeLength; - var keyMemoryLength = KeyMemory.LengthInBytes; - var keyMemoryStartIndex = algorithmStartIndex + algorithmLength; - var secureMemoryLength = purposeLength + algorithmLength + keyMemoryLength; - var secureMemory = new SecureMemory(secureMemoryLength); + var keyCompositionInformation = new AsymmetricPublicKeyCompositionInformation(KeyMemory.LengthInBytes, Purpose); + var secureMemory = new SecureMemory(keyCompositionInformation.TotalLengthInBytes); try { secureMemory.Access(memory => { - memory[purposeStartIndex] = Convert.ToByte(Purpose); - memory[algorithmStartIndex] = Convert.ToByte(Algorithm); - Array.Copy(KeyMemory, 0, memory, keyMemoryStartIndex, keyMemoryLength); + memory[AsymmetricPublicKeyCompositionInformation.PurposeStartIndex] = Convert.ToByte(Purpose); + memory[AsymmetricPublicKeyCompositionInformation.AlgorithmStartIndex] = Convert.ToByte(Algorithm); + Array.Copy(KeyMemory, 0, memory, keyCompositionInformation.KeyMemoryStartIndex, keyCompositionInformation.KeyMemoryLengthInBytes); }); return secureMemory; diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricPublicKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricPublicKey.cs index 63a6c3be..445ea2d2 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricPublicKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricPublicKey.cs @@ -15,11 +15,22 @@ public interface IAsymmetricPublicKey : IAsymmetricKey /// Converts the current to its textual Base64 representation. /// /// - /// A Base64 string representation of the byte collection. + /// A Base64 string representation of the public key. /// /// /// The object is disposed. /// public String ToBase64String(); + + /// + /// Converts the current to a serializable model. + /// + /// + /// A serializable model representation of the public key. + /// + /// + /// The object is disposed. + /// + public AsymmetricPublicKeyModel ToModel(); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricPublicKeyModel.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricPublicKeyModel.cs new file mode 100644 index 00000000..63c3fbc7 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricPublicKeyModel.cs @@ -0,0 +1,85 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature; +using RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange; +using System; +using System.Security; + +namespace RapidField.SolidInstruments.Cryptography.Asymmetric +{ + /// + /// Represents a serializable asymmetric-key algorithm and the public key bits for an asymmetric key pair. + /// + public interface IAsymmetricPublicKeyModel : IModel + { + /// + /// Extracts the digital signature algorithm specification from , if the key is purposed for digital + /// signature. + /// + /// + /// A value that specifies the distinct algorithm for the key. + /// + /// + /// The key is not purposed for digital signature. + /// + /// + /// is invalid. + /// + internal DigitalSignatureAlgorithmSpecification ExtractDigitalSignatureAlgorithm(); + + /// + /// Extracts the key exchange algorithm specification from , if the key is purposed for key exchange. + /// + /// + /// A value that specifies the distinct algorithm for the key. + /// + /// + /// The key is not purposed for key exchange. + /// + /// + /// is invalid. + /// + internal KeyExchangeAlgorithmSpecification ExtractKeyExchangeAlgorithm(); + + /// + /// Extracts the key memory bytes from . + /// + /// + /// The public key memory bytes. + /// + /// + /// is invalid. + /// + internal Span ExtractKeyMemory(); + + /// + /// Extracts the purpose of the key from . + /// + /// + /// A value that specifies the purpose of the asymmetric key. + /// + /// + /// is invalid. + /// + internal AsymmetricKeyPurpose ExtractKeyPurpose(); + + /// + /// Gets the date and time when the associated key expires and is no longer valid for use. + /// + public DateTime ExpirationTimeStamp + { + get; + } + + /// + /// Gets a textual, Base64-encoded representation of the public key. + /// + public String Key + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhKeyPair.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhKeyPair.cs index 9402695b..47e31e03 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhKeyPair.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhKeyPair.cs @@ -31,6 +31,37 @@ internal EcdhKeyPair(KeyExchangeAlgorithmSpecification algorithm) return; } + /// + /// Derives material bytes that are used to create a new symmetric key that can be used to secure communications with a + /// second party by whom the specified public key was provided. + /// + /// + /// The asymmetric algorithm provider that facilitates key material derivation. + /// + /// + /// The private key which, in combination with the second party public key, is used to derive the symmetric key material. + /// + /// + /// The public key which, in combination with the first party private key, is used to derive the symmetric key material. + /// + /// + /// The resulting key material bytes. + /// + protected sealed override ISecureMemory DeriveSymmetricKeyMaterial(ECDiffieHellman provider, EcdhPrivateKey firstPartyPrivateKey, Span secondPartyPublicKey) + { + using (var keyMaterialMemory = new PinnedMemory(provider.DeriveKeyMaterial(new SecondPartyEcdhPublicKey(secondPartyPublicKey.ToArray())), true)) + { + var keyMaterial = new SecureMemory(keyMaterialMemory.LengthInBytes); + + keyMaterial.Access(memory => + { + keyMaterialMemory.ReadOnlySpan.CopyTo(memory.Span); + }); + + return keyMaterial; + } + } + /// /// Releases all resources consumed by the current . /// @@ -91,5 +122,29 @@ protected sealed override EcdhPublicKey InitializePublicKey(KeyExchangeAlgorithm /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal static readonly TimeSpan DefaultKeyLifespanDuration = TimeSpan.FromHours(8); + + /// + /// Represents an implementation of which supports reconstitution of a second party + /// public key. + /// + private sealed class SecondPartyEcdhPublicKey : ECDiffieHellmanPublicKey + { + /// + /// Initializes a new instance of the class. + /// + /// + /// Public key bytes which, in combination with first party private key bytes, are used to derive symmetric key + /// material. + /// + /// + /// is . + /// + [DebuggerHidden] + internal SecondPartyEcdhPublicKey(Byte[] keyBlob) + : base(keyBlob) + { + return; + } + } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/IKeyExchangeKeyPair.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/IKeyExchangeKeyPair.cs index a18a8a11..0d6dd58e 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/IKeyExchangeKeyPair.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/IKeyExchangeKeyPair.cs @@ -2,6 +2,9 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= +using System; +using System.Security; + namespace RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange { /// @@ -24,5 +27,29 @@ public interface IKeyExchangeKeyPair : IAsymmetricKeyPa /// public interface IKeyExchangeKeyPair : IAsymmetricKeyPair { + /// + /// Derives material bytes that are used to create a new symmetric key that can be used to secure communications with a + /// second party by whom the specified public key was provided. + /// + /// + /// A public key which, in combination with the current first party key pair, is used to derive the symmetric key material. + /// + /// + /// Material bytes that are used to create a new symmetric key that can be used to secure communications with the party who + /// provided . + /// + /// + /// The purpose and/or algorithm of does not match the current key pair. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// is invalid or an exception was raised while deriving the symmetric key. + /// + public ISecureMemory DeriveSymmetricKeyMaterial(IAsymmetricPublicKeyModel secondPartyKey); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangeKeyPair.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangeKeyPair.cs index 0ef2e5f5..11d7567e 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangeKeyPair.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangeKeyPair.cs @@ -3,6 +3,7 @@ // ================================================================================================================================= using System; +using System.Security; using System.Security.Cryptography; namespace RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange @@ -51,6 +52,75 @@ protected KeyExchangeKeyPair(Guid identifier, KeyExchangeAlgorithmSpecification return; } + /// + /// Derives material bytes that are used to create a new symmetric key that can be used to secure communications with a + /// second party by whom the specified public key was provided. + /// + /// + /// A public key which, in combination with the current first party key pair, is used to derive the symmetric key material. + /// + /// + /// Material bytes that are used to create a new symmetric key that can be used to secure communications with the party who + /// provided . + /// + /// + /// The purpose and/or algorithm of does not match the current key pair. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// is invalid or an exception was raised while deriving the symmetric key. + /// + public ISecureMemory DeriveSymmetricKeyMaterial(IAsymmetricPublicKeyModel secondPartyKey) + { + var secondPartyKeyPurpose = secondPartyKey.ExtractKeyPurpose(); + + if (secondPartyKeyPurpose != AsymmetricKeyPurpose.KeyExchange) + { + throw new ArgumentException("The purpose of the specified key does not match the current key pair.", nameof(secondPartyKey)); + } + + var secondPartyKeyAlgorithm = secondPartyKey.ExtractKeyExchangeAlgorithm(); + + if (secondPartyKeyAlgorithm != Algorithm) + { + throw new ArgumentException("The algorithm of the specified key does not match the current key pair.", nameof(secondPartyKey)); + } + + var secondPartyKeyMemory = secondPartyKey.ExtractKeyMemory(); + + try + { + return DeriveSymmetricKeyMaterial(Provider, PrivateKey, secondPartyKeyMemory); + } + catch (Exception exception) + { + throw new SecurityException("The second party key is invalid.", exception); + } + } + + /// + /// Derives material bytes that are used to create a new symmetric key that can be used to secure communications with a + /// second party by whom the specified public key was provided. + /// + /// + /// The asymmetric algorithm provider that facilitates key material derivation. + /// + /// + /// The private key which, in combination with the second party public key, is used to derive the symmetric key material. + /// + /// + /// The public key which, in combination with the first party private key, is used to derive the symmetric key material. + /// + /// + /// The resulting key material bytes. + /// + protected abstract ISecureMemory DeriveSymmetricKeyMaterial(TProvider provider, TPrivateKey firstPartyPrivateKey, Span secondPartyPublicKey); + /// /// Releases all resources consumed by the current . /// diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangePublicKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangePublicKey.cs index 291faa5c..47e1de3b 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangePublicKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangePublicKey.cs @@ -95,7 +95,7 @@ protected KeyExchangePublicKey(Guid keyPairIdentifier, KeyExchangeAlgorithmSpeci /// Converts the current to its textual Base64 representation. /// /// - /// A Base64 string representation of the byte collection. + /// A Base64 string representation of the public key. /// /// /// The object is disposed. @@ -116,6 +116,17 @@ public String ToBase64String() return result; } + /// + /// Converts the current to a serializable model. + /// + /// + /// A serializable model representation of the public key. + /// + /// + /// The object is disposed. + /// + public AsymmetricPublicKeyModel ToModel() => new AsymmetricPublicKeyModel(KeyPairIdentifier, ToBase64String(), ExpirationTimeStamp); + /// /// Converts the value of the current to its equivalent string representation. /// @@ -124,6 +135,72 @@ public String ToBase64String() /// public override String ToString() => $"{{ \"{nameof(Purpose)}\": {Purpose}, \"{nameof(Algorithm)}\": {Algorithm}, \"{nameof(KeyPairIdentifier)}\": {KeyPairIdentifier.ToSerializedString()} }}"; + /// + /// Extracts the key memory bytes from the specified, Base64-encoded public key. + /// + /// + /// A Base64 string representation of the public key. + /// + /// + /// The public key memory bytes. + /// + /// + /// is empty. + /// + /// + /// The length of is invalid. + /// + /// + /// is . + /// + /// + /// is not a valid Base64 string. + /// + [DebuggerHidden] + internal static KeyExchangeAlgorithmSpecification ExtractAlgorithm(String publicKey) + { + var publicKeyMemory = new Memory(Convert.FromBase64String(publicKey.RejectIf().IsNullOrEmpty(nameof(publicKey)))); + var keyCompositionInformation = new AsymmetricPublicKeyCompositionInformation(publicKeyMemory, AsymmetricKeyPurpose.KeyExchange); + var algorithmBytes = publicKeyMemory.Slice(AsymmetricPublicKeyCompositionInformation.AlgorithmStartIndex, keyCompositionInformation.AlgorithmLengthInBytes).ToArray(); + + return algorithmBytes.Length switch + { + 1 => (KeyExchangeAlgorithmSpecification)algorithmBytes[0], + 2 => (KeyExchangeAlgorithmSpecification)BitConverter.ToInt16(algorithmBytes), + 4 => (KeyExchangeAlgorithmSpecification)BitConverter.ToInt32(algorithmBytes), + _ => throw new UnsupportedSpecificationException($"The bit length of {nameof(KeyExchangeAlgorithmSpecification)} is invalid."), + }; + } + + /// + /// Extracts the key memory bytes from the specified, Base64-encoded public key. + /// + /// + /// A Base64 string representation of the public key. + /// + /// + /// The public key memory bytes. + /// + /// + /// is empty. + /// + /// + /// The length of is invalid. + /// + /// + /// is . + /// + /// + /// is not a valid Base64 string. + /// + [DebuggerHidden] + internal static Span ExtractKeyMemory(String publicKey) + { + var publicKeyMemory = new Memory(Convert.FromBase64String(publicKey.RejectIf().IsNullOrEmpty(nameof(publicKey)))); + var keyCompositionInformation = new AsymmetricPublicKeyCompositionInformation(publicKeyMemory, AsymmetricKeyPurpose.KeyExchange); + return publicKeyMemory.Slice(keyCompositionInformation.KeyMemoryStartIndex, keyCompositionInformation.KeyMemoryLengthInBytes).Span; + } + /// /// Releases all resources consumed by the current . /// @@ -156,22 +233,16 @@ protected override void Dispose(Boolean disposing) /// protected sealed override ISecureMemory ToSecureMemory(IConcurrencyControlToken controlToken) { - var purposeLength = sizeof(AsymmetricKeyPurpose); - var purposeStartIndex = 0; - var algorithmLength = sizeof(KeyExchangeAlgorithmSpecification); - var algorithmStartIndex = purposeStartIndex + purposeLength; - var keyMemoryLength = KeyMemory.LengthInBytes; - var keyMemoryStartIndex = algorithmStartIndex + algorithmLength; - var secureMemoryLength = purposeLength + algorithmLength + keyMemoryLength; - var secureMemory = new SecureMemory(secureMemoryLength); + var keyCompositionInformation = new AsymmetricPublicKeyCompositionInformation(KeyMemory.LengthInBytes, Purpose); + var secureMemory = new SecureMemory(keyCompositionInformation.TotalLengthInBytes); try { secureMemory.Access(memory => { - memory[purposeStartIndex] = Convert.ToByte(Purpose); - memory[algorithmStartIndex] = Convert.ToByte(Algorithm); - Array.Copy(KeyMemory, 0, memory, keyMemoryStartIndex, keyMemoryLength); + memory[AsymmetricPublicKeyCompositionInformation.PurposeStartIndex] = Convert.ToByte(Purpose); + memory[AsymmetricPublicKeyCompositionInformation.AlgorithmStartIndex] = Convert.ToByte(Algorithm); + Array.Copy(KeyMemory, 0, memory, keyCompositionInformation.KeyMemoryStartIndex, keyCompositionInformation.KeyMemoryLengthInBytes); }); return secureMemory; diff --git a/src/RapidField.SolidInstruments.Cryptography/Hashing/HashTree.cs b/src/RapidField.SolidInstruments.Cryptography/Hashing/HashTree.cs index a30174d9..ac1f5aa8 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Hashing/HashTree.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Hashing/HashTree.cs @@ -318,7 +318,7 @@ public ITreeNode RootNode /// /// Represents a node in a hash tree. /// - private class HashTreeNode : BinaryTreeNode + private sealed class HashTreeNode : BinaryTreeNode { /// /// Initializes a new instance of the class. diff --git a/test/RapidField.SolidInstruments.Collections.UnitTests/TreeNodeTests.cs b/test/RapidField.SolidInstruments.Collections.UnitTests/TreeNodeTests.cs index a209c779..94c3b319 100644 --- a/test/RapidField.SolidInstruments.Collections.UnitTests/TreeNodeTests.cs +++ b/test/RapidField.SolidInstruments.Collections.UnitTests/TreeNodeTests.cs @@ -316,7 +316,7 @@ public void RemoveChild_ShouldRaiseArgumentNullException_ForNullChildNodeArgumen action.Should().Throw($"because {nameof(childNode)} is null"); } - private class TestTreeNode : TreeNode + private sealed class TestTreeNode : TreeNode { public TestTreeNode() : base() From 807b536afc8062785bdc0c98091b7509b04e95da Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Thu, 9 Jul 2020 10:52:29 -0500 Subject: [PATCH 43/55] Introduce Argon2. --- .editorconfig | 5 + en-US_User.dic | 1 + .../Asymmetric/AsymmetricKeyPair.cs | 43 +++- .../Asymmetric/AsymmetricProcessor.cs | 66 +++++- .../DigitalSignatureKeyPair.cs | 12 +- .../DigitalSignaturePrivateKey.cs | 42 +++- .../DigitalSignatureProcessor.cs | 29 +-- .../DigitalSignaturePublicKey.cs | 42 +++- .../DigitalSignature/Ecdsa/EcdsaKeyPair.cs | 24 +- .../DigitalSignature/Ecdsa/EcdsaPrivateKey.cs | 30 +++ .../DigitalSignature/Ecdsa/EcdsaPublicKey.cs | 36 +++ .../IDigitalSignatureProcessor.cs | 4 +- .../Asymmetric/IAsymmetricKeyPair.cs | 8 + .../Asymmetric/IAsymmetricProcessor.cs | 17 +- .../KeyExchange/Ecdh/EcdhKeyPair.cs | 24 +- .../KeyExchange/Ecdh/EcdhPrivateKey.cs | 30 +++ .../KeyExchange/Ecdh/EcdhPublicKey.cs | 36 +++ .../KeyExchange/KeyExchangeKeyPair.cs | 12 +- .../KeyExchange/KeyExchangePrivateKey.cs | 42 +++- .../KeyExchange/KeyExchangeProcessor.cs | 7 +- .../KeyExchange/KeyExchangePublicKey.cs | 42 +++- .../CryptographicComponentUsage.cs | 40 ++++ .../CryptographicKey.cs | 26 +++ .../CryptographicProcessor.cs | 135 +++++++++++ ...HashingAlgorithmSpecificationExtensions.cs | 11 +- .../ISymmetricEncryptorDecryptorExtensions.cs | 18 +- .../Hashing/Argon2/Argon2HashAlgorithm.cs | 209 ++++++++++++++++++ .../Hashing/HashAlgorithmBase.cs | 187 ++++++++++++++++ .../Hashing/HashingAlgorithmSpecification.cs | 36 ++- .../Hashing/HashingProcessor.cs | 26 +-- .../Hashing/IHashingProcessor.cs | 3 +- .../Hashing/Pbkdf2/Pbkdf2HashAlgorithm.cs | 112 ++-------- .../ICryptographicComponent.cs | 20 ++ .../ICryptographicKey.cs | 29 ++- .../ICryptographicProcessor.cs | 68 ++++++ .../IManagedKeyCipher.cs | 2 +- .../ISecurityAppliance.cs | 2 +- .../ISoftwareSecurityModule.cs | 36 +++ ...Field.SolidInstruments.Cryptography.csproj | 3 + .../Secrets/IPassword.cs | 8 +- .../Secrets/Password.cs | 10 +- .../SoftwareSecurityModule.cs | 32 ++- .../Symmetric/CascadingSymmetricKey.cs | 5 + .../Symmetric/ISymmetricProcessor.cs | 3 +- .../Symmetric/SymmetricKey.cs | 5 + .../Symmetric/SymmetricProcessor.cs | 25 +-- .../Hashing/HashTreeTests.cs | 13 ++ .../Hashing/HashingProcessorTests.cs | 68 ++++++ .../Hashing/HashingStringProcessorTests.cs | 12 + 49 files changed, 1480 insertions(+), 216 deletions(-) create mode 100644 src/RapidField.SolidInstruments.Cryptography/CryptographicComponentUsage.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/CryptographicProcessor.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Hashing/Argon2/Argon2HashAlgorithm.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/Hashing/HashAlgorithmBase.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/ICryptographicComponent.cs create mode 100644 src/RapidField.SolidInstruments.Cryptography/ICryptographicProcessor.cs diff --git a/.editorconfig b/.editorconfig index 233ee712..7cfccc1b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -213,6 +213,8 @@ dotnet_naming_style.begins_with_i.required_suffix = dotnet_naming_style.begins_with_i.word_separator = dotnet_naming_style.begins_with_i.capitalization = pascal_case +#### Custom rules #### + # IDE0060: Remove unused parameter dotnet_code_quality_unused_parameters = all : silent @@ -222,5 +224,8 @@ dotnet_diagnostic.IDE0051.severity = silent # IDE0052: Remove unread private members dotnet_diagnostic.IDE0052.severity = silent +# CA1027: Mark enums with FlagsAttribute +dotnet_diagnostic.CA1027.severity = silent + # CA1031: Do not catch general exception types dotnet_diagnostic.CA1031.severity = silent \ No newline at end of file diff --git a/en-US_User.dic b/en-US_User.dic index 711e9e4d..d7e6399b 100644 --- a/en-US_User.dic +++ b/en-US_User.dic @@ -15,6 +15,7 @@ Backport backported backporting baz +bcrypt boolean bp brainpool diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricKeyPair.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricKeyPair.cs index 634aab99..773f0ab0 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricKeyPair.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricKeyPair.cs @@ -55,13 +55,17 @@ public abstract class AsymmetricKeyPair /// The length of time for which the paired keys are valid. /// + /// + /// A value indicating whether or not the key pair is constructed from serialized memory bits. + /// /// /// is equal to -or- is equal to the /// default/unspecified value -or- is less than eight seconds. /// - protected AsymmetricKeyPair(Guid identifier, TAlgorithm algorithm, TimeSpan keyLifespanDuration) + protected AsymmetricKeyPair(Guid identifier, TAlgorithm algorithm, TimeSpan keyLifespanDuration, Boolean isReconstituted) : base(identifier, algorithm, keyLifespanDuration) { + IsReconstituted = isReconstituted; LazyPrivateKey = new Lazy(InitializePrivateKey, LazyThreadSafetyMode.ExecutionAndPublication); LazyProvider = new Lazy(InitializeProvider, LazyThreadSafetyMode.ExecutionAndPublication); LazyPublicKey = new Lazy(InitializePublicKey, LazyThreadSafetyMode.ExecutionAndPublication); @@ -100,10 +104,13 @@ protected override void Dispose(Boolean disposing) /// /// The algorithm provider that facilitates cryptographic operations for the key pair. /// + /// + /// A value indicating whether or not the key pair is constructed from serialized memory bits. + /// /// /// The private key. /// - protected abstract TPrivateKey InitializePrivateKey(TAlgorithm algorithm, TProvider provider); + protected abstract TPrivateKey InitializePrivateKey(TAlgorithm algorithm, TProvider provider, Boolean isReconstituted); /// /// Initializes the algorithm provider that facilitates cryptographic operations for the key pair. @@ -111,10 +118,13 @@ protected override void Dispose(Boolean disposing) /// /// The asymmetric-key algorithm for which the key pair is used. /// + /// + /// A value indicating whether or not the key pair is constructed from serialized memory bits. + /// /// /// The algorithm provider that facilitates cryptographic operations for the key pair. /// - protected abstract TProvider InitializeProvider(TAlgorithm algorithm); + protected abstract TProvider InitializeProvider(TAlgorithm algorithm, Boolean isReconstituted); /// /// Initializes the private key. @@ -125,10 +135,13 @@ protected override void Dispose(Boolean disposing) /// /// The algorithm provider that facilitates cryptographic operations for the key pair. /// + /// + /// A value indicating whether or not the key pair is constructed from serialized memory bits. + /// /// /// The private key. /// - protected abstract TPublicKey InitializePublicKey(TAlgorithm algorithm, TProvider provider); + protected abstract TPublicKey InitializePublicKey(TAlgorithm algorithm, TProvider provider, Boolean isReconstituted); /// /// Initializes the private key. @@ -144,7 +157,7 @@ private TPrivateKey InitializePrivateKey() { try { - return InitializePrivateKey(Algorithm, Provider); + return InitializePrivateKey(Algorithm, Provider, IsReconstituted); } catch (Exception exception) { @@ -166,7 +179,7 @@ private TProvider InitializeProvider() { try { - return InitializeProvider(Algorithm); + return InitializeProvider(Algorithm, IsReconstituted); } catch (Exception exception) { @@ -188,7 +201,7 @@ private TPublicKey InitializePublicKey() { try { - return InitializePublicKey(Algorithm, Provider); + return InitializePublicKey(Algorithm, Provider, IsReconstituted); } catch (Exception exception) { @@ -212,6 +225,14 @@ private TPublicKey InitializePublicKey() [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal TProvider Provider => LazyProvider.Value; + /// + /// Represents a value indicating whether or not the current + /// is constructed from serialized + /// memory bits. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly Boolean IsReconstituted; + /// /// Represents the lazily-initialized private key. /// @@ -327,6 +348,14 @@ public Guid Identifier get; } + /// + /// Gets a value that specifies what the key pair is used for. + /// + public abstract AsymmetricKeyPurpose Purpose + { + get; + } + /// /// Gets the length of time for which the paired keys are valid. /// diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricProcessor.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricProcessor.cs index 3dcd942f..6439477b 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricProcessor.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/AsymmetricProcessor.cs @@ -2,7 +2,7 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= -using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Serialization; using System; using System.Diagnostics; using System.Security.Cryptography; @@ -10,12 +10,12 @@ namespace RapidField.SolidInstruments.Cryptography.Asymmetric { /// - /// Provides facilities for performing asymmetric key operations. + /// Provides facilities for performing asymmetric key operations upon byte arrays. /// /// /// is the default implementation of . /// - public abstract class AsymmetricProcessor : IAsymmetricProcessor + public abstract class AsymmetricProcessor : AsymmetricProcessor, IAsymmetricProcessor { /// /// Initializes a new instance of the class. @@ -27,14 +27,66 @@ public abstract class AsymmetricProcessor : IAsymmetricProcessor /// is . /// protected AsymmetricProcessor(RandomNumberGenerator randomnessProvider) + : base(randomnessProvider, new PassThroughSerializer()) { - RandomnessProvider = randomnessProvider.RejectIf().IsNull(nameof(randomnessProvider)); + return; + } + } + + /// + /// Provides facilities for performing asymmetric key operations upon typed objects. + /// + /// + /// is the default implementation of . + /// + /// + /// The type of the object upon which operations are performed. + /// + public abstract class AsymmetricProcessor : CryptographicProcessor, IAsymmetricProcessor + where T : class + { + /// + /// Initializes a new instance of the class. + /// + [DebuggerHidden] + internal AsymmetricProcessor() + : this(HardenedRandomNumberGenerator.Instance) + { + return; } /// - /// Represents a random number generator that is used to generate initialization vectors. + /// Initializes a new instance of the class. /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly RandomNumberGenerator RandomnessProvider; + /// + /// A random number generator that is used to generate initialization vectors. + /// + /// + /// is . + /// + protected AsymmetricProcessor(RandomNumberGenerator randomnessProvider) + : base(randomnessProvider) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A random number generator that is used to generate initialization vectors. + /// + /// + /// A serializer that is used to transform plaintext. + /// + /// + /// is -or- is + /// . + /// + protected AsymmetricProcessor(RandomNumberGenerator randomnessProvider, ISerializer serializer) + : base(randomnessProvider, serializer) + { + return; + } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignatureKeyPair.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignatureKeyPair.cs index 62742bf8..691cace9 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignatureKeyPair.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignatureKeyPair.cs @@ -40,13 +40,16 @@ public abstract class DigitalSignatureKeyPair /// The length of time for which the paired keys are valid. /// + /// + /// A value indicating whether or not the key pair is constructed from serialized memory bits. + /// /// /// is equal to -or- is equal to /// -or- is less /// than eight seconds. /// - protected DigitalSignatureKeyPair(Guid identifier, DigitalSignatureAlgorithmSpecification algorithm, TimeSpan keyLifespanDuration) - : base(identifier, algorithm, keyLifespanDuration) + protected DigitalSignatureKeyPair(Guid identifier, DigitalSignatureAlgorithmSpecification algorithm, TimeSpan keyLifespanDuration, Boolean isReconstituted) + : base(identifier, algorithm, keyLifespanDuration, isReconstituted) { return; } @@ -59,5 +62,10 @@ protected DigitalSignatureKeyPair(Guid identifier, DigitalSignatureAlgorithmSpec /// A value indicating whether or not managed resources should be released. /// protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Gets a value that specifies what the key pair is used for. + /// + public override sealed AsymmetricKeyPurpose Purpose => AsymmetricKeyPurpose.DigitalSignature; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignaturePrivateKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignaturePrivateKey.cs index b9f50f63..8297fa0a 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignaturePrivateKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignaturePrivateKey.cs @@ -82,10 +82,45 @@ protected DigitalSignaturePrivateKey(Guid keyPairIdentifier, DigitalSignatureAlg /// than eight seconds. /// protected DigitalSignaturePrivateKey(Guid keyPairIdentifier, DigitalSignatureAlgorithmSpecification algorithm, CryptographicKeyDerivationMode derivationMode, PinnedMemory keySource, TimeSpan lifespanDuration) + : this(keyPairIdentifier, algorithm, derivationMode, keySource, TimeStamp.Current.Add(lifespanDuration.RejectIf().IsLessThan(MinimumLifespanDuration, nameof(lifespanDuration)))) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The globally unique identifier for the key pair to which the key belongs. + /// + /// + /// The asymmetric-key algorithm for which the key is used. + /// + /// + /// The mode used to derive the output key. + /// + /// + /// A bit field that is used to derive key bits. + /// + /// + /// The date and time when the key expires and is no longer valid for use. + /// + /// + /// contains too many elements. + /// + /// + /// is . + /// + /// + /// is equal to -or- is equal + /// to -or- is + /// equal to . + /// + protected DigitalSignaturePrivateKey(Guid keyPairIdentifier, DigitalSignatureAlgorithmSpecification algorithm, CryptographicKeyDerivationMode derivationMode, PinnedMemory keySource, DateTime expirationTimeStamp) : base(algorithm, derivationMode, keySource, algorithm.ToPrivateKeyBitLength()) { Curve = algorithm.ToCurve(); - ExpirationTimeStamp = TimeStamp.Current.Add(lifespanDuration.RejectIf().IsLessThan(MinimumLifespanDuration, nameof(lifespanDuration))); + ExpirationTimeStamp = expirationTimeStamp.RejectIf().IsEqualToValue(DateTime.MaxValue, nameof(expirationTimeStamp)); KeyPairIdentifier = keyPairIdentifier.RejectIf().IsEqualToValue(Guid.Empty, nameof(keyPairIdentifier)); Purpose = AsymmetricKeyPurpose.DigitalSignature; } @@ -137,6 +172,11 @@ public AsymmetricKeyPurpose Purpose get; } + /// + /// Gets a value specifying the valid purposes and uses of the current . + /// + public override sealed CryptographicComponentUsage Usage => CryptographicComponentUsage.DigitalSignature; + /// /// Represents an elliptic curve matching . /// diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignatureProcessor.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignatureProcessor.cs index 465546fa..5734c6a3 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignatureProcessor.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignatureProcessor.cs @@ -2,7 +2,6 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= -using RapidField.SolidInstruments.Core.ArgumentValidation; using RapidField.SolidInstruments.Serialization; using System; using System.Diagnostics; @@ -57,11 +56,14 @@ public static IDigitalSignatureProcessor ForType() /// /// is the default implementation of . /// - public class DigitalSignatureProcessor : AsymmetricProcessor, IDigitalSignatureProcessor + /// + /// The type of the object that can be digitally signed. + /// + public class DigitalSignatureProcessor : AsymmetricProcessor, IDigitalSignatureProcessor where T : class { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// A random number generator that is used to generate initialization vectors. @@ -70,13 +72,13 @@ public class DigitalSignatureProcessor : AsymmetricProcessor, IDigitalSignatu /// is . /// public DigitalSignatureProcessor(RandomNumberGenerator randomnessProvider) - : this(randomnessProvider, DefaultSerializer) + : base(randomnessProvider) { return; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// A random number generator that is used to generate initialization vectors. @@ -89,9 +91,9 @@ public DigitalSignatureProcessor(RandomNumberGenerator randomnessProvider) /// . /// public DigitalSignatureProcessor(RandomNumberGenerator randomnessProvider, ISerializer serializer) - : base(randomnessProvider) + : base(randomnessProvider, serializer) { - Serializer = serializer.RejectIf().IsNull(nameof(serializer)).TargetArgument; + return; } /// @@ -99,21 +101,14 @@ public DigitalSignatureProcessor(RandomNumberGenerator randomnessProvider, ISeri /// [DebuggerHidden] internal DigitalSignatureProcessor() - : this(HardenedRandomNumberGenerator.Instance) + : base() { return; } /// - /// Represents the default serializer that is used to transform plaintext. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private static readonly ISerializer DefaultSerializer = new CompressedJsonSerializer(); - - /// - /// Represents a serializer that is used to transform plaintext. + /// Gets a value specifying the valid purposes and uses of the current . /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly ISerializer Serializer; + public override sealed CryptographicComponentUsage Usage => CryptographicComponentUsage.DigitalSignature; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignaturePublicKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignaturePublicKey.cs index 786d5d91..42d08e82 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignaturePublicKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/DigitalSignaturePublicKey.cs @@ -82,10 +82,45 @@ protected DigitalSignaturePublicKey(Guid keyPairIdentifier, DigitalSignatureAlgo /// than eight seconds. /// protected DigitalSignaturePublicKey(Guid keyPairIdentifier, DigitalSignatureAlgorithmSpecification algorithm, Byte[] keyMemory, TimeSpan lifespanDuration) + : this(keyPairIdentifier, algorithm, keyMemory, TimeStamp.Current.Add(lifespanDuration.RejectIf().IsLessThan(MinimumLifespanDuration, nameof(lifespanDuration)))) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The globally unique identifier for the key pair to which the key belongs. + /// + /// + /// The asymmetric-key algorithm for which the key is used. + /// + /// + /// The plaintext key bits for the public key. + /// + /// + /// The date and time when the key expires and is no longer valid for use. + /// + /// + /// is empty. + /// + /// + /// The length of is invalid for . + /// + /// + /// is . + /// + /// + /// is equal to -or- is equal + /// to -or- is + /// equal to . + /// + protected DigitalSignaturePublicKey(Guid keyPairIdentifier, DigitalSignatureAlgorithmSpecification algorithm, Byte[] keyMemory, DateTime expirationTimeStamp) : base() { Algorithm = algorithm.RejectIf().IsEqualToValue(DigitalSignatureAlgorithmSpecification.Unspecified, nameof(algorithm)); - ExpirationTimeStamp = TimeStamp.Current.Add(lifespanDuration.RejectIf().IsLessThan(MinimumLifespanDuration, nameof(lifespanDuration))); + ExpirationTimeStamp = expirationTimeStamp.RejectIf().IsEqualToValue(DateTime.MaxValue, nameof(expirationTimeStamp)); KeyMemory = new PinnedMemory(keyMemory.RejectIf().IsNullOrEmpty(nameof(keyMemory)).OrIf(argument => argument.Length != (algorithm.ToPublicKeyBitLength() / 8), nameof(keyMemory), $"The length of the specified key is invalid for the specified algorithm, \"{algorithm}\"."), true); KeyPairIdentifier = keyPairIdentifier.RejectIf().IsEqualToValue(Guid.Empty, nameof(keyPairIdentifier)); Purpose = AsymmetricKeyPurpose.DigitalSignature; @@ -292,6 +327,11 @@ public AsymmetricKeyPurpose Purpose get; } + /// + /// Gets a value specifying the valid purposes and uses of the current . + /// + public override sealed CryptographicComponentUsage Usage => CryptographicComponentUsage.DigitalSignature; + /// /// Represents the plaintext key bits for the current . /// diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/Ecdsa/EcdsaKeyPair.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/Ecdsa/EcdsaKeyPair.cs index ae46d75b..3051d2bd 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/Ecdsa/EcdsaKeyPair.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/Ecdsa/EcdsaKeyPair.cs @@ -21,12 +21,15 @@ public sealed class EcdsaKeyPair : DigitalSignatureKeyPair /// The asymmetric-key algorithm for which the key is used. /// + /// + /// A value indicating whether or not the key pair is constructed from serialized memory bits. + /// /// /// is equal to . /// [DebuggerHidden] - internal EcdsaKeyPair(DigitalSignatureAlgorithmSpecification algorithm) - : base(Guid.NewGuid(), algorithm, DefaultKeyLifespanDuration) + internal EcdsaKeyPair(DigitalSignatureAlgorithmSpecification algorithm, Boolean isReconstituted) + : base(Guid.NewGuid(), algorithm, DefaultKeyLifespanDuration, isReconstituted) { return; } @@ -48,10 +51,13 @@ internal EcdsaKeyPair(DigitalSignatureAlgorithmSpecification algorithm) /// /// The algorithm provider that facilitates cryptographic operations for the key pair. /// + /// + /// A value indicating whether or not the key pair is constructed from serialized memory bits. + /// /// /// The private key. /// - protected sealed override EcdsaPrivateKey InitializePrivateKey(DigitalSignatureAlgorithmSpecification algorithm, ECDsa provider) + protected sealed override EcdsaPrivateKey InitializePrivateKey(DigitalSignatureAlgorithmSpecification algorithm, ECDsa provider, Boolean isReconstituted) { using var keySource = new PinnedMemory(provider.ExportECPrivateKey()); return new EcdsaPrivateKey(Identifier, algorithm, keySource, KeyLifespanDuration); @@ -63,10 +69,13 @@ protected sealed override EcdsaPrivateKey InitializePrivateKey(DigitalSignatureA /// /// The asymmetric-key algorithm for which the key pair is used. /// + /// + /// A value indicating whether or not the key pair is constructed from serialized memory bits. + /// /// /// The algorithm provider that facilitates cryptographic operations for the key pair. /// - protected sealed override ECDsa InitializeProvider(DigitalSignatureAlgorithmSpecification algorithm) => ECDsa.Create(algorithm.ToCurve()); + protected sealed override ECDsa InitializeProvider(DigitalSignatureAlgorithmSpecification algorithm, Boolean isReconstituted) => ECDsa.Create(algorithm.ToCurve()); /// /// Initializes the private key. @@ -77,10 +86,13 @@ protected sealed override EcdsaPrivateKey InitializePrivateKey(DigitalSignatureA /// /// The algorithm provider that facilitates cryptographic operations for the key pair. /// + /// + /// A value indicating whether or not the key pair is constructed from serialized memory bits. + /// /// /// The private key. /// - protected sealed override EcdsaPublicKey InitializePublicKey(DigitalSignatureAlgorithmSpecification algorithm, ECDsa provider) + protected sealed override EcdsaPublicKey InitializePublicKey(DigitalSignatureAlgorithmSpecification algorithm, ECDsa provider, Boolean isReconstituted) { var keyMemory = provider.ExportSubjectPublicKeyInfo(); return new EcdsaPublicKey(Identifier, algorithm, keyMemory, KeyLifespanDuration); @@ -90,6 +102,6 @@ protected sealed override EcdsaPublicKey InitializePublicKey(DigitalSignatureAlg /// Represents the default length of time for which paired keys are valid. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - internal static readonly TimeSpan DefaultKeyLifespanDuration = TimeSpan.FromDays(366); + internal static readonly TimeSpan DefaultKeyLifespanDuration = TimeSpan.FromDays(372); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/Ecdsa/EcdsaPrivateKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/Ecdsa/EcdsaPrivateKey.cs index 044f4061..da28b553 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/Ecdsa/EcdsaPrivateKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/Ecdsa/EcdsaPrivateKey.cs @@ -43,6 +43,36 @@ internal EcdsaPrivateKey(Guid keyPairIdentifier, DigitalSignatureAlgorithmSpecif return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The globally unique identifier for the key pair to which the key belongs. + /// + /// + /// The asymmetric-key algorithm for which the key is used. + /// + /// + /// A bit field that is used to derive key bits. + /// + /// + /// The date and time when the key expires and is no longer valid for use. + /// + /// + /// is . + /// + /// + /// is equal to -or- is equal + /// to -or- is + /// equal to . + /// + [DebuggerHidden] + internal EcdsaPrivateKey(Guid keyPairIdentifier, DigitalSignatureAlgorithmSpecification algorithm, PinnedMemory keySource, DateTime expirationTimeStamp) + : base(keyPairIdentifier, algorithm, KeyDerivationMode, keySource, expirationTimeStamp) + { + return; + } + /// /// Releases all resources consumed by the current . /// diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/Ecdsa/EcdsaPublicKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/Ecdsa/EcdsaPublicKey.cs index 28641e15..73f19520 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/Ecdsa/EcdsaPublicKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/Ecdsa/EcdsaPublicKey.cs @@ -49,6 +49,42 @@ internal EcdsaPublicKey(Guid keyPairIdentifier, DigitalSignatureAlgorithmSpecifi return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The globally unique identifier for the key pair to which the key belongs. + /// + /// + /// The asymmetric-key algorithm for which the key is used. + /// + /// + /// The plaintext key bits for the public key. + /// + /// + /// The date and time when the key expires and is no longer valid for use. + /// + /// + /// is empty. + /// + /// + /// The length of is invalid for . + /// + /// + /// is . + /// + /// + /// is equal to -or- is equal + /// to -or- is + /// equal to . + /// + [DebuggerHidden] + internal EcdsaPublicKey(Guid keyPairIdentifier, DigitalSignatureAlgorithmSpecification algorithm, Byte[] keyMemory, DateTime expirationTimeStamp) + : base(keyPairIdentifier, algorithm, keyMemory, expirationTimeStamp) + { + return; + } + /// /// Releases all resources consumed by the current . /// diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/IDigitalSignatureProcessor.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/IDigitalSignatureProcessor.cs index 35a1e526..c36273fb 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/IDigitalSignatureProcessor.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/DigitalSignature/IDigitalSignatureProcessor.cs @@ -9,7 +9,7 @@ namespace RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature /// /// Provides facilities for digitally signing byte arrays. /// - public interface IDigitalSignatureProcessor : IDigitalSignatureProcessor + public interface IDigitalSignatureProcessor : IAsymmetricProcessor, IDigitalSignatureProcessor { } @@ -19,7 +19,7 @@ public interface IDigitalSignatureProcessor : IDigitalSignatureProcessor /// /// The type of the object that can be digitally signed. /// - public interface IDigitalSignatureProcessor : IAsymmetricProcessor + public interface IDigitalSignatureProcessor : IAsymmetricProcessor where T : class { } diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricKeyPair.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricKeyPair.cs index b4326ae8..6a7864ca 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricKeyPair.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricKeyPair.cs @@ -74,5 +74,13 @@ public Guid Identifier { get; } + + /// + /// Gets a value that specifies what the key pair is used for. + /// + public AsymmetricKeyPurpose Purpose + { + get; + } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricProcessor.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricProcessor.cs index d9acca4a..6d83716a 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricProcessor.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/IAsymmetricProcessor.cs @@ -2,12 +2,25 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= +using System; + namespace RapidField.SolidInstruments.Cryptography.Asymmetric { /// - /// Provides facilities for performing asymmetric key operations. + /// Provides facilities for performing asymmetric key operations upon byte arrays. + /// + public interface IAsymmetricProcessor : IAsymmetricProcessor + { + } + + /// + /// Provides facilities for performing asymmetric key operations upon typed objects. /// - public interface IAsymmetricProcessor + /// + /// The type of the object upon which operations are performed. + /// + public interface IAsymmetricProcessor : ICryptographicProcessor + where T : class { } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhKeyPair.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhKeyPair.cs index 47e31e03..c2289225 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhKeyPair.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhKeyPair.cs @@ -21,12 +21,15 @@ public sealed class EcdhKeyPair : KeyExchangeKeyPair /// The asymmetric-key algorithm for which the key is used. /// + /// + /// A value indicating whether or not the key pair is constructed from serialized memory bits. + /// /// /// is equal to . /// [DebuggerHidden] - internal EcdhKeyPair(KeyExchangeAlgorithmSpecification algorithm) - : base(Guid.NewGuid(), algorithm, DefaultKeyLifespanDuration) + internal EcdhKeyPair(KeyExchangeAlgorithmSpecification algorithm, Boolean isReconstituted) + : base(Guid.NewGuid(), algorithm, DefaultKeyLifespanDuration, isReconstituted) { return; } @@ -79,10 +82,13 @@ protected sealed override ISecureMemory DeriveSymmetricKeyMaterial(ECDiffieHellm /// /// The algorithm provider that facilitates cryptographic operations for the key pair. /// + /// + /// A value indicating whether or not the key pair is constructed from serialized memory bits. + /// /// /// The private key. /// - protected sealed override EcdhPrivateKey InitializePrivateKey(KeyExchangeAlgorithmSpecification algorithm, ECDiffieHellman provider) + protected sealed override EcdhPrivateKey InitializePrivateKey(KeyExchangeAlgorithmSpecification algorithm, ECDiffieHellman provider, Boolean isReconstituted) { using var keySource = new PinnedMemory(provider.ExportECPrivateKey()); return new EcdhPrivateKey(Identifier, algorithm, keySource, KeyLifespanDuration); @@ -94,10 +100,13 @@ protected sealed override EcdhPrivateKey InitializePrivateKey(KeyExchangeAlgorit /// /// The asymmetric-key algorithm for which the key pair is used. /// + /// + /// A value indicating whether or not the key pair is constructed from serialized memory bits. + /// /// /// The algorithm provider that facilitates cryptographic operations for the key pair. /// - protected sealed override ECDiffieHellman InitializeProvider(KeyExchangeAlgorithmSpecification algorithm) => ECDiffieHellman.Create(algorithm.ToCurve()); + protected sealed override ECDiffieHellman InitializeProvider(KeyExchangeAlgorithmSpecification algorithm, Boolean isReconstituted) => ECDiffieHellman.Create(algorithm.ToCurve()); /// /// Initializes the private key. @@ -108,10 +117,13 @@ protected sealed override EcdhPrivateKey InitializePrivateKey(KeyExchangeAlgorit /// /// The algorithm provider that facilitates cryptographic operations for the key pair. /// + /// + /// A value indicating whether or not the key pair is constructed from serialized memory bits. + /// /// /// The private key. /// - protected sealed override EcdhPublicKey InitializePublicKey(KeyExchangeAlgorithmSpecification algorithm, ECDiffieHellman provider) + protected sealed override EcdhPublicKey InitializePublicKey(KeyExchangeAlgorithmSpecification algorithm, ECDiffieHellman provider, Boolean isReconstituted) { var keyMemory = provider.ExportSubjectPublicKeyInfo(); return new EcdhPublicKey(Identifier, algorithm, keyMemory, KeyLifespanDuration); @@ -121,7 +133,7 @@ protected sealed override EcdhPublicKey InitializePublicKey(KeyExchangeAlgorithm /// Represents the default length of time for which paired keys are valid. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - internal static readonly TimeSpan DefaultKeyLifespanDuration = TimeSpan.FromHours(8); + internal static readonly TimeSpan DefaultKeyLifespanDuration = TimeSpan.FromHours(36); /// /// Represents an implementation of which supports reconstitution of a second party diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhPrivateKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhPrivateKey.cs index 7e51d5da..6a6d1640 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhPrivateKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhPrivateKey.cs @@ -44,6 +44,36 @@ internal EcdhPrivateKey(Guid keyPairIdentifier, KeyExchangeAlgorithmSpecificatio return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The globally unique identifier for the key pair to which the key belongs. + /// + /// + /// The asymmetric-key algorithm for which the key is used. + /// + /// + /// A bit field that is used to derive key bits. + /// + /// + /// The date and time when the key expires and is no longer valid for use. + /// + /// + /// is . + /// + /// + /// is equal to -or- is equal + /// to -or- is equal to + /// . + /// + [DebuggerHidden] + internal EcdhPrivateKey(Guid keyPairIdentifier, KeyExchangeAlgorithmSpecification algorithm, PinnedMemory keySource, DateTime expirationTimeStamp) + : base(keyPairIdentifier, algorithm, KeyDerivationMode, keySource, expirationTimeStamp) + { + return; + } + /// /// Releases all resources consumed by the current . /// diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhPublicKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhPublicKey.cs index 730e8b8f..4cbf00db 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhPublicKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/Ecdh/EcdhPublicKey.cs @@ -50,6 +50,42 @@ internal EcdhPublicKey(Guid keyPairIdentifier, KeyExchangeAlgorithmSpecification return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The globally unique identifier for the key pair to which the key belongs. + /// + /// + /// The asymmetric-key algorithm for which the key is used. + /// + /// + /// The plaintext key bits for the public key. + /// + /// + /// The date and time when the key expires and is no longer valid for use. + /// + /// + /// is empty. + /// + /// + /// The length of is invalid for . + /// + /// + /// is . + /// + /// + /// is equal to -or- is equal + /// to -or- is equal to + /// . + /// + [DebuggerHidden] + internal EcdhPublicKey(Guid keyPairIdentifier, KeyExchangeAlgorithmSpecification algorithm, Byte[] keyMemory, DateTime expirationTimeStamp) + : base(keyPairIdentifier, algorithm, keyMemory, expirationTimeStamp) + { + return; + } + /// /// Releases all resources consumed by the current . /// diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangeKeyPair.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangeKeyPair.cs index 11d7567e..bc4bc482 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangeKeyPair.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangeKeyPair.cs @@ -41,13 +41,16 @@ public abstract class KeyExchangeKeyPair : A /// /// The length of time for which the paired keys are valid. /// + /// + /// A value indicating whether or not the key pair is constructed from serialized memory bits. + /// /// /// is equal to -or- is equal to /// -or- is less than /// eight seconds. /// - protected KeyExchangeKeyPair(Guid identifier, KeyExchangeAlgorithmSpecification algorithm, TimeSpan keyLifespanDuration) - : base(identifier, algorithm, keyLifespanDuration) + protected KeyExchangeKeyPair(Guid identifier, KeyExchangeAlgorithmSpecification algorithm, TimeSpan keyLifespanDuration, Boolean isReconstituted) + : base(identifier, algorithm, keyLifespanDuration, isReconstituted) { return; } @@ -128,5 +131,10 @@ public ISecureMemory DeriveSymmetricKeyMaterial(IAsymmetricPublicKeyModel second /// A value indicating whether or not managed resources should be released. /// protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Gets a value that specifies what the key pair is used for. + /// + public override sealed AsymmetricKeyPurpose Purpose => AsymmetricKeyPurpose.KeyExchange; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangePrivateKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangePrivateKey.cs index e8081488..0f83c952 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangePrivateKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangePrivateKey.cs @@ -82,10 +82,45 @@ protected KeyExchangePrivateKey(Guid keyPairIdentifier, KeyExchangeAlgorithmSpec /// eight seconds. /// protected KeyExchangePrivateKey(Guid keyPairIdentifier, KeyExchangeAlgorithmSpecification algorithm, CryptographicKeyDerivationMode derivationMode, PinnedMemory keySource, TimeSpan lifespanDuration) + : this(keyPairIdentifier, algorithm, derivationMode, keySource, TimeStamp.Current.Add(lifespanDuration.RejectIf().IsLessThan(MinimumLifespanDuration, nameof(lifespanDuration)))) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The globally unique identifier for the key pair to which the key belongs. + /// + /// + /// The asymmetric-key algorithm for which the key is used. + /// + /// + /// The mode used to derive the output key. + /// + /// + /// A bit field that is used to derive key bits. + /// + /// + /// The date and time when the key expires and is no longer valid for use. + /// + /// + /// contains too many elements. + /// + /// + /// is . + /// + /// + /// is equal to -or- is equal + /// to -or- is equal to + /// . + /// + protected KeyExchangePrivateKey(Guid keyPairIdentifier, KeyExchangeAlgorithmSpecification algorithm, CryptographicKeyDerivationMode derivationMode, PinnedMemory keySource, DateTime expirationTimeStamp) : base(algorithm, derivationMode, keySource, algorithm.ToPrivateKeyBitLength()) { Curve = algorithm.ToCurve(); - ExpirationTimeStamp = TimeStamp.Current.Add(lifespanDuration.RejectIf().IsLessThan(MinimumLifespanDuration, nameof(lifespanDuration))); + ExpirationTimeStamp = expirationTimeStamp.RejectIf().IsEqualToValue(DateTime.MaxValue, nameof(expirationTimeStamp)); KeyPairIdentifier = keyPairIdentifier.RejectIf().IsEqualToValue(Guid.Empty, nameof(keyPairIdentifier)); Purpose = AsymmetricKeyPurpose.KeyExchange; } @@ -135,6 +170,11 @@ public AsymmetricKeyPurpose Purpose get; } + /// + /// Gets a value specifying the valid purposes and uses of the current . + /// + public override sealed CryptographicComponentUsage Usage => CryptographicComponentUsage.KeyExchange; + /// /// Represents an elliptic curve matching . /// diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangeProcessor.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangeProcessor.cs index 6ea229e1..80bb6c3f 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangeProcessor.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangeProcessor.cs @@ -16,7 +16,7 @@ namespace RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange public sealed class KeyExchangeProcessor : AsymmetricProcessor, IKeyExchangeProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// A random number generator that is used to generate initialization vectors. @@ -30,6 +30,11 @@ public KeyExchangeProcessor(RandomNumberGenerator randomnessProvider) return; } + /// + /// Gets a value specifying the valid purposes and uses of the current . + /// + public override sealed CryptographicComponentUsage Usage => CryptographicComponentUsage.KeyExchange; + /// /// Represents a singleton instance of the class. /// diff --git a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangePublicKey.cs b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangePublicKey.cs index 47e1de3b..27af095d 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangePublicKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Asymmetric/KeyExchange/KeyExchangePublicKey.cs @@ -82,10 +82,45 @@ protected KeyExchangePublicKey(Guid keyPairIdentifier, KeyExchangeAlgorithmSpeci /// eight seconds. /// protected KeyExchangePublicKey(Guid keyPairIdentifier, KeyExchangeAlgorithmSpecification algorithm, Byte[] keyMemory, TimeSpan lifespanDuration) + : this(keyPairIdentifier, algorithm, keyMemory, TimeStamp.Current.Add(lifespanDuration.RejectIf().IsLessThan(MinimumLifespanDuration, nameof(lifespanDuration)))) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The globally unique identifier for the key pair to which the key belongs. + /// + /// + /// The asymmetric-key algorithm for which the key is used. + /// + /// + /// The plaintext key bits for the public key. + /// + /// + /// The date and time when the key expires and is no longer valid for use. + /// + /// + /// is empty. + /// + /// + /// The length of is invalid for . + /// + /// + /// is . + /// + /// + /// is equal to -or- is equal + /// to -or- is equal to + /// . + /// + protected KeyExchangePublicKey(Guid keyPairIdentifier, KeyExchangeAlgorithmSpecification algorithm, Byte[] keyMemory, DateTime expirationTimeStamp) : base() { Algorithm = algorithm.RejectIf().IsEqualToValue(KeyExchangeAlgorithmSpecification.Unspecified, nameof(algorithm)); - ExpirationTimeStamp = TimeStamp.Current.Add(lifespanDuration.RejectIf().IsLessThan(MinimumLifespanDuration, nameof(lifespanDuration))); + ExpirationTimeStamp = expirationTimeStamp.RejectIf().IsEqualToValue(DateTime.MaxValue, nameof(expirationTimeStamp)); KeyMemory = new PinnedMemory(keyMemory.RejectIf().IsNullOrEmpty(nameof(keyMemory)).OrIf(argument => argument.Length != (algorithm.ToPublicKeyBitLength() / 8), nameof(keyMemory), $"The length of the specified key is invalid for the specified algorithm, \"{algorithm}\"."), true); KeyPairIdentifier = keyPairIdentifier.RejectIf().IsEqualToValue(Guid.Empty, nameof(keyPairIdentifier)); Purpose = AsymmetricKeyPurpose.KeyExchange; @@ -291,6 +326,11 @@ public AsymmetricKeyPurpose Purpose get; } + /// + /// Gets a value specifying the valid purposes and uses of the current . + /// + public override sealed CryptographicComponentUsage Usage => CryptographicComponentUsage.KeyExchange; + /// /// Represents the plaintext key bits for the current . /// diff --git a/src/RapidField.SolidInstruments.Cryptography/CryptographicComponentUsage.cs b/src/RapidField.SolidInstruments.Cryptography/CryptographicComponentUsage.cs new file mode 100644 index 00000000..4e080e41 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/CryptographicComponentUsage.cs @@ -0,0 +1,40 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.Cryptography +{ + /// + /// Defines the valid purposes and uses of a cryptographic key or instrument. + /// + [Flags] + public enum CryptographicComponentUsage : Byte + { + /// + /// No valid uses are defined for the component. + /// + None = 0x00, + + /// + /// The component can be used to produce hash values for plaintext information. + /// + Hashing = 0x01, + + /// + /// The component can be used to encrypt or decrypt information using symmetric key cryptography. + /// + SymmetricKeyEncryption = 0x02, + + /// + /// The component can be used to digitally sign information using asymmetric key cryptography. + /// + DigitalSignature = 0x04, + + /// + /// The component can be used to securely exchange symmetric keys with remote parties. + /// + KeyExchange = 0x08 + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/CryptographicKey.cs b/src/RapidField.SolidInstruments.Cryptography/CryptographicKey.cs index 2269900c..806e74de 100644 --- a/src/RapidField.SolidInstruments.Cryptography/CryptographicKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/CryptographicKey.cs @@ -644,6 +644,32 @@ internal static PinnedMemory DeriveKeySourceBytesFromPassword(IPassword password /// protected abstract ISecureMemory ToSecureMemory(IConcurrencyControlToken controlToken); + /// + /// Gets a value indicating whether or not the current can be used to digitally sign + /// information using asymmetric key cryptography. + /// + public Boolean SupportsDigitalSignature => Usage.HasFlag(CryptographicComponentUsage.DigitalSignature); + + /// + /// Gets a value indicating whether or not the current can be used to securely exchange + /// symmetric keys with remote parties. + /// + public Boolean SupportsKeyExchange => Usage.HasFlag(CryptographicComponentUsage.KeyExchange); + + /// + /// Gets a value indicating whether or not the current can be used to encrypt or decrypt + /// information using symmetric key cryptography. + /// + public Boolean SupportsSymmetricKeyEncryption => Usage.HasFlag(CryptographicComponentUsage.SymmetricKeyEncryption); + + /// + /// Gets a value specifying the valid purposes and uses of the current . + /// + public abstract CryptographicComponentUsage Usage + { + get; + } + /// /// Represents the minimum allowable length, in characters, of a password. /// diff --git a/src/RapidField.SolidInstruments.Cryptography/CryptographicProcessor.cs b/src/RapidField.SolidInstruments.Cryptography/CryptographicProcessor.cs new file mode 100644 index 00000000..5c246f94 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/CryptographicProcessor.cs @@ -0,0 +1,135 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Serialization; +using System; +using System.Diagnostics; +using System.Security.Cryptography; + +namespace RapidField.SolidInstruments.Cryptography +{ + /// + /// Provides facilities for performing cryptographic operations upon typed objects. + /// + /// + /// is the default implementation of . + /// + /// + /// The type of the object upon which cryptographic operations are performed. + /// + public abstract class CryptographicProcessor : CryptographicProcessor, ICryptographicProcessor + where T : class + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A random number generator that is used to generate initialization vectors. + /// + /// + /// is . + /// + protected CryptographicProcessor(RandomNumberGenerator randomnessProvider) + : this(randomnessProvider, DefaultSerializer) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A random number generator that is used to generate initialization vectors. + /// + /// + /// A serializer that is used to transform plaintext. + /// + /// + /// is -or- is + /// . + /// + protected CryptographicProcessor(RandomNumberGenerator randomnessProvider, ISerializer serializer) + : base(randomnessProvider) + { + Serializer = serializer.RejectIf().IsNull(nameof(serializer)).TargetArgument; + } + + /// + /// Gets the type of the object upon which cryptographic operations are performed. + /// + public Type TargetObjectType => typeof(T); + + /// + /// Represents the default serializer that is used to transform plaintext. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal static readonly ISerializer DefaultSerializer = new CompressedJsonSerializer(); + + /// + /// Represents a serializer that is used to transform plaintext. + /// + protected readonly ISerializer Serializer; + } + + /// + /// Provides facilities for performing cryptographic operations upon typed objects. + /// + /// + /// is the default implementation of . + /// + public abstract class CryptographicProcessor : ICryptographicProcessor + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A random number generator that is used to generate initialization vectors. + /// + /// + /// is . + /// + protected CryptographicProcessor(RandomNumberGenerator randomnessProvider) + { + RandomnessProvider = randomnessProvider.RejectIf().IsNull(nameof(randomnessProvider)); + } + + /// + /// Gets a value indicating whether or not the current can be used to digitally sign + /// information using asymmetric key cryptography. + /// + public Boolean SupportsDigitalSignature => Usage.HasFlag(CryptographicComponentUsage.DigitalSignature); + + /// + /// Gets a value indicating whether or not the current can be used to produce hash + /// values for plaintext information. + /// + public Boolean SupportsHashing => Usage.HasFlag(CryptographicComponentUsage.Hashing); + + /// + /// Gets a value indicating whether or not the current can be used to securely + /// exchange symmetric keys with remote parties. + /// + public Boolean SupportsKeyExchange => Usage.HasFlag(CryptographicComponentUsage.KeyExchange); + + /// + /// Gets a value indicating whether or not the current can be used to encrypt or + /// decrypt information using symmetric key cryptography. + /// + public Boolean SupportsSymmetricKeyEncryption => Usage.HasFlag(CryptographicComponentUsage.SymmetricKeyEncryption); + + /// + /// Gets a value specifying the valid purposes and uses of the current . + /// + public abstract CryptographicComponentUsage Usage + { + get; + } + + /// + /// Represents a random number generator that is used to generate initialization vectors. + /// + protected readonly RandomNumberGenerator RandomnessProvider; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Extensions/HashingAlgorithmSpecificationExtensions.cs b/src/RapidField.SolidInstruments.Cryptography/Extensions/HashingAlgorithmSpecificationExtensions.cs index 30e122ca..77f7e2c4 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Extensions/HashingAlgorithmSpecificationExtensions.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Extensions/HashingAlgorithmSpecificationExtensions.cs @@ -4,6 +4,7 @@ using RapidField.SolidInstruments.Core; using RapidField.SolidInstruments.Cryptography.Hashing; +using RapidField.SolidInstruments.Cryptography.Hashing.Argon2; using RapidField.SolidInstruments.Cryptography.Hashing.Pbkdf2; using System; using System.Diagnostics; @@ -29,8 +30,12 @@ internal static class HashingAlgorithmSpecificationExtensions internal static Int32 ToDigestBitLength(this HashingAlgorithmSpecification target) => target switch { HashingAlgorithmSpecification.Unspecified => default, + HashingAlgorithmSpecification.Argon2idBalanced => HashAlgorithmBase.DigestLengthInBitsForArgon2, + HashingAlgorithmSpecification.Argon2idIterativelyExpensive => HashAlgorithmBase.DigestLengthInBitsForArgon2, + HashingAlgorithmSpecification.Argon2idMemoryExpensive => HashAlgorithmBase.DigestLengthInBitsForArgon2, + HashingAlgorithmSpecification.Argon2idThreadExpensive => HashAlgorithmBase.DigestLengthInBitsForArgon2, HashingAlgorithmSpecification.Md5 => 128, - HashingAlgorithmSpecification.Pbkdf2 => Pbkdf2HashAlgorithm.DigestLengthInBits, + HashingAlgorithmSpecification.Pbkdf2 => HashAlgorithmBase.DigestLengthInBitsForPbkdf2, HashingAlgorithmSpecification.ShaTwo256 => 256, HashingAlgorithmSpecification.ShaTwo384 => 384, HashingAlgorithmSpecification.ShaTwo512 => 512, @@ -50,6 +55,10 @@ internal static class HashingAlgorithmSpecificationExtensions internal static HashAlgorithm ToHashAlgorithm(this HashingAlgorithmSpecification target) => target switch { HashingAlgorithmSpecification.Unspecified => null, + HashingAlgorithmSpecification.Argon2idBalanced => Argon2HashAlgorithm.WithBalancedConfiguration(), + HashingAlgorithmSpecification.Argon2idIterativelyExpensive => Argon2HashAlgorithm.WithIterativelyExpensiveConfiguration(), + HashingAlgorithmSpecification.Argon2idMemoryExpensive => Argon2HashAlgorithm.WithMemoryExpensiveConfiguration(), + HashingAlgorithmSpecification.Argon2idThreadExpensive => Argon2HashAlgorithm.WithThreadExpensiveConfiguration(), HashingAlgorithmSpecification.Md5 => MD5.Create(), HashingAlgorithmSpecification.Pbkdf2 => Pbkdf2HashAlgorithm.Create(), HashingAlgorithmSpecification.ShaTwo256 => SHA256.Create(), diff --git a/src/RapidField.SolidInstruments.Cryptography/Extensions/ISymmetricEncryptorDecryptorExtensions.cs b/src/RapidField.SolidInstruments.Cryptography/Extensions/ISymmetricEncryptorDecryptorExtensions.cs index 76ff4025..95046e8e 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Extensions/ISymmetricEncryptorDecryptorExtensions.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Extensions/ISymmetricEncryptorDecryptorExtensions.cs @@ -37,7 +37,8 @@ public static class ISymmetricProcessorExtensions /// /// An exception was raised during decryption or deserialization. /// - public static T DecryptFromBase64String(this ISymmetricProcessor target, String ciphertext, SymmetricKey key) => target.Decrypt(Convert.FromBase64String(ciphertext), key); + public static T DecryptFromBase64String(this ISymmetricProcessor target, String ciphertext, SymmetricKey key) + where T : class => target.Decrypt(Convert.FromBase64String(ciphertext), key); /// /// Decrypts the specified Base64 string ciphertext. @@ -63,7 +64,8 @@ public static class ISymmetricProcessorExtensions /// /// An exception was raised during decryption or deserialization. /// - public static T DecryptFromBase64String(this ISymmetricProcessor target, String ciphertext, CascadingSymmetricKey key) => target.Decrypt(Convert.FromBase64String(ciphertext), key); + public static T DecryptFromBase64String(this ISymmetricProcessor target, String ciphertext, CascadingSymmetricKey key) + where T : class => target.Decrypt(Convert.FromBase64String(ciphertext), key); /// /// Decrypts the specified Base64 string ciphertext. @@ -92,7 +94,8 @@ public static class ISymmetricProcessorExtensions /// /// An exception was raised during decryption or deserialization. /// - public static T DecryptFromBase64String(this ISymmetricProcessor target, String ciphertext, ISecureMemory key, SymmetricAlgorithmSpecification algorithm) => target.Decrypt(Convert.FromBase64String(ciphertext), key, algorithm); + public static T DecryptFromBase64String(this ISymmetricProcessor target, String ciphertext, ISecureMemory key, SymmetricAlgorithmSpecification algorithm) + where T : class => target.Decrypt(Convert.FromBase64String(ciphertext), key, algorithm); /// /// Encrypts the specified plaintext object to a Base64 string. @@ -115,7 +118,8 @@ public static class ISymmetricProcessorExtensions /// /// An exception was raised during encryption or serialization. /// - public static String EncryptToBase64String(this ISymmetricProcessor target, T plaintextObject, SymmetricKey key) => Convert.ToBase64String(target.Encrypt(plaintextObject, key)); + public static String EncryptToBase64String(this ISymmetricProcessor target, T plaintextObject, SymmetricKey key) + where T : class => Convert.ToBase64String(target.Encrypt(plaintextObject, key)); /// /// Encrypts the specified plaintext object to a Base64 string. @@ -138,7 +142,8 @@ public static class ISymmetricProcessorExtensions /// /// An exception was raised during encryption or serialization. /// - public static String EncryptToBase64String(this ISymmetricProcessor target, T plaintextObject, CascadingSymmetricKey key) => Convert.ToBase64String(target.Encrypt(plaintextObject, key)); + public static String EncryptToBase64String(this ISymmetricProcessor target, T plaintextObject, CascadingSymmetricKey key) + where T : class => Convert.ToBase64String(target.Encrypt(plaintextObject, key)); /// /// Encrypts the specified plaintext object to a Base64 string. @@ -164,6 +169,7 @@ public static class ISymmetricProcessorExtensions /// /// An exception was raised during encryption or serialization. /// - public static String EncryptToBase64String(this ISymmetricProcessor target, T plaintextObject, ISecureMemory key, SymmetricAlgorithmSpecification algorithm) => Convert.ToBase64String(target.Encrypt(plaintextObject, key, algorithm)); + public static String EncryptToBase64String(this ISymmetricProcessor target, T plaintextObject, ISecureMemory key, SymmetricAlgorithmSpecification algorithm) + where T : class => Convert.ToBase64String(target.Encrypt(plaintextObject, key, algorithm)); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Hashing/Argon2/Argon2HashAlgorithm.cs b/src/RapidField.SolidInstruments.Cryptography/Hashing/Argon2/Argon2HashAlgorithm.cs new file mode 100644 index 00000000..6cb98ff0 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Hashing/Argon2/Argon2HashAlgorithm.cs @@ -0,0 +1,209 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Konscious.Security.Cryptography; +using RapidField.SolidInstruments.Collections; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using System; +using System.Diagnostics; +using System.Security.Cryptography; + +namespace RapidField.SolidInstruments.Cryptography.Hashing.Argon2 +{ + /// + /// Represents a implementation of the PBKDF2 key derivation function. + /// + internal sealed class Argon2HashAlgorithm : HashAlgorithmBase + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The number of threads to use concurrently while processing the hash value. + /// + /// + /// The number of Argon2 iterations to perform. + /// + /// + /// The number of 1KB memory blocks to use while processing the hash value. + /// + /// + /// is less than one -or- is less than one -or- + /// is less than one. + /// + [DebuggerHidden] + private Argon2HashAlgorithm(Int32 degreeOfParallelism, Int32 iterationCount, Int32 memorySizeInKilobytes) + : base(DigestLengthInBitsForArgon2) + { + DegreeOfParallelism = degreeOfParallelism.RejectIf().IsLessThan(1, nameof(degreeOfParallelism)); + IterationCount = iterationCount.RejectIf().IsLessThan(1, nameof(iterationCount)); + MemorySizeInKilobytes = memorySizeInKilobytes.RejectIf().IsLessThan(1, nameof(memorySizeInKilobytes)); + } + + /// + /// Creates a new instance of the class that performs eight (8) iterations using 8MB of + /// memory and eight (8) threads. + /// + /// + /// A new instance of the class. + /// + [DebuggerHidden] + internal static new HashAlgorithm Create() => WithBalancedConfiguration(); + + /// + /// Creates a new instance of the class that performs eight (8) iterations using 8MB of + /// memory and eight (8) threads. + /// + /// + /// A new instance of the class. + /// + [DebuggerHidden] + internal static HashAlgorithm WithBalancedConfiguration() => new Argon2HashAlgorithm(IntermediateDegreeOfParallelism, IntermediateIterationCount, IntermediateMemorySizeInKilobytes); + + /// + /// Creates a new instance of the class that performs twenty-four (24) iterations using + /// 4MB of memory and eight (8) threads. + /// + /// + /// A new instance of the class. + /// + [DebuggerHidden] + internal static HashAlgorithm WithIterativelyExpensiveConfiguration() => new Argon2HashAlgorithm(IntermediateDegreeOfParallelism, HighIterationCount, LowMemorySizeInKilobytes); + + /// + /// Creates a new instance of the class that performs eight (8) iterations using 16MB of + /// memory and four (4) threads. + /// + /// + /// A new instance of the class. + /// + [DebuggerHidden] + internal static HashAlgorithm WithMemoryExpensiveConfiguration() => new Argon2HashAlgorithm(LowDegreeOfParallelism, IntermediateIterationCount, HighMemorySizeInKilobytes); + + /// + /// Creates a new instance of the class that performs four (4) iterations using 8MB of + /// memory and sixteen (16) threads. + /// + /// + /// A new instance of the class. + /// + [DebuggerHidden] + internal static HashAlgorithm WithThreadExpensiveConfiguration() => new Argon2HashAlgorithm(HighDegreeOfParallelism, LowIterationCount, IntermediateMemorySizeInKilobytes); + + /// + /// Produces and returns a digest for the specified plaintext. + /// + /// + /// The plaintext from which to produce a digest. + /// + /// + /// The deterministic salt bytes for . + /// + /// + /// The length, in bytes, of digests produced by the algorithm. + /// + /// + /// The resulting digest bytes. + /// + protected override sealed Byte[] ComputeHash(PinnedMemory plaintext, PinnedMemory salt, Int32 digestLengthInBytes) + { + var keyDerivationFunction = new Argon2id(plaintext) + { + AssociatedData = DefaultAssociatedData, + DegreeOfParallelism = DegreeOfParallelism, + Iterations = IterationCount, + MemorySize = MemorySizeInKilobytes, + Salt = salt + }; + return keyDerivationFunction.GetBytes(digestLengthInBytes); + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Represents the number of threads to use concurrently while processing the hash value. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal readonly Int32 DegreeOfParallelism; + + /// + /// Represents the number of Argon2 iterations to perform. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal readonly Int32 IterationCount; + + /// + /// Represents the number of 1KB memory blocks to use while processing the hash value. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal readonly Int32 MemorySizeInKilobytes; + + /// + /// Represents the high default number of threads to use concurrently while processing the hash value. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Int32 HighDegreeOfParallelism = 16; + + /// + /// Represents the high default number of Argon2 iterations to perform. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Int32 HighIterationCount = 24; + + /// + /// Represents the default number of 1KB memory blocks to use while processing the hash value. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Int32 HighMemorySizeInKilobytes = 16384; + + /// + /// Represents the intermediate default number of threads to use concurrently while processing the hash value. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Int32 IntermediateDegreeOfParallelism = 8; + + /// + /// Represents the intermediate default number of Argon2 iterations to perform. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Int32 IntermediateIterationCount = 8; + + /// + /// Represents the default number of 1KB memory blocks to use while processing the hash value. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Int32 IntermediateMemorySizeInKilobytes = 8192; + + /// + /// Represents the low default number of threads to use concurrently while processing the hash value. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Int32 LowDegreeOfParallelism = 4; + + /// + /// Represents the low default number of Argon2 iterations to perform. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Int32 LowIterationCount = 4; + + /// + /// Represents the default number of 1KB memory blocks to use while processing the hash value. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Int32 LowMemorySizeInKilobytes = 4096; + + /// + /// Represents an arbitrary byte array that is used for every instance to thwart + /// dictionary attacks. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static readonly Byte[] DefaultAssociatedData = new Byte[] { 0xcc, 0xf0, 0x3c, 0x03, 0xaf, 0xa5, 0x0f, 0xca, 0xac, 0xf0, 0x5a, 0xfa, 0x30, 0xc3, 0x0f, 0xcc }; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Hashing/HashAlgorithmBase.cs b/src/RapidField.SolidInstruments.Cryptography/Hashing/HashAlgorithmBase.cs new file mode 100644 index 00000000..693ffc7a --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/Hashing/HashAlgorithmBase.cs @@ -0,0 +1,187 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Collections; +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Core.Extensions; +using RapidField.SolidInstruments.Cryptography.Extensions; +using RapidField.SolidInstruments.Cryptography.Hashing.Argon2; +using RapidField.SolidInstruments.Cryptography.Hashing.Pbkdf2; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Security; +using System.Security.Cryptography; + +namespace RapidField.SolidInstruments.Cryptography.Hashing +{ + /// + /// Represents a implementation. + /// + internal abstract class HashAlgorithmBase : HashAlgorithm + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The length, in bits, of digests produced by the algorithm. + /// + /// + /// is less than or equal to zero. + /// + protected HashAlgorithmBase(Int32 digestLengthInBits) + : base() + { + DigestLengthInBits = digestLengthInBits.RejectIf().IsLessThanOrEqualTo(0, nameof(digestLengthInBits)); + } + + /// + /// Resets the hash algorithm to its initial state. + /// + public override void Initialize() => HashValue = Array.Empty(); + + /// + /// Produces and returns a digest for the specified plaintext. + /// + /// + /// The plaintext from which to produce a digest. + /// + /// + /// The deterministic salt bytes for . + /// + /// + /// The length, in bytes, of digests produced by the algorithm. + /// + /// + /// The resulting digest bytes. + /// + protected abstract Byte[] ComputeHash(PinnedMemory plaintext, PinnedMemory salt, Int32 digestLengthInBytes); + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Routes data written to the object into the hash algorithm. + /// + /// + /// The input to compute the hash code for. + /// + /// + /// The offset into the byte array from which to begin using data. + /// + /// + /// The number of bytes in the byte array to use as data. + /// + /// + /// An exception was raised while computing the hash value. + /// + protected override void HashCore(Byte[] array, Int32 ibStart, Int32 cbSize) + { + try + { + using var plaintext = new PinnedMemory(array.Skip(ibStart).Take(cbSize).ToArray(), true); + using var salt = DeriveDeterministicSaltValue(plaintext); + HashValue = ComputeHash(plaintext, salt, DigestLengthInBytes); + } + catch (Exception exception) + { + throw new SecurityException("An exception was raised while computing a hash value.", exception); + } + } + + /// + /// Finalizes the hash computation after the last data is processed by the cryptographic stream object. + /// + /// + /// The computed hash code. + /// + protected override Byte[] HashFinal() => HashValue; + + /// + /// Derives a deterministic 256-bit salt value from the specified plaintext bytes. + /// + /// + /// Salt bytes are derived deterministically because handles salt generation and + /// application. Therefore, contains both plaintext and salt bytes and the deterministic salt + /// input can safely be derived from them. + /// + /// + /// Plaintext bytes from which to derive a salt value. + /// + /// + /// The derived salt bytes. + /// + [DebuggerHidden] + private static PinnedMemory DeriveDeterministicSaltValue(IReadOnlyPinnedMemory plaintext) + { + var processedPlaintext = new List(plaintext); + + try + { + processedPlaintext.ReverseStaggerSort(); + using var saltDerivationAlgorithm = SaltDerivationHashAlgorithm.ToHashAlgorithm(); + var saltBytes = saltDerivationAlgorithm.ComputeHash(processedPlaintext.ToArray().PerformCircularBitShift(SaltDerivationBitShiftDirection, SaltDerivationBitShiftCount)); + return new PinnedMemory(saltBytes, true); + } + finally + { + processedPlaintext.Clear(); + } + } + + /// + /// Gets the length, in bits, of digests produced by instances. + /// + public override Int32 HashSize => DigestLengthInBits; + + /// + /// Gets the length, in bytes, of digests produced by instances. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private Int32 DigestLengthInBytes => DigestLengthInBits / 8; + + /// + /// Represents the length, in bits, of digests produced by instances. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal const Int32 DigestLengthInBitsForArgon2 = 256; + + /// + /// Represents the length, in bits, of digests produced by instances. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal const Int32 DigestLengthInBitsForPbkdf2 = 256; + + /// + /// Represents the bit shift count that is used to derive deterministic salt bytes from the plaintext. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Int32 SaltDerivationBitShiftCount = 5; + + /// + /// Represents the bit shift direction that is used to derive deterministic salt bytes from the plaintext. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const BitShiftDirection SaltDerivationBitShiftDirection = BitShiftDirection.Right; + + /// + /// Represents the hashing algorithm that is used to derive deterministic salt bytes from the plaintext. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const HashingAlgorithmSpecification SaltDerivationHashAlgorithm = HashingAlgorithmSpecification.ShaTwo256; + + /// + /// Represents the length, in bits, of digests produced by instances. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly Int32 DigestLengthInBits; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Hashing/HashingAlgorithmSpecification.cs b/src/RapidField.SolidInstruments.Cryptography/Hashing/HashingAlgorithmSpecification.cs index ab9f60dd..808d0754 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Hashing/HashingAlgorithmSpecification.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Hashing/HashingAlgorithmSpecification.cs @@ -17,29 +17,53 @@ public enum HashingAlgorithmSpecification : Byte Unspecified = 0x00, /// - /// Specifies the MD5 hashing algorithm. + /// Specifies an Argon2id algorithm that performs eight (8) iterations using 8MB of memory and eight (8) threads to produce + /// a 256-bit digest. /// - Md5 = 0x01, + Argon2idBalanced = 0x01, + + /// + /// Specifies an Argon2id algorithm that performs twenty-four (24) iterations using 4MB of memory and eight (8) threads to + /// produce a 256-bit digest. + /// + Argon2idIterativelyExpensive = 0x02, + + /// + /// Specifies an Argon2id algorithm that performs eight (8) iterations using 16MB of memory and four (4) threads to produce + /// a 256-bit digest. + /// + Argon2idMemoryExpensive = 0x03, + + /// + /// Specifies an Argon2id algorithm that performs four (4) iterations using 8MB of memory and sixteen (16) threads to + /// produce a 256-bit digest. + /// + Argon2idThreadExpensive = 0x04, + + /// + /// Specifies the MD5 hashing algorithm, which produces a 128-bit digest. + /// + Md5 = 0x05, /// /// Specifies the PBKDF2 key-derivation function (PRF: 512-bit SHA-2, 256-bit salt) using 17,711 iterations to produce a /// 256-bit digest. /// - Pbkdf2 = 0x02, + Pbkdf2 = 0x06, /// /// Specifies the SHA-2 hashing algorithm using a 256-bit digest. /// - ShaTwo256 = 0x03, + ShaTwo256 = 0x07, /// /// Specifies the SHA-2 hashing algorithm using a 384-bit digest. /// - ShaTwo384 = 0x04, + ShaTwo384 = 0x08, /// /// Specifies the SHA-2 hashing algorithm using a 512-bit digest. /// - ShaTwo512 = 0x05 + ShaTwo512 = 0x09 } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Hashing/HashingProcessor.cs b/src/RapidField.SolidInstruments.Cryptography/Hashing/HashingProcessor.cs index 85b30efc..39330999 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Hashing/HashingProcessor.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Hashing/HashingProcessor.cs @@ -64,7 +64,7 @@ public static IHashingProcessor ForType() /// /// The type of the object that can be hashed. /// - public class HashingProcessor : IHashingProcessor + public class HashingProcessor : CryptographicProcessor, IHashingProcessor where T : class { /// @@ -121,9 +121,8 @@ public HashingProcessor(RandomNumberGenerator randomnessProvider, ISerializer /// is less than one. /// public HashingProcessor(RandomNumberGenerator randomnessProvider, ISerializer serializer, Int32 saltLengthInBytes) + : base(randomnessProvider, serializer) { - Serializer = serializer.RejectIf().IsNull(nameof(serializer)).TargetArgument; - RandomnessProvider = randomnessProvider.RejectIf().IsNull(nameof(randomnessProvider)); SaltLengthInBytes = saltLengthInBytes.RejectIf().IsLessThan(1, nameof(saltLengthInBytes)); } @@ -420,27 +419,14 @@ public Int32 SaltLengthInBytes } /// - /// Represents the default salt length, in bytes, to use when calculating and evaluating hash values. + /// Gets a value specifying the valid purposes and uses of the current . /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const Int32 DefaultSaltLengthInBytes = 16; - - /// - /// Represents the default serializer that is used to transform plaintext. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private static readonly ISerializer DefaultSerializer = new CompressedJsonSerializer(); + public override sealed CryptographicComponentUsage Usage => CryptographicComponentUsage.Hashing; /// - /// Represents a random number generator that is used to generate salt values. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly RandomNumberGenerator RandomnessProvider; - - /// - /// Represents a serializer that is used to transform plaintext. + /// Represents the default salt length, in bytes, to use when calculating and evaluating hash values. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly ISerializer Serializer; + private const Int32 DefaultSaltLengthInBytes = 16; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Hashing/IHashingProcessor.cs b/src/RapidField.SolidInstruments.Cryptography/Hashing/IHashingProcessor.cs index fd174f40..befa9ff6 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Hashing/IHashingProcessor.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Hashing/IHashingProcessor.cs @@ -21,7 +21,8 @@ public interface IHashingProcessor : IHashingProcessor /// /// The type of the object that can be hashed. /// - public interface IHashingProcessor + public interface IHashingProcessor : ICryptographicProcessor + where T : class { /// /// Calculates a hash value for the specified plaintext byte array. diff --git a/src/RapidField.SolidInstruments.Cryptography/Hashing/Pbkdf2/Pbkdf2HashAlgorithm.cs b/src/RapidField.SolidInstruments.Cryptography/Hashing/Pbkdf2/Pbkdf2HashAlgorithm.cs index 0ebc3fe0..42f5a361 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Hashing/Pbkdf2/Pbkdf2HashAlgorithm.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Hashing/Pbkdf2/Pbkdf2HashAlgorithm.cs @@ -3,12 +3,9 @@ // ================================================================================================================================= using RapidField.SolidInstruments.Collections; -using RapidField.SolidInstruments.Core; -using RapidField.SolidInstruments.Core.Extensions; using RapidField.SolidInstruments.Cryptography.Extensions; using System; using System.Diagnostics; -using System.Linq; using System.Security.Cryptography; namespace RapidField.SolidInstruments.Cryptography.Hashing.Pbkdf2 @@ -16,23 +13,18 @@ namespace RapidField.SolidInstruments.Cryptography.Hashing.Pbkdf2 /// /// Represents a implementation of the PBKDF2 key derivation function. /// - internal sealed class Pbkdf2HashAlgorithm : HashAlgorithm + internal sealed class Pbkdf2HashAlgorithm : HashAlgorithmBase { /// /// Initializes a new instance of the class. /// [DebuggerHidden] private Pbkdf2HashAlgorithm() - : base() + : base(DigestLengthInBitsForPbkdf2) { return; } - /// - /// Resets the hash algorithm to its initial state. - /// - public override void Initialize() => HashValue = Array.Empty(); - /// /// Creates a new instance of the class. /// @@ -40,84 +32,36 @@ private Pbkdf2HashAlgorithm() /// A new instance of the class. /// [DebuggerHidden] - internal static new Pbkdf2HashAlgorithm Create() => new Pbkdf2HashAlgorithm(); + internal static new HashAlgorithm Create() => new Pbkdf2HashAlgorithm(); /// - /// Releases all resources consumed by the current . + /// Produces and returns a digest for the specified plaintext. /// - /// - /// A value indicating whether or not managed resources should be released. - /// - protected override void Dispose(Boolean disposing) => base.Dispose(disposing); - - /// - /// Routes data written to the object into the hash algorithm. - /// - /// - /// The input to compute the hash code for. - /// - /// - /// The offset into the byte array from which to begin using data. + /// + /// The plaintext from which to produce a digest. /// - /// - /// The number of bytes in the byte array to use as data. + /// + /// The deterministic salt bytes for . /// - protected override void HashCore(Byte[] array, Int32 ibStart, Int32 cbSize) - { - using var plaintext = new PinnedMemory(array.Skip(ibStart).Take(cbSize).ToArray(), true); - using var salt = DeriveSaltValue(plaintext); - using var keyDerivationFunction = new Rfc2898DeriveBytes(plaintext, salt, IterationCount, KeyDerivationHashAlgorithm.ToHashAlgorithmName()); - HashValue = keyDerivationFunction.GetBytes(DigestLengthInBytes); - } - - /// - /// Finalizes the hash computation after the last data is processed by the cryptographic stream object. - /// - /// - /// The computed hash code. - /// - protected override Byte[] HashFinal() => HashValue; - - /// - /// Derives a deterministic 256-bit salt value from the specified plaintext bytes. - /// - /// - /// Salt bytes are derived deterministically because handles salt generation and - /// application. Therefore, contains both plaintext and salt bytes and the PBKDF2 salt input - /// can safely be derived from them. - /// - /// - /// Plaintext bytes from which to derive a salt value. + /// + /// The length, in bytes, of digests produced by the algorithm. /// /// - /// The derived salt bytes. + /// The resulting digest bytes. /// - [DebuggerHidden] - private static PinnedMemory DeriveSaltValue(Byte[] plaintext) + protected override sealed Byte[] ComputeHash(PinnedMemory plaintext, PinnedMemory salt, Int32 digestLengthInBytes) { - using var saltDerivationAlgorithm = SaltDerivationHashAlgorithm.ToHashAlgorithm(); - var saltBytes = saltDerivationAlgorithm.ComputeHash(plaintext); - saltBytes.ReverseStaggerSort(); - saltBytes = saltBytes.PerformCircularBitShift(SaltDerivationBitShiftDirection, SaltDerivationBitShiftCount); - return new PinnedMemory(saltBytes, true); + using var keyDerivationFunction = new Rfc2898DeriveBytes(plaintext, salt, IterationCount, KeyDerivationHashAlgorithm.ToHashAlgorithmName()); + return keyDerivationFunction.GetBytes(digestLengthInBytes); } /// - /// Gets the length, in bits, of digests produced by instances. - /// - public override Int32 HashSize => DigestLengthInBits; - - /// - /// Represents the length, in bits, of digests produced by instances. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - internal const Int32 DigestLengthInBits = 256; - - /// - /// Represents the length, in bytes, of digests produced by instances. + /// Releases all resources consumed by the current . /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const Int32 DigestLengthInBytes = DigestLengthInBits / 8; + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); /// /// Represents the number of PBKDF2 iterations to perform. @@ -130,23 +74,5 @@ private static PinnedMemory DeriveSaltValue(Byte[] plaintext) /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] private const HashingAlgorithmSpecification KeyDerivationHashAlgorithm = HashingAlgorithmSpecification.ShaTwo512; - - /// - /// Represents the bit shift count that is used to derive PBKDF2 salt bytes from the plaintext. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const Int32 SaltDerivationBitShiftCount = 5; - - /// - /// Represents the bit shift direction that is used to derive PBKDF2 salt bytes from the plaintext. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const BitShiftDirection SaltDerivationBitShiftDirection = BitShiftDirection.Right; - - /// - /// Represents the hashing algorithm that is used to derive PBKDF2 salt bytes from the plaintext. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const HashingAlgorithmSpecification SaltDerivationHashAlgorithm = HashingAlgorithmSpecification.ShaTwo256; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/ICryptographicComponent.cs b/src/RapidField.SolidInstruments.Cryptography/ICryptographicComponent.cs new file mode 100644 index 00000000..b5bbb311 --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/ICryptographicComponent.cs @@ -0,0 +1,20 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +namespace RapidField.SolidInstruments.Cryptography +{ + /// + /// Represents a cryptographic key or instrument. + /// + public interface ICryptographicComponent + { + /// + /// Gets a value specifying the valid purposes and uses of the current . + /// + public CryptographicComponentUsage Usage + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/ICryptographicKey.cs b/src/RapidField.SolidInstruments.Cryptography/ICryptographicKey.cs index 8e213d69..551029e4 100644 --- a/src/RapidField.SolidInstruments.Cryptography/ICryptographicKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/ICryptographicKey.cs @@ -40,7 +40,7 @@ public TAlgorithm Algorithm /// /// Represents a cryptographic algorithm and key bits. /// - public interface ICryptographicKey : IAsyncDisposable, IDisposable + public interface ICryptographicKey : IAsyncDisposable, ICryptographicComponent, IDisposable { /// /// Converts the value of the current to a secure bit field. @@ -52,5 +52,32 @@ public interface ICryptographicKey : IAsyncDisposable, IDisposable /// The object is disposed. /// public ISecureMemory ToSecureMemory(); + + /// + /// Gets a value indicating whether or not the current can be used to digitally sign + /// information using asymmetric key cryptography. + /// + public Boolean SupportsDigitalSignature + { + get; + } + + /// + /// Gets a value indicating whether or not the current can be used to securely exchange + /// symmetric keys with remote parties. + /// + public Boolean SupportsKeyExchange + { + get; + } + + /// + /// Gets a value indicating whether or not the current can be used to encrypt or decrypt + /// information using symmetric key cryptography. + /// + public Boolean SupportsSymmetricKeyEncryption + { + get; + } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/ICryptographicProcessor.cs b/src/RapidField.SolidInstruments.Cryptography/ICryptographicProcessor.cs new file mode 100644 index 00000000..a526f74a --- /dev/null +++ b/src/RapidField.SolidInstruments.Cryptography/ICryptographicProcessor.cs @@ -0,0 +1,68 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.Cryptography +{ + /// + /// Provides facilities for performing cryptographic operations upon typed objects. + /// + /// + /// The type of the object upon which cryptographic operations are performed. + /// + public interface ICryptographicProcessor : ICryptographicProcessor + where T : class + { + /// + /// Gets the type of the object upon which cryptographic operations are performed. + /// + public Type TargetObjectType + { + get; + } + } + + /// + /// Provides facilities for performing cryptographic operations upon typed objects. + /// + public interface ICryptographicProcessor : ICryptographicComponent + { + /// + /// Gets a value indicating whether or not the current can be used to digitally sign + /// information using asymmetric key cryptography. + /// + public Boolean SupportsDigitalSignature + { + get; + } + + /// + /// Gets a value indicating whether or not the current can be used to produce hash + /// values for plaintext information. + /// + public Boolean SupportsHashing + { + get; + } + + /// + /// Gets a value indicating whether or not the current can be used to securely + /// exchange symmetric keys with remote parties. + /// + public Boolean SupportsKeyExchange + { + get; + } + + /// + /// Gets a value indicating whether or not the current can be used to encrypt or + /// decrypt information using symmetric key cryptography. + /// + public Boolean SupportsSymmetricKeyEncryption + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/IManagedKeyCipher.cs b/src/RapidField.SolidInstruments.Cryptography/IManagedKeyCipher.cs index dd784e52..58246f1c 100644 --- a/src/RapidField.SolidInstruments.Cryptography/IManagedKeyCipher.cs +++ b/src/RapidField.SolidInstruments.Cryptography/IManagedKeyCipher.cs @@ -12,7 +12,7 @@ namespace RapidField.SolidInstruments.Cryptography /// /// Represents a symmetric key encryption facility that uses managed keys to perform cryptographic operations. /// - public interface IManagedKeyCipher : IAsyncDisposable, IDisposable + public interface IManagedKeyCipher : IAsyncDisposable, ICryptographicComponent, IDisposable { /// /// Decrypts the specified model using the master key. diff --git a/src/RapidField.SolidInstruments.Cryptography/ISecurityAppliance.cs b/src/RapidField.SolidInstruments.Cryptography/ISecurityAppliance.cs index 7b1ec775..4d2c4987 100644 --- a/src/RapidField.SolidInstruments.Cryptography/ISecurityAppliance.cs +++ b/src/RapidField.SolidInstruments.Cryptography/ISecurityAppliance.cs @@ -9,7 +9,7 @@ namespace RapidField.SolidInstruments.Cryptography /// /// Represents a centralized utility for performing cryptographic operations. /// - public interface ISecurityAppliance : IManagedKeyCipher, ISecretCertificateImporter, ISecretKeyProducer + public interface ISecurityAppliance : ICryptographicComponent, IManagedKeyCipher, ISecretCertificateImporter, ISecretKeyProducer { /// /// Gets the secret reading facility for the current . diff --git a/src/RapidField.SolidInstruments.Cryptography/ISoftwareSecurityModule.cs b/src/RapidField.SolidInstruments.Cryptography/ISoftwareSecurityModule.cs index 1f25d855..2bf410fd 100644 --- a/src/RapidField.SolidInstruments.Cryptography/ISoftwareSecurityModule.cs +++ b/src/RapidField.SolidInstruments.Cryptography/ISoftwareSecurityModule.cs @@ -3,6 +3,7 @@ // ================================================================================================================================= using RapidField.SolidInstruments.Cryptography.Secrets; +using System; namespace RapidField.SolidInstruments.Cryptography { @@ -37,5 +38,40 @@ public interface ISoftwareSecurityModule public interface ISoftwareSecurityModule : IPersistentSecretStore, ISecurityAppliance { + /// + /// Gets a value indicating whether or not the current can be used to digitally sign + /// information using asymmetric key cryptography. + /// + public Boolean SupportsDigitalSignature + { + get; + } + + /// + /// Gets a value indicating whether or not the current can be used to produce hash + /// values for plaintext information. + /// + public Boolean SupportsHashing + { + get; + } + + /// + /// Gets a value indicating whether or not the current can be used to securely + /// exchange symmetric keys with remote parties. + /// + public Boolean SupportsKeyExchange + { + get; + } + + /// + /// Gets a value indicating whether or not the current can be used to encrypt or + /// decrypt information using symmetric key cryptography. + /// + public Boolean SupportsSymmetricKeyEncryption + { + get; + } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/RapidField.SolidInstruments.Cryptography.csproj b/src/RapidField.SolidInstruments.Cryptography/RapidField.SolidInstruments.Cryptography.csproj index 4478a787..f9fbbc3f 100644 --- a/src/RapidField.SolidInstruments.Cryptography/RapidField.SolidInstruments.Cryptography.csproj +++ b/src/RapidField.SolidInstruments.Cryptography/RapidField.SolidInstruments.Cryptography.csproj @@ -36,6 +36,9 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in + + + diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/IPassword.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/IPassword.cs index 8c98cd0d..2544aff1 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/IPassword.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/IPassword.cs @@ -14,7 +14,7 @@ namespace RapidField.SolidInstruments.Cryptography.Secrets public interface IPassword : IReadOnlySecret { /// - /// Calculates a cryptographically secure salted hash value for the current using the PBKDF2 + /// Calculates a cryptographically secure salted hash value for the current using the Argon2id /// algorithm and returns it as a Base64 string. /// /// @@ -26,7 +26,7 @@ public interface IPassword : IReadOnlySecret public String CalculateSecureHashString(); /// - /// Calculates a cryptographically secure salted hash value for the current using the PBKDF2 + /// Calculates a cryptographically secure salted hash value for the current using the Argon2id /// algorithm. /// /// @@ -38,7 +38,7 @@ public interface IPassword : IReadOnlySecret public IReadOnlyPinnedMemory CalculateSecureHashValue(); /// - /// Calculates a cryptographically secure salted hash value for the current using the PBKDF2 + /// Calculates a cryptographically secure salted hash value for the current using the Argon2id /// algorithm and compares the result with the specified Base64-encoded digest. /// /// @@ -57,7 +57,7 @@ public interface IPassword : IReadOnlySecret public Boolean EvaluateSecureHashString(String hashString); /// - /// Calculates a cryptographically secure salted hash value for the current using the PBKDF2 + /// Calculates a cryptographically secure salted hash value for the current using the Argon2id /// algorithm and compares the result with the specified salted hash value. /// /// diff --git a/src/RapidField.SolidInstruments.Cryptography/Secrets/Password.cs b/src/RapidField.SolidInstruments.Cryptography/Secrets/Password.cs index b190ad19..85ec39d5 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Secrets/Password.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Secrets/Password.cs @@ -198,7 +198,7 @@ public static Password NewStrongPassword() } /// - /// Calculates a cryptographically secure salted hash value for the current using the PBKDF2 + /// Calculates a cryptographically secure salted hash value for the current using the Argon2id /// algorithm and returns it as a Base64 string. /// /// @@ -216,7 +216,7 @@ public String CalculateSecureHashString() } /// - /// Calculates a cryptographically secure salted hash value for the current using the PBKDF2 + /// Calculates a cryptographically secure salted hash value for the current using the Argon2id /// algorithm. /// /// @@ -238,7 +238,7 @@ public IReadOnlyPinnedMemory CalculateSecureHashValue() } /// - /// Calculates a cryptographically secure salted hash value for the current using the PBKDF2 + /// Calculates a cryptographically secure salted hash value for the current using the Argon2id /// algorithm and compares the result with the specified Base64-encoded digest. /// /// @@ -257,7 +257,7 @@ public IReadOnlyPinnedMemory CalculateSecureHashValue() public Boolean EvaluateSecureHashString(String hashString) => EvaluateSecureHashValue(Convert.FromBase64String(hashString.RejectIf().IsNullOrEmpty(nameof(hashString)))); /// - /// Calculates a cryptographically secure salted hash value for the current using the PBKDF2 + /// Calculates a cryptographically secure salted hash value for the current using the Argon2id /// algorithm and compares the result with the specified salted hash value. /// /// @@ -463,7 +463,7 @@ private static Password FromString(String password, String name, Encoding encodi /// instances. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const HashingAlgorithmSpecification HashingAlgorithm = HashingAlgorithmSpecification.Pbkdf2; + private const HashingAlgorithmSpecification HashingAlgorithm = HashingAlgorithmSpecification.Argon2idBalanced; /// /// Represents the lower boundary for the length of randomly-generated strong passwords. diff --git a/src/RapidField.SolidInstruments.Cryptography/SoftwareSecurityModule.cs b/src/RapidField.SolidInstruments.Cryptography/SoftwareSecurityModule.cs index 73646315..e514b55b 100644 --- a/src/RapidField.SolidInstruments.Cryptography/SoftwareSecurityModule.cs +++ b/src/RapidField.SolidInstruments.Cryptography/SoftwareSecurityModule.cs @@ -232,7 +232,7 @@ internal SoftwareSecurityModule(IPassword masterPassword, Boolean deleteStateFil /// [DebuggerHidden] private SoftwareSecurityModule(SecretStoreFilePersistenceVehicle persistenceVehicle) - : base(persistenceVehicle, String.IsNullOrEmpty(persistenceVehicle?.FilePath) ? false : File.Exists(persistenceVehicle.FilePath)) + : base(persistenceVehicle, String.IsNullOrEmpty(persistenceVehicle?.FilePath) == false && File.Exists(persistenceVehicle.FilePath)) { return; } @@ -928,5 +928,35 @@ private void EnsureExistenceOfMasterKey() /// Gets the secret reading facility for the current . /// public ISecretReader SecretReader => InMemoryStore; + + /// + /// Gets a value indicating whether or not the current can be + /// used to digitally sign information using asymmetric key cryptography. + /// + public Boolean SupportsDigitalSignature => Usage.HasFlag(CryptographicComponentUsage.DigitalSignature); + + /// + /// Gets a value indicating whether or not the current can be + /// used to produce hash values for plaintext information. + /// + public Boolean SupportsHashing => Usage.HasFlag(CryptographicComponentUsage.Hashing); + + /// + /// Gets a value indicating whether or not the current can be + /// used to securely exchange symmetric keys with remote parties. + /// + public Boolean SupportsKeyExchange => Usage.HasFlag(CryptographicComponentUsage.KeyExchange); + + /// + /// Gets a value indicating whether or not the current can be + /// used to encrypt or decrypt information using symmetric key cryptography. + /// + public Boolean SupportsSymmetricKeyEncryption => Usage.HasFlag(CryptographicComponentUsage.SymmetricKeyEncryption); + + /// + /// Gets a value specifying the valid purposes and uses of the current + /// . + /// + public CryptographicComponentUsage Usage => CryptographicComponentUsage.SymmetricKeyEncryption | CryptographicComponentUsage.DigitalSignature | CryptographicComponentUsage.KeyExchange; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/CascadingSymmetricKey.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/CascadingSymmetricKey.cs index 2b99dcf6..983fbf4a 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Symmetric/CascadingSymmetricKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Symmetric/CascadingSymmetricKey.cs @@ -485,6 +485,11 @@ public IEnumerable Keys get; } + /// + /// Gets a value specifying the valid purposes and uses of the current . + /// + public override sealed CryptographicComponentUsage Usage => CryptographicComponentUsage.SymmetricKeyEncryption; + /// /// Represents the number of bytes comprising a serialized representation of a . /// diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/ISymmetricProcessor.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/ISymmetricProcessor.cs index 0d335a3d..a4cf0828 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Symmetric/ISymmetricProcessor.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Symmetric/ISymmetricProcessor.cs @@ -20,7 +20,8 @@ public interface ISymmetricProcessor : ISymmetricProcessor /// /// The type of the object that can be encrypted and decrypted. /// - public interface ISymmetricProcessor + public interface ISymmetricProcessor : ICryptographicProcessor + where T : class { /// /// Decrypts the specified ciphertext. diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKey.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKey.cs index 086630fa..15de7d67 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKey.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricKey.cs @@ -359,6 +359,11 @@ internal static SymmetricKey FromKeyMaterial(Byte[] keyMaterial, SymmetricAlgori /// protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + /// + /// Gets a value specifying the valid purposes and uses of the current . + /// + public override sealed CryptographicComponentUsage Usage => CryptographicComponentUsage.SymmetricKeyEncryption; + /// /// Represents the default symmetric-key algorithm specification for new keys. /// diff --git a/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricProcessor.cs b/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricProcessor.cs index 8fd0d257..07a46fe8 100644 --- a/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricProcessor.cs +++ b/src/RapidField.SolidInstruments.Cryptography/Symmetric/SymmetricProcessor.cs @@ -65,7 +65,7 @@ public static ISymmetricProcessor ForType() /// /// The type of the object that can be encrypted and decrypted. /// - public class SymmetricProcessor : ISymmetricProcessor + public class SymmetricProcessor : CryptographicProcessor, ISymmetricProcessor where T : class { /// @@ -78,7 +78,7 @@ public class SymmetricProcessor : ISymmetricProcessor /// is . /// public SymmetricProcessor(RandomNumberGenerator randomnessProvider) - : this(randomnessProvider, DefaultSerializer) + : base(randomnessProvider) { return; } @@ -97,9 +97,9 @@ public SymmetricProcessor(RandomNumberGenerator randomnessProvider) /// . /// public SymmetricProcessor(RandomNumberGenerator randomnessProvider, ISerializer serializer) + : base(randomnessProvider, serializer) { - Serializer = serializer.RejectIf().IsNull(nameof(serializer)).TargetArgument; - RandomnessProvider = randomnessProvider.RejectIf().IsNull(nameof(randomnessProvider)); + return; } /// @@ -455,21 +455,8 @@ private Byte[] Encrypt(T plaintextObject, PinnedMemory key, SymmetricAlgorithmSp } /// - /// Represents the default serializer that is used to transform plaintext. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private static readonly ISerializer DefaultSerializer = new CompressedJsonSerializer(); - - /// - /// Represents a random number generator that is used to generate initialization vectors. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly RandomNumberGenerator RandomnessProvider; - - /// - /// Represents a serializer that is used to transform plaintext. + /// Gets a value specifying the valid purposes and uses of the current . /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly ISerializer Serializer; + public override sealed CryptographicComponentUsage Usage => CryptographicComponentUsage.SymmetricKeyEncryption; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Hashing/HashTreeTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Hashing/HashTreeTests.cs index 38ac84ae..5f64318a 100644 --- a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Hashing/HashTreeTests.cs +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Hashing/HashTreeTests.cs @@ -16,6 +16,19 @@ namespace RapidField.SolidInstruments.Cryptography.UnitTests.Hashing [TestClass] public class HashTreeTests { + [TestMethod] + public void Constructor_ShouldProduceDesiredResults_ForMd5() + { + // Arrange. + var algorithm = HashingAlgorithmSpecification.Md5; + var maxBlockCount = (TreeHeightDictionary.Count - 1); + + for (var i = 0; i <= maxBlockCount; i++) + { + Constructor_ShouldProduceDesiredResults(i, algorithm); + } + } + [TestMethod] public void Constructor_ShouldProduceDesiredResults_ForShaTwo256() { diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Hashing/HashingProcessorTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Hashing/HashingProcessorTests.cs index c0b86095..19aa216d 100644 --- a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Hashing/HashingProcessorTests.cs +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Hashing/HashingProcessorTests.cs @@ -15,6 +15,18 @@ namespace RapidField.SolidInstruments.Cryptography.UnitTests.Hashing [TestClass] public class HashingProcessorTests { + [TestMethod] + public void CalculateHash_ShouldBeDeterministic_ForUnsalted_Argon2idBalanced() => CalculateHash_ShouldBeDeterministic_ForUnsaltedHashing(HashingAlgorithmSpecification.Argon2idBalanced); + + [TestMethod] + public void CalculateHash_ShouldBeDeterministic_ForUnsalted_Argon2idIterativelyExpensive() => CalculateHash_ShouldBeDeterministic_ForUnsaltedHashing(HashingAlgorithmSpecification.Argon2idIterativelyExpensive); + + [TestMethod] + public void CalculateHash_ShouldBeDeterministic_ForUnsalted_Argon2idMemoryExpensive() => CalculateHash_ShouldBeDeterministic_ForUnsaltedHashing(HashingAlgorithmSpecification.Argon2idMemoryExpensive); + + [TestMethod] + public void CalculateHash_ShouldBeDeterministic_ForUnsalted_Argon2idThreadExpensive() => CalculateHash_ShouldBeDeterministic_ForUnsaltedHashing(HashingAlgorithmSpecification.Argon2idThreadExpensive); + [TestMethod] public void CalculateHash_ShouldBeDeterministic_ForUnsalted_Md5() => CalculateHash_ShouldBeDeterministic_ForUnsaltedHashing(HashingAlgorithmSpecification.Md5); @@ -30,6 +42,18 @@ public class HashingProcessorTests [TestMethod] public void CalculateHash_ShouldBeDeterministic_ForUnsalted_ShaTwo512() => CalculateHash_ShouldBeDeterministic_ForUnsaltedHashing(HashingAlgorithmSpecification.ShaTwo512); + [TestMethod] + public void CalculateHash_ShouldNotBeDeterministic_ForSalted_Argon2idBalanced() => CalculateHash_ShouldNotBeDeterministic_ForSaltedHashing(HashingAlgorithmSpecification.Argon2idBalanced); + + [TestMethod] + public void CalculateHash_ShouldNotBeDeterministic_ForSalted_Argon2idIterativelyExpensive() => CalculateHash_ShouldNotBeDeterministic_ForSaltedHashing(HashingAlgorithmSpecification.Argon2idIterativelyExpensive); + + [TestMethod] + public void CalculateHash_ShouldNotBeDeterministic_ForSalted_Argon2idMemoryExpensive() => CalculateHash_ShouldNotBeDeterministic_ForSaltedHashing(HashingAlgorithmSpecification.Argon2idMemoryExpensive); + + [TestMethod] + public void CalculateHash_ShouldNotBeDeterministic_ForSalted_Argon2idThreadExpensive() => CalculateHash_ShouldNotBeDeterministic_ForSaltedHashing(HashingAlgorithmSpecification.Argon2idThreadExpensive); + [TestMethod] public void CalculateHash_ShouldNotBeDeterministic_ForSalted_Md5() => CalculateHash_ShouldNotBeDeterministic_ForSaltedHashing(HashingAlgorithmSpecification.Md5); @@ -45,6 +69,18 @@ public class HashingProcessorTests [TestMethod] public void CalculateHash_ShouldNotBeDeterministic_ForSalted_ShaTwo512() => CalculateHash_ShouldNotBeDeterministic_ForSaltedHashing(HashingAlgorithmSpecification.ShaTwo512); + [TestMethod] + public void EvaluateHash_ShouldProduceDesiredResults_ForSalted_Argon2idBalanced() => EvaluateHash_ShouldProduceDesiredResults_ForSaltedHashing(HashingAlgorithmSpecification.Argon2idBalanced); + + [TestMethod] + public void EvaluateHash_ShouldProduceDesiredResults_ForSalted_Argon2idIterativelyExpensive() => EvaluateHash_ShouldProduceDesiredResults_ForSaltedHashing(HashingAlgorithmSpecification.Argon2idIterativelyExpensive); + + [TestMethod] + public void EvaluateHash_ShouldProduceDesiredResults_ForSalted_Argon2idMemoryExpensive() => EvaluateHash_ShouldProduceDesiredResults_ForSaltedHashing(HashingAlgorithmSpecification.Argon2idMemoryExpensive); + + [TestMethod] + public void EvaluateHash_ShouldProduceDesiredResults_ForSalted_Argon2idThreadExpensive() => EvaluateHash_ShouldProduceDesiredResults_ForSaltedHashing(HashingAlgorithmSpecification.Argon2idThreadExpensive); + [TestMethod] public void EvaluateHash_ShouldProduceDesiredResults_ForSalted_Md5() => EvaluateHash_ShouldProduceDesiredResults_ForSaltedHashing(HashingAlgorithmSpecification.Md5); @@ -60,6 +96,18 @@ public class HashingProcessorTests [TestMethod] public void EvaluateHash_ShouldProduceDesiredResults_ForSalted_ShaTwo512() => EvaluateHash_ShouldProduceDesiredResults_ForSaltedHashing(HashingAlgorithmSpecification.ShaTwo512); + [TestMethod] + public void EvaluateHash_ShouldProduceDesiredResults_ForUnsalted_Argon2idBalanced() => EvaluateHash_ShouldProduceDesiredResults_ForUnsaltedHashing(HashingAlgorithmSpecification.Argon2idBalanced); + + [TestMethod] + public void EvaluateHash_ShouldProduceDesiredResults_ForUnsalted_Argon2idIterativelyExpensive() => EvaluateHash_ShouldProduceDesiredResults_ForUnsaltedHashing(HashingAlgorithmSpecification.Argon2idIterativelyExpensive); + + [TestMethod] + public void EvaluateHash_ShouldProduceDesiredResults_ForUnsalted_Argon2idMemoryExpensive() => EvaluateHash_ShouldProduceDesiredResults_ForUnsaltedHashing(HashingAlgorithmSpecification.Argon2idMemoryExpensive); + + [TestMethod] + public void EvaluateHash_ShouldProduceDesiredResults_ForUnsalted_Argon2idThreadExpensive() => EvaluateHash_ShouldProduceDesiredResults_ForUnsaltedHashing(HashingAlgorithmSpecification.Argon2idThreadExpensive); + [TestMethod] public void EvaluateHash_ShouldProduceDesiredResults_ForUnsalted_Md5() => EvaluateHash_ShouldProduceDesiredResults_ForUnsaltedHashing(HashingAlgorithmSpecification.Md5); @@ -149,6 +197,26 @@ private static void ValidateDigestLength(Byte[] hashValue, HashingAlgorithmSpeci { switch (algorithm) { + case HashingAlgorithmSpecification.Argon2idBalanced: + + hashValue.Length.Should().Be(32 + (saltingMode == SaltingMode.Salted ? saltLengthInBytes : 0)); + break; + + case HashingAlgorithmSpecification.Argon2idIterativelyExpensive: + + hashValue.Length.Should().Be(32 + (saltingMode == SaltingMode.Salted ? saltLengthInBytes : 0)); + break; + + case HashingAlgorithmSpecification.Argon2idMemoryExpensive: + + hashValue.Length.Should().Be(32 + (saltingMode == SaltingMode.Salted ? saltLengthInBytes : 0)); + break; + + case HashingAlgorithmSpecification.Argon2idThreadExpensive: + + hashValue.Length.Should().Be(32 + (saltingMode == SaltingMode.Salted ? saltLengthInBytes : 0)); + break; + case HashingAlgorithmSpecification.Md5: hashValue.Length.Should().Be(16 + (saltingMode == SaltingMode.Salted ? saltLengthInBytes : 0)); diff --git a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Hashing/HashingStringProcessorTests.cs b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Hashing/HashingStringProcessorTests.cs index a98b780a..ad7b49aa 100644 --- a/test/RapidField.SolidInstruments.Cryptography.UnitTests/Hashing/HashingStringProcessorTests.cs +++ b/test/RapidField.SolidInstruments.Cryptography.UnitTests/Hashing/HashingStringProcessorTests.cs @@ -15,9 +15,15 @@ namespace RapidField.SolidInstruments.Cryptography.UnitTests.Hashing [TestClass] public class HashingStringProcessorTests { + [TestMethod] + public void CalculateHash_ShouldBeDeterministic_ForUnsalted_Argon2idBalanced() => CalculateHash_ShouldBeDeterministic_ForUnsaltedHashing(HashingAlgorithmSpecification.Argon2idBalanced); + [TestMethod] public void CalculateHash_ShouldBeDeterministic_ForUnsalted_Md5() => CalculateHash_ShouldBeDeterministic_ForUnsaltedHashing(HashingAlgorithmSpecification.Md5); + [TestMethod] + public void CalculateHash_ShouldBeDeterministic_ForUnsalted_Pbkdf2() => CalculateHash_ShouldBeDeterministic_ForUnsaltedHashing(HashingAlgorithmSpecification.Pbkdf2); + [TestMethod] public void CalculateHash_ShouldBeDeterministic_ForUnsalted_ShaTwo256() => CalculateHash_ShouldBeDeterministic_ForUnsaltedHashing(HashingAlgorithmSpecification.ShaTwo256); @@ -27,9 +33,15 @@ public class HashingStringProcessorTests [TestMethod] public void CalculateHash_ShouldBeDeterministic_ForUnsalted_ShaTwo512() => CalculateHash_ShouldBeDeterministic_ForUnsaltedHashing(HashingAlgorithmSpecification.ShaTwo512); + [TestMethod] + public void CalculateHash_ShouldNotBeDeterministic_ForSalted_Argon2idBalanced() => CalculateHash_ShouldNotBeDeterministic_ForSaltedHashing(HashingAlgorithmSpecification.Argon2idBalanced); + [TestMethod] public void CalculateHash_ShouldNotBeDeterministic_ForSalted_Md5() => CalculateHash_ShouldNotBeDeterministic_ForSaltedHashing(HashingAlgorithmSpecification.Md5); + [TestMethod] + public void CalculateHash_ShouldNotBeDeterministic_ForSalted_Pbkdf2() => CalculateHash_ShouldNotBeDeterministic_ForSaltedHashing(HashingAlgorithmSpecification.Pbkdf2); + [TestMethod] public void CalculateHash_ShouldNotBeDeterministic_ForSalted_ShaTwo256() => CalculateHash_ShouldNotBeDeterministic_ForSaltedHashing(HashingAlgorithmSpecification.ShaTwo256); From a2b4b39f898abd25e1e4efe263e33bea525a61f6 Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Tue, 14 Jul 2020 09:20:13 -0500 Subject: [PATCH 44/55] Add IDataAccessModelRepository, IDomainModelRepository and related types. --- .../AddFibonacciNumberCommandHandler.cs | 10 +- .../GetFibonacciNumberValuesCommandHandler.cs | 10 +- src/RapidField.SolidInstruments.Core/Model.cs | 4 +- .../EntityFrameworkRepository.cs | 73 +++++ .../DataAccessCommandHandler.cs | 100 ++++-- .../IAggregateDataAccessModel.cs | 42 +++ .../IDataAccessModel.cs | 75 +++++ .../IDataAccessModelRepository.cs | 41 +++ .../IDomainModelRepository.cs | 290 ++++++++++++++++++ ...IGlobalIdentityAggregateDataAccessModel.cs | 29 ++ .../IGlobalIdentityDataAccessModel.cs | 28 ++ .../IGlobalIdentityValueDataAccessModel.cs | 27 ++ ...NumericIdentityAggregateDataAccessModel.cs | 29 ++ .../INumericIdentityDataAccessModel.cs | 28 ++ .../INumericIdentityValueDataAccessModel.cs | 29 ++ .../IReadOnlyDataAccessModelRepository.cs | 44 +++ .../IReadOnlyDomainModelRepository.cs | 137 +++++++++ ...emanticIdentityAggregateDataAccessModel.cs | 29 ++ .../ISemanticIdentityDataAccessModel.cs | 28 ++ .../ISemanticIdentityValueDataAccessModel.cs | 29 ++ .../IValueDataAccessModel.cs | 42 +++ ...idField.SolidInstruments.DataAccess.csproj | 2 - 22 files changed, 1075 insertions(+), 51 deletions(-) create mode 100644 src/RapidField.SolidInstruments.DataAccess/IAggregateDataAccessModel.cs create mode 100644 src/RapidField.SolidInstruments.DataAccess/IDataAccessModel.cs create mode 100644 src/RapidField.SolidInstruments.DataAccess/IDataAccessModelRepository.cs create mode 100644 src/RapidField.SolidInstruments.DataAccess/IDomainModelRepository.cs create mode 100644 src/RapidField.SolidInstruments.DataAccess/IGlobalIdentityAggregateDataAccessModel.cs create mode 100644 src/RapidField.SolidInstruments.DataAccess/IGlobalIdentityDataAccessModel.cs create mode 100644 src/RapidField.SolidInstruments.DataAccess/IGlobalIdentityValueDataAccessModel.cs create mode 100644 src/RapidField.SolidInstruments.DataAccess/INumericIdentityAggregateDataAccessModel.cs create mode 100644 src/RapidField.SolidInstruments.DataAccess/INumericIdentityDataAccessModel.cs create mode 100644 src/RapidField.SolidInstruments.DataAccess/INumericIdentityValueDataAccessModel.cs create mode 100644 src/RapidField.SolidInstruments.DataAccess/IReadOnlyDataAccessModelRepository.cs create mode 100644 src/RapidField.SolidInstruments.DataAccess/IReadOnlyDomainModelRepository.cs create mode 100644 src/RapidField.SolidInstruments.DataAccess/ISemanticIdentityAggregateDataAccessModel.cs create mode 100644 src/RapidField.SolidInstruments.DataAccess/ISemanticIdentityDataAccessModel.cs create mode 100644 src/RapidField.SolidInstruments.DataAccess/ISemanticIdentityValueDataAccessModel.cs create mode 100644 src/RapidField.SolidInstruments.DataAccess/IValueDataAccessModel.cs diff --git a/example/RapidField.SolidInstruments.Example.DatabaseModel/CommandHandlers/AddFibonacciNumberCommandHandler.cs b/example/RapidField.SolidInstruments.Example.DatabaseModel/CommandHandlers/AddFibonacciNumberCommandHandler.cs index 78bebaf6..2f23a341 100644 --- a/example/RapidField.SolidInstruments.Example.DatabaseModel/CommandHandlers/AddFibonacciNumberCommandHandler.cs +++ b/example/RapidField.SolidInstruments.Example.DatabaseModel/CommandHandlers/AddFibonacciNumberCommandHandler.cs @@ -3,8 +3,6 @@ // ================================================================================================================================= using RapidField.SolidInstruments.Command; -using RapidField.SolidInstruments.Core.Concurrency; -using RapidField.SolidInstruments.DataAccess; using RapidField.SolidInstruments.Example.DatabaseModel.Commands; using RapidField.SolidInstruments.Example.DatabaseModel.Entities; using RapidField.SolidInstruments.Example.DatabaseModel.Repositories; @@ -55,13 +53,7 @@ public AddFibonacciNumberCommandHandler(ICommandMediator mediator, ExampleReposi /// /// An object that provides access to data access repositories. /// - /// - /// A transaction that is used to process the command. - /// - /// - /// A token that represents and manages contextual thread safety. - /// - protected override void Process(AddFibonacciNumberCommand command, IFactoryProducedInstanceGroup repositories, IDataAccessTransaction transaction, IConcurrencyControlToken controlToken) + protected override void Process(AddFibonacciNumberCommand command, IFactoryProducedInstanceGroup repositories) { var fibonacciNumberSeries = NumberSeries.Named.Fibonacci; var numberRepository = repositories.Get(); diff --git a/example/RapidField.SolidInstruments.Example.DatabaseModel/CommandHandlers/GetFibonacciNumberValuesCommandHandler.cs b/example/RapidField.SolidInstruments.Example.DatabaseModel/CommandHandlers/GetFibonacciNumberValuesCommandHandler.cs index 3a50a434..0b3692db 100644 --- a/example/RapidField.SolidInstruments.Example.DatabaseModel/CommandHandlers/GetFibonacciNumberValuesCommandHandler.cs +++ b/example/RapidField.SolidInstruments.Example.DatabaseModel/CommandHandlers/GetFibonacciNumberValuesCommandHandler.cs @@ -3,8 +3,6 @@ // ================================================================================================================================= using RapidField.SolidInstruments.Command; -using RapidField.SolidInstruments.Core.Concurrency; -using RapidField.SolidInstruments.DataAccess; using RapidField.SolidInstruments.Example.DatabaseModel.Commands; using RapidField.SolidInstruments.Example.DatabaseModel.Entities; using RapidField.SolidInstruments.Example.DatabaseModel.Repositories; @@ -56,16 +54,10 @@ public GetFibonacciNumberValuesCommandHandler(ICommandMediator mediator, Example /// /// An object that provides access to data access repositories. /// - /// - /// A transaction that is used to process the command. - /// - /// - /// A token that represents and manages contextual thread safety. - /// /// /// The result that is emitted when processing the command. /// - protected sealed override IEnumerable Process(GetFibonacciNumberValuesCommand command, IFactoryProducedInstanceGroup repositories, IDataAccessTransaction transaction, IConcurrencyControlToken controlToken) + protected sealed override IEnumerable Process(GetFibonacciNumberValuesCommand command, IFactoryProducedInstanceGroup repositories) { var numberSeriesNumberRepository = repositories.Get(); return numberSeriesNumberRepository.FindWhere(entity => entity.NumberSeries.Identifier == NumberSeries.Named.Fibonacci.Identifier).Select(entity => entity.Number.Value).ToList(); diff --git a/src/RapidField.SolidInstruments.Core/Model.cs b/src/RapidField.SolidInstruments.Core/Model.cs index d45060cb..675bdec5 100644 --- a/src/RapidField.SolidInstruments.Core/Model.cs +++ b/src/RapidField.SolidInstruments.Core/Model.cs @@ -276,9 +276,9 @@ public override Boolean Equals(Object obj) { return false; } - else if (obj is IModel) + else if (obj is IModel model) { - return Equals((IModel)obj); + return Equals(model); } return false; diff --git a/src/RapidField.SolidInstruments.DataAccess.EntityFramework/EntityFrameworkRepository.cs b/src/RapidField.SolidInstruments.DataAccess.EntityFramework/EntityFrameworkRepository.cs index a5d913f5..77ca00f0 100644 --- a/src/RapidField.SolidInstruments.DataAccess.EntityFramework/EntityFrameworkRepository.cs +++ b/src/RapidField.SolidInstruments.DataAccess.EntityFramework/EntityFrameworkRepository.cs @@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore; using RapidField.SolidInstruments.Core.ArgumentValidation; using RapidField.SolidInstruments.Core.Concurrency; +using RapidField.SolidInstruments.Core.Domain; using System; using System.Collections.Generic; using System.Diagnostics; @@ -13,6 +14,78 @@ namespace RapidField.SolidInstruments.DataAccess.EntityFramework { + /// + /// Performs data access operations against an Entity Framework data model type using a single transaction. + /// + /// + /// The type of the unique primary identifier for the data model. + /// + /// + /// The type of the data model. + /// + /// + /// The type of the domain model to which the data access model is mapped. + /// + /// + /// The type of the database session for the repository. + /// + public class EntityFrameworkRepository : EntityFrameworkRepository, IDomainModelRepository + where TIdentifier : IComparable, IComparable, IEquatable + where TDomainModel : class, IDomainModel + where TDataAccessModel : class, IDataAccessModel, new() + where TContext : DbContext + { + /// + /// Initializes a new instance of the + /// class. + /// + /// + /// The database session for the repository. + /// + /// + /// is . + /// + public EntityFrameworkRepository(TContext context) + : base(context) + { + return; + } + } + + /// + /// Performs data access operations against an Entity Framework data model type using a single transaction. + /// + /// + /// The type of the unique primary identifier for the data model. + /// + /// + /// The type of the data model. + /// + /// + /// The type of the database session for the repository. + /// + public class EntityFrameworkRepository : EntityFrameworkRepository, IDataAccessModelRepository + where TIdentifier : IComparable, IComparable, IEquatable + where TDataAccessModel : class, IDataAccessModel + where TContext : DbContext + { + /// + /// Initializes a new instance of the + /// class. + /// + /// + /// The database session for the repository. + /// + /// + /// is . + /// + public EntityFrameworkRepository(TContext context) + : base(context) + { + return; + } + } + /// /// Performs data access operations against an Entity Framework entity type using a single transaction. /// diff --git a/src/RapidField.SolidInstruments.DataAccess/DataAccessCommandHandler.cs b/src/RapidField.SolidInstruments.DataAccess/DataAccessCommandHandler.cs index 9f1502b3..a29bcd2b 100644 --- a/src/RapidField.SolidInstruments.DataAccess/DataAccessCommandHandler.cs +++ b/src/RapidField.SolidInstruments.DataAccess/DataAccessCommandHandler.cs @@ -98,22 +98,13 @@ protected sealed override void Process(TCommand command, ICommandMediator mediat try { Transaction.Begin(); - Process(command, Repositories, Transaction, controlToken); - - if (Transaction.State == DataAccessTransactionState.InProgress) - { - controlToken.AttachTask(Transaction.CommitAsync()); - } - + Process(command, Repositories); + CommitTransaction(Transaction, controlToken); return; } catch { - if (Transaction.State == DataAccessTransactionState.InProgress) - { - controlToken.AttachTask(Transaction.RejectAsync()); - } - + AbortTransaction(Transaction, controlToken); throw; } } @@ -130,13 +121,43 @@ protected sealed override void Process(TCommand command, ICommandMediator mediat /// /// An object that provides access to data access repositories. /// + protected abstract void Process(TCommand command, IFactoryProducedInstanceGroup repositories); + + /// + /// Conditionally starts an asynchronous task that rejects all changes made within the scope of the specified transaction. + /// + /// + /// A transaction that is used to process the command. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + [DebuggerHidden] + private static void AbortTransaction(IDataAccessTransaction transaction, IConcurrencyControlToken controlToken) + { + if (transaction.State == DataAccessTransactionState.InProgress) + { + controlToken.AttachTask(transaction.RejectAsync()); + } + } + + /// + /// Conditionally starts an asynchronous task that commits all changes made within the scope of the specified transaction. + /// /// /// A transaction that is used to process the command. /// /// /// A token that represents and manages contextual thread safety. /// - protected abstract void Process(TCommand command, IFactoryProducedInstanceGroup repositories, IDataAccessTransaction transaction, IConcurrencyControlToken controlToken); + [DebuggerHidden] + private static void CommitTransaction(IDataAccessTransaction transaction, IConcurrencyControlToken controlToken) + { + if (transaction.State == DataAccessTransactionState.InProgress) + { + controlToken.AttachTask(transaction.CommitAsync()); + } + } /// /// Represents an object that provides access to data access repositories. @@ -244,22 +265,13 @@ protected sealed override TResult Process(TCommand command, ICommandMediator med try { Transaction.Begin(); - var result = Process(command, Repositories, Transaction, controlToken); - - if (Transaction.State == DataAccessTransactionState.InProgress) - { - controlToken.AttachTask(Transaction.CommitAsync()); - } - + var result = Process(command, Repositories); + CommitTransaction(Transaction, controlToken); return result; } catch { - if (Transaction.State == DataAccessTransactionState.InProgress) - { - controlToken.AttachTask(Transaction.RejectAsync()); - } - + AbortTransaction(Transaction, controlToken); throw; } } @@ -276,16 +288,46 @@ protected sealed override TResult Process(TCommand command, ICommandMediator med /// /// An object that provides access to data access repositories. /// + /// + /// The result that is emitted when processing the command. + /// + protected abstract TResult Process(TCommand command, IFactoryProducedInstanceGroup repositories); + + /// + /// Conditionally starts an asynchronous task that rejects all changes made within the scope of the specified transaction. + /// /// /// A transaction that is used to process the command. /// /// /// A token that represents and manages contextual thread safety. /// - /// - /// The result that is emitted when processing the command. - /// - protected abstract TResult Process(TCommand command, IFactoryProducedInstanceGroup repositories, IDataAccessTransaction transaction, IConcurrencyControlToken controlToken); + [DebuggerHidden] + private static void AbortTransaction(IDataAccessTransaction transaction, IConcurrencyControlToken controlToken) + { + if (transaction.State == DataAccessTransactionState.InProgress) + { + controlToken.AttachTask(transaction.RejectAsync()); + } + } + + /// + /// Conditionally starts an asynchronous task that commits all changes made within the scope of the specified transaction. + /// + /// + /// A transaction that is used to process the command. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + [DebuggerHidden] + private static void CommitTransaction(IDataAccessTransaction transaction, IConcurrencyControlToken controlToken) + { + if (transaction.State == DataAccessTransactionState.InProgress) + { + controlToken.AttachTask(transaction.CommitAsync()); + } + } /// /// Represents an object that provides access to data access repositories. diff --git a/src/RapidField.SolidInstruments.DataAccess/IAggregateDataAccessModel.cs b/src/RapidField.SolidInstruments.DataAccess/IAggregateDataAccessModel.cs new file mode 100644 index 00000000..2711499a --- /dev/null +++ b/src/RapidField.SolidInstruments.DataAccess/IAggregateDataAccessModel.cs @@ -0,0 +1,42 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; +using System; + +namespace RapidField.SolidInstruments.DataAccess +{ + /// + /// Represents an object that models a data access entity as a domain aggregate. + /// + /// + /// The type of the unique primary identifier for the model. + /// + /// + /// The type of the domain model to which the data access model is mapped. + /// + public interface IAggregateDataAccessModel : IAggregateDataAccessModel, IValueDataAccessModel + where TIdentifier : IComparable, IComparable, IEquatable + where TDomainModel : class, IDomainModel + { + } + + /// + /// Represents an object that models a data access entity as a domain aggregate. + /// + /// + /// The type of the unique primary identifier for the model. + /// + public interface IAggregateDataAccessModel : IAggregateDataAccessModel, IValueDataAccessModel + where TIdentifier : IComparable, IComparable, IEquatable + { + } + + /// + /// Represents an object that models a data access entity as a domain aggregate. + /// + public interface IAggregateDataAccessModel : IValueDataAccessModel + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess/IDataAccessModel.cs b/src/RapidField.SolidInstruments.DataAccess/IDataAccessModel.cs new file mode 100644 index 00000000..87e3d6a0 --- /dev/null +++ b/src/RapidField.SolidInstruments.DataAccess/IDataAccessModel.cs @@ -0,0 +1,75 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; +using System; + +namespace RapidField.SolidInstruments.DataAccess +{ + /// + /// Represents an object that models a data access entity. + /// + /// + /// The type of the unique primary identifier for the model. + /// + /// + /// The type of the domain model to which the data access model is mapped. + /// + public interface IDataAccessModel : IDataAccessModel + where TIdentifier : IComparable, IComparable, IEquatable + where TDomainModel : class, IDomainModel + { + /// + /// Copies the state of the specified domain model to the current + /// . + /// + /// + /// A domain model from which to hydrate the state of the data access model. + /// + /// + /// The state of is invalid. + /// + /// + /// is . + /// + public void HydrateFromDomainModel(TDomainModel domainModel); + + /// + /// Converts the current to its equivalent domain model + /// representation. + /// + /// + /// An that is equivalent to the current data access model. + /// + /// + /// An exception was raised while converting the data access model to its equivalent domain model. + /// + public TDomainModel ToDomainModel(); + } + + /// + /// Represents an object that models a data access entity. + /// + /// + /// The type of the unique primary identifier for the model. + /// + public interface IDataAccessModel : IDataAccessModel + where TIdentifier : IComparable, IComparable, IEquatable + { + /// + /// Gets a value that uniquely identifies the current . + /// + public TIdentifier Identifier + { + get; + } + } + + /// + /// Represents an object that models a data access entity. + /// + public interface IDataAccessModel + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess/IDataAccessModelRepository.cs b/src/RapidField.SolidInstruments.DataAccess/IDataAccessModelRepository.cs new file mode 100644 index 00000000..bacc9f45 --- /dev/null +++ b/src/RapidField.SolidInstruments.DataAccess/IDataAccessModelRepository.cs @@ -0,0 +1,41 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.DataAccess +{ + /// + /// Performs data access operations for a specified data access model type. + /// + /// + /// The type of the unique primary identifier for the data model. + /// + /// + /// The type of the data model. + /// + public interface IDataAccessModelRepository : IDataAccessRepository, IReadOnlyDataAccessModelRepository + where TIdentifier : IComparable, IComparable, IEquatable + where TDataAccessModel : class, IDataAccessModel + { + /// + /// Removes the data model matching the specified identifier from the current + /// . + /// + /// + /// The unique primary identifier for the data model. + /// + /// + /// The object is disposed. + /// + public void RemoveByIdentifier(TIdentifier identifier) => RemoveWhere(entity => entity.Identifier.Equals(identifier)); + } + + /// + /// Performs data access operations for a specified data access model type. + /// + public interface IDataAccessModelRepository : IDataAccessRepository + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess/IDomainModelRepository.cs b/src/RapidField.SolidInstruments.DataAccess/IDomainModelRepository.cs new file mode 100644 index 00000000..c409192f --- /dev/null +++ b/src/RapidField.SolidInstruments.DataAccess/IDomainModelRepository.cs @@ -0,0 +1,290 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Core.Domain; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace RapidField.SolidInstruments.DataAccess +{ + /// + /// Performs data access operations for a specified domain model type. + /// + /// + /// The type of the unique primary identifier for the data model. + /// + /// + /// The type of the data model. + /// + /// + /// The type of the domain model to which the data access model is mapped. + /// + public interface IDomainModelRepository : IDataAccessModelRepository, IReadOnlyDomainModelRepository + where TIdentifier : IComparable, IComparable, IEquatable + where TDomainModel : class, IDomainModel + where TDataAccessModel : class, IDataAccessModel, new() + { + /// + /// Adds the specified domain model to the current + /// . + /// + /// + /// The domain model to add. + /// + /// + /// The state of is invalid. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public void AddDomainModel(TDomainModel domainModel) => Add(ConvertToDataAccessModel(domainModel)); + + /// + /// Adds the specified domain models to the current + /// . + /// + /// + /// The domain models to add. + /// + /// + /// contains one or more null references -or- the state of one or more models is invalid. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public void AddDomainModelRange(IEnumerable domainModels) => AddRange(domainModels.RejectIf().IsNull(nameof(domainModels)).TargetArgument.Select(domainModel => ConvertToDataAccessModel(domainModel))); + + /// + /// Updates the specified domain model in the current + /// , or adds it if it doesn't exist. + /// + /// + /// The domain model to add or update. + /// + /// + /// The state of is invalid. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public void AddOrUpdateDomainModel(TDomainModel domainModel) => AddOrUpdate(ConvertToDataAccessModel(domainModel)); + + /// + /// Updates the specified domain models in the current + /// , or adds them if they don't exist. + /// + /// + /// The domain models to add or update. + /// + /// + /// contains one or more null references -or- the state of one or more models is invalid. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public void AddOrUpdateDomainModelRange(IEnumerable domainModels) => AddOrUpdateRange(domainModels.RejectIf().IsNull(nameof(domainModels)).TargetArgument.Select(domainModel => ConvertToDataAccessModel(domainModel))); + + /// + /// Removes the specified domain model from the current + /// . + /// + /// + /// The domain model to remove. + /// + /// + /// The state of is invalid. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public void RemoveDomainModel(TDomainModel domainModel) => Remove(ConvertToDataAccessModel(domainModel)); + + /// + /// Removes the specified domain models from the current + /// . + /// + /// + /// The domain models to remove. + /// + /// + /// contains one or more null references -or- the state of one or more models is invalid. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public void RemoveDomainModelRange(IEnumerable domainModels) => RemoveRange(domainModels.RejectIf().IsNull(nameof(domainModels)).TargetArgument.Select(domainModel => ConvertToDataAccessModel(domainModel))); + + /// + /// Updates the specified domain model in the current + /// . + /// + /// + /// The domain model to update. + /// + /// + /// The state of is invalid. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public void UpdateDomainModel(TDomainModel domainModel) => Update(ConvertToDataAccessModel(domainModel)); + + /// + /// Updates the specified domain models in the current + /// . + /// + /// + /// The domain models to update. + /// + /// + /// contains one or more null references -or- the state of one or more models is invalid. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public void UpdateDomainModelRange(IEnumerable domainModels) => UpdateRange(domainModels.RejectIf().IsNull(nameof(domainModels)).TargetArgument.Select(domainModel => ConvertToDataAccessModel(domainModel))); + + /// + /// Converts the specified domain model to its data access model equivalent. + /// + /// + /// The domain model to convert. + /// + /// + /// The resulting data access model. + /// + /// + /// The state of is invalid. + /// + /// + /// is . + /// + [DebuggerHidden] + private TDataAccessModel ConvertToDataAccessModel(TDomainModel domainModel) => ConvertToDataAccessModel(domainModel); + } + + /// + /// Performs data access operations for a specified domain model type. + /// + public interface IDomainModelRepository : IDataAccessModelRepository + { + /// + /// Converts the specified domain model to its data access model equivalent. + /// + /// + /// The type of the unique primary identifier for the data model. + /// + /// + /// The type of the data model. + /// + /// + /// The type of the domain model to which the data access model is mapped. + /// + /// + /// The domain model to convert. + /// + /// + /// The resulting data access model. + /// + /// + /// The state of is invalid. + /// + /// + /// is . + /// + [DebuggerHidden] + internal static TDataAccessModel ConvertToDataAccessModel(TDomainModel domainModel) + where TIdentifier : IComparable, IComparable, IEquatable + where TDomainModel : class, IDomainModel + where TDataAccessModel : class, IDataAccessModel, new() + { + _ = domainModel.RejectIf().IsNull(nameof(domainModel)); + var dataAccessModel = new TDataAccessModel(); + + try + { + dataAccessModel.HydrateFromDomainModel(domainModel); + } + catch (ArgumentException) + { + throw; + } + catch (Exception exception) + { + throw new ArgumentException("The state of the specified domain model is invalid.", nameof(domainModel), exception); + } + + return dataAccessModel; + } + + /// + /// Converts the specified data access model to its domain model equivalent. + /// + /// + /// The type of the unique primary identifier for the data model. + /// + /// + /// The type of the data model. + /// + /// + /// The type of the domain model to which the data access model is mapped. + /// + /// + /// The data access model to convert. + /// + /// + /// The resulting domain model. + /// + /// + /// An exception was raised while converting the data access model to its equivalent domain model. + /// + [DebuggerHidden] + internal static TDomainModel ConvertToDomainModel(TDataAccessModel dataAccessModel) + where TIdentifier : IComparable, IComparable, IEquatable + where TDomainModel : class, IDomainModel + where TDataAccessModel : class, IDataAccessModel, new() + { + try + { + return dataAccessModel?.ToDomainModel(); + } + catch (TypeInitializationException) + { + throw; + } + catch (Exception exception) + { + var domainModelType = typeof(TDomainModel); + throw new TypeInitializationException(domainModelType.FullName, exception); + } + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess/IGlobalIdentityAggregateDataAccessModel.cs b/src/RapidField.SolidInstruments.DataAccess/IGlobalIdentityAggregateDataAccessModel.cs new file mode 100644 index 00000000..e77f3cbf --- /dev/null +++ b/src/RapidField.SolidInstruments.DataAccess/IGlobalIdentityAggregateDataAccessModel.cs @@ -0,0 +1,29 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; +using System; + +namespace RapidField.SolidInstruments.DataAccess +{ + /// + /// Represents an object that models an aggregate data access entity and that is identified primarily by a + /// value. + /// + /// + /// The type of the domain model to which the data access model is mapped. + /// + public interface IGlobalIdentityAggregateDataAccessModel : IGlobalIdentityAggregateDataAccessModel, IAggregateDataAccessModel + where TDomainModel : class, IGlobalIdentityAggregateDomainModel + { + } + + /// + /// Represents an object that models an aggregate data access entity and that is identified primarily by a + /// value. + /// + public interface IGlobalIdentityAggregateDataAccessModel : IGlobalIdentityDataAccessModel, IAggregateDataAccessModel + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess/IGlobalIdentityDataAccessModel.cs b/src/RapidField.SolidInstruments.DataAccess/IGlobalIdentityDataAccessModel.cs new file mode 100644 index 00000000..26700fe8 --- /dev/null +++ b/src/RapidField.SolidInstruments.DataAccess/IGlobalIdentityDataAccessModel.cs @@ -0,0 +1,28 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.Domain; +using System; + +namespace RapidField.SolidInstruments.DataAccess +{ + /// + /// Represents an object that models a data access entity and that is identified primarily by a value. + /// + /// + /// The type of the domain model to which the data access model is mapped. + /// + public interface IGlobalIdentityDataAccessModel : IGlobalIdentityDataAccessModel, IDataAccessModel + where TDomainModel : class, IGlobalIdentityDomainModel + { + } + + /// + /// Represents an object that models a data access entity and that is identified primarily by a value. + /// + public interface IGlobalIdentityDataAccessModel : IDataAccessModel, IGlobalIdentityModel + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess/IGlobalIdentityValueDataAccessModel.cs b/src/RapidField.SolidInstruments.DataAccess/IGlobalIdentityValueDataAccessModel.cs new file mode 100644 index 00000000..1ccbddae --- /dev/null +++ b/src/RapidField.SolidInstruments.DataAccess/IGlobalIdentityValueDataAccessModel.cs @@ -0,0 +1,27 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; +using System; + +namespace RapidField.SolidInstruments.DataAccess +{ + /// + /// Represents an object that models a value data access entity and that is identified primarily by a value. + /// + /// + /// The type of the domain model to which the data access model is mapped. + /// + public interface IGlobalIdentityValueDataAccessModel : IGlobalIdentityValueDataAccessModel, IValueDataAccessModel + where TDomainModel : class, IGlobalIdentityValueDomainModel + { + } + + /// + /// Represents an object that models a value data access entity and that is identified primarily by a value. + /// + public interface IGlobalIdentityValueDataAccessModel : IGlobalIdentityDataAccessModel, IValueDataAccessModel + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess/INumericIdentityAggregateDataAccessModel.cs b/src/RapidField.SolidInstruments.DataAccess/INumericIdentityAggregateDataAccessModel.cs new file mode 100644 index 00000000..cdfddddb --- /dev/null +++ b/src/RapidField.SolidInstruments.DataAccess/INumericIdentityAggregateDataAccessModel.cs @@ -0,0 +1,29 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; +using System; + +namespace RapidField.SolidInstruments.DataAccess +{ + /// + /// Represents an object that models an aggregate data access entity and that is identified primarily by an + /// value. + /// + /// + /// The type of the domain model to which the data access model is mapped. + /// + public interface INumericIdentityAggregateDataAccessModel : INumericIdentityAggregateDataAccessModel, IAggregateDataAccessModel + where TDomainModel : class, INumericIdentityAggregateDomainModel + { + } + + /// + /// Represents an object that models an aggregate data access entity and that is identified primarily by an + /// value. + /// + public interface INumericIdentityAggregateDataAccessModel : INumericIdentityDataAccessModel, IAggregateDataAccessModel + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess/INumericIdentityDataAccessModel.cs b/src/RapidField.SolidInstruments.DataAccess/INumericIdentityDataAccessModel.cs new file mode 100644 index 00000000..a094037d --- /dev/null +++ b/src/RapidField.SolidInstruments.DataAccess/INumericIdentityDataAccessModel.cs @@ -0,0 +1,28 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.Domain; +using System; + +namespace RapidField.SolidInstruments.DataAccess +{ + /// + /// Represents an object that models a data access entity and that is identified primarily by an value. + /// + /// + /// The type of the domain model to which the data access model is mapped. + /// + public interface INumericIdentityDataAccessModel : INumericIdentityDataAccessModel, IDataAccessModel + where TDomainModel : class, INumericIdentityDomainModel + { + } + + /// + /// Represents an object that models a data access entity and that is identified primarily by an value. + /// + public interface INumericIdentityDataAccessModel : IDataAccessModel, INumericIdentityModel + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess/INumericIdentityValueDataAccessModel.cs b/src/RapidField.SolidInstruments.DataAccess/INumericIdentityValueDataAccessModel.cs new file mode 100644 index 00000000..ab394f73 --- /dev/null +++ b/src/RapidField.SolidInstruments.DataAccess/INumericIdentityValueDataAccessModel.cs @@ -0,0 +1,29 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; +using System; + +namespace RapidField.SolidInstruments.DataAccess +{ + /// + /// Represents an object that models a value data access entity and that is identified primarily by an + /// value. + /// + /// + /// The type of the domain model to which the data access model is mapped. + /// + public interface INumericIdentityValueDataAccessModel : IGlobalIdentityValueDataAccessModel, IValueDataAccessModel + where TDomainModel : class, INumericIdentityValueDomainModel + { + } + + /// + /// Represents an object that models a value data access entity and that is identified primarily by an + /// value. + /// + public interface INumericIdentityValueDataAccessModel : INumericIdentityDataAccessModel, IValueDataAccessModel + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess/IReadOnlyDataAccessModelRepository.cs b/src/RapidField.SolidInstruments.DataAccess/IReadOnlyDataAccessModelRepository.cs new file mode 100644 index 00000000..7b73d324 --- /dev/null +++ b/src/RapidField.SolidInstruments.DataAccess/IReadOnlyDataAccessModelRepository.cs @@ -0,0 +1,44 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Linq; + +namespace RapidField.SolidInstruments.DataAccess +{ + /// + /// Performs read-only data access operations for a specified data access model type. + /// + /// + /// The type of the unique primary identifier for the data model. + /// + /// + /// The type of the data model. + /// + public interface IReadOnlyDataAccessModelRepository : IDataAccessModelRepository, IReadOnlyDataAccessRepository + where TIdentifier : IComparable, IComparable, IEquatable + where TDataAccessModel : class, IDataAccessModel + { + /// + /// Returns the data model matching the specified identifier from the current + /// , or if no model + /// matching is found. + /// + /// + /// The unique primary identifier for the data model. + /// + /// + /// The data model matching the specified identifier within the current + /// , or if no model + /// matching is found. + /// + /// + /// The result set contains more than one entity. + /// + /// + /// The object is disposed. + /// + public TDataAccessModel FindByIdentifier(TIdentifier identifier) => FindWhere(entity => entity.Identifier.Equals(identifier)).SingleOrDefault(); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess/IReadOnlyDomainModelRepository.cs b/src/RapidField.SolidInstruments.DataAccess/IReadOnlyDomainModelRepository.cs new file mode 100644 index 00000000..b30c4d3f --- /dev/null +++ b/src/RapidField.SolidInstruments.DataAccess/IReadOnlyDomainModelRepository.cs @@ -0,0 +1,137 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Core.Domain; +using System; +using System.Diagnostics; +using System.Linq; +using System.Linq.Expressions; + +namespace RapidField.SolidInstruments.DataAccess +{ + /// + /// Performs read-only data access operations for a specified domain model type. + /// + /// + /// The type of the unique primary identifier for the data model. + /// + /// + /// The type of the data model. + /// + /// + /// The type of the domain model to which the data access model is mapped. + /// + public interface IReadOnlyDomainModelRepository : IDomainModelRepository, IReadOnlyDataAccessModelRepository + where TIdentifier : IComparable, IComparable, IEquatable + where TDomainModel : class, IDomainModel + where TDataAccessModel : class, IDataAccessModel, new() + { + /// + /// Determines whether or not the specified domain model exists in the current + /// . + /// + /// + /// The domain model to evaluate. + /// + /// + /// if the specified domain model exists in the current + /// , otherwise + /// . + /// + /// + /// The state of is invalid. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public Boolean ContainsDomainModel(TDomainModel domainModel) + { + _ = domainModel.RejectIf().IsNull(nameof(domainModel)); + var dataAccessModel = new TDataAccessModel(); + + try + { + dataAccessModel.HydrateFromDomainModel(domainModel); + } + catch (ArgumentException) + { + throw; + } + catch (Exception exception) + { + throw new ArgumentException("The state of the specified domain model is invalid.", nameof(domainModel), exception); + } + + return Contains(dataAccessModel); + } + + /// + /// Returns the domain model matching the specified identifier from the current + /// , or + /// if no model matching is found. + /// + /// + /// The unique primary identifier for the domain model. + /// + /// + /// The domain model matching the specified identifier within the current + /// , or + /// if no model matching is found. + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised while converting the data access model to its equivalent domain model. + /// + public TDomainModel FindDomainModelByIdentifier(TIdentifier identifier) => ConvertToDomainModel(FindByIdentifier(identifier)); + + /// + /// Returns all domain models matching the specified predicate from the current + /// . + /// + /// + /// An expression to test each entity for a condition. + /// + /// + /// All domain models matching the specified predicate within the current + /// . + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// An exception was raised while converting one or more data access models to their equivalent domain model(s). + /// + public IQueryable FindDomainModelsWhere(Expression> predicate) => FindWhere(predicate).Select(dataAccesModel => ConvertToDomainModel(dataAccesModel)); + + /// + /// Converts the specified data access model to its domain model equivalent. + /// + /// + /// The data access model to convert. + /// + /// + /// The resulting domain model. + /// + /// + /// An exception was raised while converting the data access model to its equivalent domain model. + /// + [DebuggerHidden] + private TDomainModel ConvertToDomainModel(TDataAccessModel dataAccessModel) => ConvertToDomainModel(dataAccessModel); + + /// + /// Gets the domain model type of the current + /// . + /// + public Type DomainModelType => typeof(TDomainModel); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess/ISemanticIdentityAggregateDataAccessModel.cs b/src/RapidField.SolidInstruments.DataAccess/ISemanticIdentityAggregateDataAccessModel.cs new file mode 100644 index 00000000..80142a5b --- /dev/null +++ b/src/RapidField.SolidInstruments.DataAccess/ISemanticIdentityAggregateDataAccessModel.cs @@ -0,0 +1,29 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; +using System; + +namespace RapidField.SolidInstruments.DataAccess +{ + /// + /// Represents an object that models an aggregate data access entity and that is identified primarily by a + /// value. + /// + /// + /// The type of the domain model to which the data access model is mapped. + /// + public interface ISemanticIdentityAggregateDataAccessModel : ISemanticIdentityAggregateDataAccessModel, IAggregateDataAccessModel + where TDomainModel : class, ISemanticIdentityAggregateDomainModel + { + } + + /// + /// Represents an object that models an aggregate data access entity and that is identified primarily by a + /// value. + /// + public interface ISemanticIdentityAggregateDataAccessModel : ISemanticIdentityDataAccessModel, IAggregateDataAccessModel + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess/ISemanticIdentityDataAccessModel.cs b/src/RapidField.SolidInstruments.DataAccess/ISemanticIdentityDataAccessModel.cs new file mode 100644 index 00000000..57e1b233 --- /dev/null +++ b/src/RapidField.SolidInstruments.DataAccess/ISemanticIdentityDataAccessModel.cs @@ -0,0 +1,28 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.Domain; +using System; + +namespace RapidField.SolidInstruments.DataAccess +{ + /// + /// Represents an object that models a data access entity and that is identified primarily by a value. + /// + /// + /// The type of the domain model to which the data access model is mapped. + /// + public interface ISemanticIdentityDataAccessModel : ISemanticIdentityDataAccessModel, IDataAccessModel + where TDomainModel : class, ISemanticIdentityDomainModel + { + } + + /// + /// Represents an object that models a data access entity and that is identified primarily by a value. + /// + public interface ISemanticIdentityDataAccessModel : IDataAccessModel, ISemanticIdentityModel + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess/ISemanticIdentityValueDataAccessModel.cs b/src/RapidField.SolidInstruments.DataAccess/ISemanticIdentityValueDataAccessModel.cs new file mode 100644 index 00000000..5e4090d3 --- /dev/null +++ b/src/RapidField.SolidInstruments.DataAccess/ISemanticIdentityValueDataAccessModel.cs @@ -0,0 +1,29 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; +using System; + +namespace RapidField.SolidInstruments.DataAccess +{ + /// + /// Represents an object that models a value data access entity and that is identified primarily by a + /// value. + /// + /// + /// The type of the domain model to which the data access model is mapped. + /// + public interface ISemanticIdentityValueDataAccessModel : IGlobalIdentityValueDataAccessModel, IValueDataAccessModel + where TDomainModel : class, ISemanticIdentityValueDomainModel + { + } + + /// + /// Represents an object that models a value data access entity and that is identified primarily by a + /// value. + /// + public interface ISemanticIdentityValueDataAccessModel : ISemanticIdentityDataAccessModel, IValueDataAccessModel + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess/IValueDataAccessModel.cs b/src/RapidField.SolidInstruments.DataAccess/IValueDataAccessModel.cs new file mode 100644 index 00000000..2c460a27 --- /dev/null +++ b/src/RapidField.SolidInstruments.DataAccess/IValueDataAccessModel.cs @@ -0,0 +1,42 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; +using System; + +namespace RapidField.SolidInstruments.DataAccess +{ + /// + /// Represents an object that models a data access entity as a domain value. + /// + /// + /// The type of the unique primary identifier for the model. + /// + /// + /// The type of the domain model to which the data access model is mapped. + /// + public interface IValueDataAccessModel : IValueDataAccessModel, IDataAccessModel + where TIdentifier : IComparable, IComparable, IEquatable + where TDomainModel : class, IDomainModel + { + } + + /// + /// Represents an object that models a data access entity as a domain value. + /// + /// + /// The type of the unique primary identifier for the model. + /// + public interface IValueDataAccessModel : IValueDataAccessModel, IDataAccessModel + where TIdentifier : IComparable, IComparable, IEquatable + { + } + + /// + /// Represents an object that models a data access entity as a domain value. + /// + public interface IValueDataAccessModel : IDataAccessModel + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess/RapidField.SolidInstruments.DataAccess.csproj b/src/RapidField.SolidInstruments.DataAccess/RapidField.SolidInstruments.DataAccess.csproj index b7a45593..50ddbd0f 100644 --- a/src/RapidField.SolidInstruments.DataAccess/RapidField.SolidInstruments.DataAccess.csproj +++ b/src/RapidField.SolidInstruments.DataAccess/RapidField.SolidInstruments.DataAccess.csproj @@ -36,8 +36,6 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - From 805b858e004b3a32c752f124fb77650e1f0a32c8 Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Tue, 21 Jul 2020 02:44:08 -0500 Subject: [PATCH 45/55] Finalize in-memory messaging service testing. --- .editorconfig | 5 +- .../Startup.cs | 2 +- .../AssemblyAttributes.cs | 6 +- .../Command.cs | 83 ++++++- .../CommandHandler.cs | 2 +- .../CreateDomainModelCommand.cs | 128 ++++++++++ .../CreateDomainModelCommandHandler.cs | 83 +++++++ .../DeleteDomainModelCommand.cs | 128 ++++++++++ .../DeleteDomainModelCommandHandler.cs | 83 +++++++ .../DomainCommand.cs | 176 ++++++++++++++ .../DomainCommandHandler.cs | 77 +++++++ .../DomainModelAssociatedCommand.cs | 128 ++++++++++ .../DomainModelAssociatedCommandHandler.cs | 46 ++++ .../DomainModelCommand.cs | 177 ++++++++++++++ .../DomainModelCommandHandler.cs | 46 ++++ .../ICommandBase.cs | 9 + .../ICreateDomainModelCommand.cs | 19 ++ .../IDeleteDomainModelCommand.cs | 19 ++ .../IDomainCommand.cs | 2 +- .../IDomainModelAssociatedCommand.cs | 19 ++ .../IDomainModelCommand.cs | 50 ++++ .../IUpdateDomainModelCommand.cs | 19 ++ .../UpdateDomainModelCommand.cs | 128 ++++++++++ .../UpdateDomainModelCommandHandler.cs | 83 +++++++ .../ArgumentValidation/ValidationResult.cs | 2 +- .../AssemblyAttributes.cs | 1 + .../Concurrency/ConcurrencyControl.cs | 9 + .../Concurrency/ConcurrencyControlToken.cs | 4 +- .../ReferenceManager.cs | 4 +- .../SemanticVersion.cs | 4 +- .../TimeOfDay.cs | 8 +- .../DataAccessCommand.cs | 4 +- .../ApplicationStateEvent.cs | 133 +++++++++++ .../DomainEvent.cs | 120 ++++++++++ .../DomainEventHandler.cs | 42 ++++ .../DomainModelAssociatedEvent.cs | 88 +++++++ .../DomainModelAssociatedEventHandler.cs | 47 ++++ .../DomainModelCreatedEvent.cs | 88 +++++++ .../DomainModelCreatedEventHandler.cs | 84 +++++++ .../DomainModelDeletedEvent.cs | 88 +++++++ .../DomainModelDeletedEventHandler.cs | 84 +++++++ .../DomainModelEvent.cs | 136 +++++++++++ .../DomainModelEventHandler.cs | 47 ++++ .../DomainModelUpdatedEvent.cs | 88 +++++++ .../DomainModelUpdatedEventHandler.cs | 84 +++++++ .../ErrorEvent.cs | 218 ++++++++++++++++++ .../Event.cs | 126 +++++++++- .../EventHandler.cs | 42 ++++ .../ExceptionRaisedEvent.cs | 131 +++++++++++ .../GeneralInformationEvent.cs | 121 ++++++++++ .../IDomainModelEvent.cs | 18 +- .../IEvent.cs | 3 +- ...eld.SolidInstruments.EventAuthoring.csproj | 1 + .../SecurityEvent.cs | 117 +++++++++- .../SystemStateEvent.cs | 133 +++++++++++ .../TransactionEvent.cs | 112 +++++++++ .../UserActionEvent.cs | 112 +++++++++ .../Physics/Area.cs | 4 +- .../Physics/GeographicCoordinates.cs | 4 +- .../Physics/Length.cs | 4 +- .../Physics/Mass.cs | 4 +- .../Physics/Temperature.cs | 4 +- .../Physics/Volume.cs | 4 +- .../InMemoryClientFactory.cs | 41 ++-- .../CommandMessages/CommandMessage.cs | 59 +++++ .../CommandMessages/CommandMessageListener.cs | 62 +++++ .../CommandMessageTransmitter.cs | 65 ++++++ .../CreateDomainModelCommandMessage.cs | 54 +++++ .../DeleteDomainModelCommandMessage.cs | 54 +++++ .../CommandMessages/DomainCommandMessage.cs | 49 ++++ .../DomainModelAssociatedCommandMessage.cs | 54 +++++ .../DomainModelCommandMessage.cs | 60 +++++ .../CommandMessages/ICommandMessage.cs | 34 +++ .../ICreateDomainModelCommandMessage.cs | 24 ++ .../IDeleteDomainModelCommandMessage.cs | 24 ++ .../CommandMessages/IDomainCommandMessage.cs | 19 ++ .../IDomainModelAssociatedCommandMessage.cs | 24 ++ .../IDomainModelCommandMessage.cs | 31 +++ .../IUpdateDomainModelCommandMessage.cs | 24 ++ .../UpdateDomainModelCommandMessage.cs | 54 +++++ .../ApplicationPausedEventMessage.cs | 92 -------- .../ApplicationResumedEventMessage.cs | 92 -------- .../ApplicationStartedEventMessage.cs | 92 -------- .../ApplicationStateEventMessage.cs | 92 -------- .../ApplicationStoppedEventMessage.cs | 92 -------- .../EventMessages/DomainEventMessage.cs | 92 -------- .../DomainModelAssociatedEventMessage.cs | 46 ---- .../DomainModelCreatedEventMessage.cs | 46 ---- .../DomainModelDeletedEventMessage.cs | 46 ---- .../EventMessages/DomainModelEventMessage.cs | 46 ---- .../DomainModelUpdatedEventMessage.cs | 46 ---- .../EventMessages/ErrorEventMessage.cs | 92 -------- .../EventMessages/EventMessage.cs | 94 +------- .../EventMessages/EventMessageListener.cs | 63 +++++ .../EventMessages/EventMessageTransmitter.cs | 66 ++++++ .../ExceptionRaisedEventMessage.cs | 92 -------- .../GeneralInformationEventMessage.cs | 92 -------- .../EventMessages/SecurityEventMessage.cs | 92 -------- .../EventMessages/SystemStateEventMessage.cs | 92 -------- .../EventMessages/TransactionEventMessage.cs | 92 -------- .../EventMessages/UserActionEventMessage.cs | 92 -------- .../IMessageBase.cs | 9 - .../MessageListeningFacade.cs | 4 +- .../MessagingEntityPath.cs | 46 +++- .../Service/HeartbeatScheduleItem.cs | 4 +- .../Service/MessagingServiceExecutor.cs | 70 +++++- .../TransportPrimitives/MessageLockToken.cs | 4 +- .../TransportPrimitives/MessageQueueClient.cs | 8 +- .../MessageSubscriptionClient.cs | 8 +- .../TransportPrimitives/MessageTransport.cs | 4 +- .../MessageTransportConnection.cs | 70 +++--- .../MessagingEntityClient.cs | 95 +++++--- .../ObjectContainerDefinition.cs | 4 +- .../AssemblyAttributes.cs | 1 + .../ServiceExecutor.cs | 15 ++ .../EnhancedReadabilityGuid.cs | 4 +- .../SimulatedEntity.cs | 4 +- .../CreateDomainModelCommandHandler.cs | 64 +++++ .../DeleteDomainModelCommandHandler.cs | 64 +++++ .../UpdateDomainModelCommandHandler.cs | 64 +++++ .../CreateDomainModelCommandHandler.cs | 64 +++++ .../DeleteDomainModelCommandHandler.cs | 64 +++++ .../UpdateDomainModelCommandHandler.cs | 64 +++++ .../CreateDomainModelCommandHandler.cs | 64 +++++ .../DeleteDomainModelCommandHandler.cs | 64 +++++ .../UpdateDomainModelCommandHandler.cs | 64 +++++ .../Customer/CreateDomainModelCommand.cs | 112 ++++++++- .../Customer/DeleteDomainModelCommand.cs | 121 ++++++++++ .../Customer/UpdateDomainModelCommand.cs | 121 ++++++++++ .../CustomerOrder/CreateDomainModelCommand.cs | 121 ++++++++++ .../CustomerOrder/DeleteDomainModelCommand.cs | 121 ++++++++++ .../CustomerOrder/UpdateDomainModelCommand.cs | 121 ++++++++++ .../Product/CreateDomainModelCommand.cs | 121 ++++++++++ .../Product/DeleteDomainModelCommand.cs | 121 ++++++++++ .../Product/UpdateDomainModelCommand.cs | 121 ++++++++++ .../DomainModelCreatedEventHandler.cs | 63 +++++ .../DomainModelDeletedEventHandler.cs | 74 ++++++ .../DomainModelUpdatedEventHandler.cs | 75 ++++++ .../DomainModelCreatedEventHandler.cs | 63 +++++ .../DomainModelDeletedEventHandler.cs | 74 ++++++ .../DomainModelUpdatedEventHandler.cs | 75 ++++++ .../Product/DomainModelCreatedEventHandler.cs | 63 +++++ .../Product/DomainModelDeletedEventHandler.cs | 74 ++++++ .../Product/DomainModelUpdatedEventHandler.cs | 75 ++++++ .../Customer/DomainModelCreatedEvent.cs | 88 +++++++ .../Customer/DomainModelDeletedEvent.cs | 88 +++++++ .../Customer/DomainModelUpdatedEvent.cs | 88 +++++++ .../CustomerOrder/DomainModelCreatedEvent.cs | 88 +++++++ .../CustomerOrder/DomainModelDeletedEvent.cs | 88 +++++++ .../CustomerOrder/DomainModelUpdatedEvent.cs | 88 +++++++ .../Product/DomainModelCreatedEvent.cs | 88 +++++++ .../Product/DomainModelDeletedEvent.cs | 88 +++++++ .../Product/DomainModelUpdatedEvent.cs | 88 +++++++ .../InMemoryClientFactoryTests.cs | 145 +++++++++++- .../CreateDomainModelCommandMessage.cs | 44 ++++ .../DeleteDomainModelCommandMessage.cs | 44 ++++ .../UpdateDomainModelCommandMessage.cs | 44 ++++ .../CreateDomainModelCommandMessage.cs | 44 ++++ .../DeleteDomainModelCommandMessage.cs | 44 ++++ .../UpdateDomainModelCommandMessage.cs | 44 ++++ .../CreateDomainModelCommandMessage.cs | 44 ++++ .../DeleteDomainModelCommandMessage.cs | 44 ++++ .../UpdateDomainModelCommandMessage.cs | 44 ++++ .../DomainModelCreatedEventMessage.cs | 24 +- .../DomainModelDeletedEventMessage.cs | 24 +- .../DomainModelUpdatedEventMessage.cs | 24 +- .../DomainModelCreatedEventMessage.cs | 24 +- .../DomainModelDeletedEventMessage.cs | 24 +- .../DomainModelUpdatedEventMessage.cs | 24 +- .../Product/DomainModelCreatedEventMessage.cs | 24 +- .../Product/DomainModelDeletedEventMessage.cs | 24 +- .../Product/DomainModelUpdatedEventMessage.cs | 24 +- ...uments.Messaging.InMemory.UnitTests.csproj | 7 +- .../SimulatedDependencyModule.cs | 134 +++++++++++ .../SimulatedDependencyPackage.cs | 41 ++++ .../SimulatedMessagingServiceExecutor.cs | 173 ++++++++++++++ .../SimulatedMessagingServiceExecutorTests.cs | 63 +++++ .../SimulatedServiceState.cs | 18 ++ .../SimulatedObject.cs | 4 +- .../SimulatedObject.cs | 4 +- 180 files changed, 8954 insertions(+), 1903 deletions(-) create mode 100644 src/RapidField.SolidInstruments.Command/CreateDomainModelCommand.cs create mode 100644 src/RapidField.SolidInstruments.Command/CreateDomainModelCommandHandler.cs create mode 100644 src/RapidField.SolidInstruments.Command/DeleteDomainModelCommand.cs create mode 100644 src/RapidField.SolidInstruments.Command/DeleteDomainModelCommandHandler.cs create mode 100644 src/RapidField.SolidInstruments.Command/DomainCommand.cs create mode 100644 src/RapidField.SolidInstruments.Command/DomainCommandHandler.cs create mode 100644 src/RapidField.SolidInstruments.Command/DomainModelAssociatedCommand.cs create mode 100644 src/RapidField.SolidInstruments.Command/DomainModelAssociatedCommandHandler.cs create mode 100644 src/RapidField.SolidInstruments.Command/DomainModelCommand.cs create mode 100644 src/RapidField.SolidInstruments.Command/DomainModelCommandHandler.cs create mode 100644 src/RapidField.SolidInstruments.Command/ICreateDomainModelCommand.cs create mode 100644 src/RapidField.SolidInstruments.Command/IDeleteDomainModelCommand.cs create mode 100644 src/RapidField.SolidInstruments.Command/IDomainModelAssociatedCommand.cs create mode 100644 src/RapidField.SolidInstruments.Command/IDomainModelCommand.cs create mode 100644 src/RapidField.SolidInstruments.Command/IUpdateDomainModelCommand.cs create mode 100644 src/RapidField.SolidInstruments.Command/UpdateDomainModelCommand.cs create mode 100644 src/RapidField.SolidInstruments.Command/UpdateDomainModelCommandHandler.cs create mode 100644 src/RapidField.SolidInstruments.EventAuthoring/DomainEventHandler.cs create mode 100644 src/RapidField.SolidInstruments.EventAuthoring/DomainModelAssociatedEventHandler.cs create mode 100644 src/RapidField.SolidInstruments.EventAuthoring/DomainModelCreatedEventHandler.cs create mode 100644 src/RapidField.SolidInstruments.EventAuthoring/DomainModelDeletedEventHandler.cs create mode 100644 src/RapidField.SolidInstruments.EventAuthoring/DomainModelEventHandler.cs create mode 100644 src/RapidField.SolidInstruments.EventAuthoring/DomainModelUpdatedEventHandler.cs create mode 100644 src/RapidField.SolidInstruments.EventAuthoring/EventHandler.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/CommandMessages/CommandMessage.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/CommandMessages/CommandMessageListener.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/CommandMessages/CommandMessageTransmitter.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/CommandMessages/CreateDomainModelCommandMessage.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/CommandMessages/DeleteDomainModelCommandMessage.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/CommandMessages/DomainCommandMessage.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/CommandMessages/DomainModelAssociatedCommandMessage.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/CommandMessages/DomainModelCommandMessage.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/CommandMessages/ICommandMessage.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/CommandMessages/ICreateDomainModelCommandMessage.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/CommandMessages/IDeleteDomainModelCommandMessage.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/CommandMessages/IDomainCommandMessage.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/CommandMessages/IDomainModelAssociatedCommandMessage.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/CommandMessages/IDomainModelCommandMessage.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/CommandMessages/IUpdateDomainModelCommandMessage.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/CommandMessages/UpdateDomainModelCommandMessage.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/EventMessages/EventMessageListener.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/EventMessages/EventMessageTransmitter.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/CommandHandlers/ModelState/Customer/CreateDomainModelCommandHandler.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/CommandHandlers/ModelState/Customer/DeleteDomainModelCommandHandler.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/CommandHandlers/ModelState/Customer/UpdateDomainModelCommandHandler.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/CommandHandlers/ModelState/CustomerOrder/CreateDomainModelCommandHandler.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/CommandHandlers/ModelState/CustomerOrder/DeleteDomainModelCommandHandler.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/CommandHandlers/ModelState/CustomerOrder/UpdateDomainModelCommandHandler.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/CommandHandlers/ModelState/Product/CreateDomainModelCommandHandler.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/CommandHandlers/ModelState/Product/DeleteDomainModelCommandHandler.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/CommandHandlers/ModelState/Product/UpdateDomainModelCommandHandler.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Customer/DeleteDomainModelCommand.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Customer/UpdateDomainModelCommand.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/CustomerOrder/CreateDomainModelCommand.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/CustomerOrder/DeleteDomainModelCommand.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/CustomerOrder/UpdateDomainModelCommand.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Product/CreateDomainModelCommand.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Product/DeleteDomainModelCommand.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Product/UpdateDomainModelCommand.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/EventHandlers/ModelState/Customer/DomainModelCreatedEventHandler.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/EventHandlers/ModelState/Customer/DomainModelDeletedEventHandler.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/EventHandlers/ModelState/Customer/DomainModelUpdatedEventHandler.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/EventHandlers/ModelState/CustomerOrder/DomainModelCreatedEventHandler.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/EventHandlers/ModelState/CustomerOrder/DomainModelDeletedEventHandler.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/EventHandlers/ModelState/CustomerOrder/DomainModelUpdatedEventHandler.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/EventHandlers/ModelState/Product/DomainModelCreatedEventHandler.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/EventHandlers/ModelState/Product/DomainModelDeletedEventHandler.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/EventHandlers/ModelState/Product/DomainModelUpdatedEventHandler.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Customer/CreateDomainModelCommandMessage.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Customer/DeleteDomainModelCommandMessage.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Customer/UpdateDomainModelCommandMessage.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/CustomerOrder/CreateDomainModelCommandMessage.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/CustomerOrder/DeleteDomainModelCommandMessage.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/CustomerOrder/UpdateDomainModelCommandMessage.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Product/CreateDomainModelCommandMessage.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Product/DeleteDomainModelCommandMessage.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Product/UpdateDomainModelCommandMessage.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/SimulatedDependencyModule.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/SimulatedDependencyPackage.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/SimulatedMessagingServiceExecutor.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/SimulatedMessagingServiceExecutorTests.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/SimulatedServiceState.cs diff --git a/.editorconfig b/.editorconfig index 7cfccc1b..5325ea92 100644 --- a/.editorconfig +++ b/.editorconfig @@ -228,4 +228,7 @@ dotnet_diagnostic.IDE0052.severity = silent dotnet_diagnostic.CA1027.severity = silent # CA1031: Do not catch general exception types -dotnet_diagnostic.CA1031.severity = silent \ No newline at end of file +dotnet_diagnostic.CA1031.severity = silent + +# IDE0075: Simplify conditional expression +dotnet_style_prefer_simplified_boolean_expressions = false : none \ No newline at end of file diff --git a/example/RapidField.SolidInstruments.Example.WebApplication/Startup.cs b/example/RapidField.SolidInstruments.Example.WebApplication/Startup.cs index 65613cd4..6a498f26 100644 --- a/example/RapidField.SolidInstruments.Example.WebApplication/Startup.cs +++ b/example/RapidField.SolidInstruments.Example.WebApplication/Startup.cs @@ -52,7 +52,7 @@ public static void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); - app.UseEndpoints(endpoints => { endpoints.MapRazorPages(); }); + app.UseEndpoints(endpoints => endpoints.MapRazorPages()); } /// diff --git a/src/RapidField.SolidInstruments.Command/AssemblyAttributes.cs b/src/RapidField.SolidInstruments.Command/AssemblyAttributes.cs index ddfd1208..c7affd2a 100644 --- a/src/RapidField.SolidInstruments.Command/AssemblyAttributes.cs +++ b/src/RapidField.SolidInstruments.Command/AssemblyAttributes.cs @@ -6,4 +6,8 @@ [assembly: DisablePrivateReflection()] [assembly: InternalsVisibleTo("RapidField.SolidInstruments.Command.UnitTests")] -[assembly: InternalsVisibleTo("RapidField.SolidInstruments.InversionOfControl.Autofac")] \ No newline at end of file +[assembly: InternalsVisibleTo("RapidField.SolidInstruments.EventAuthoring")] +[assembly: InternalsVisibleTo("RapidField.SolidInstruments.InversionOfControl")] +[assembly: InternalsVisibleTo("RapidField.SolidInstruments.InversionOfControl.Autofac")] +[assembly: InternalsVisibleTo("RapidField.SolidInstruments.InversionOfControl.DotNetNative")] +[assembly: InternalsVisibleTo("RapidField.SolidInstruments.Messaging")] \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/Command.cs b/src/RapidField.SolidInstruments.Command/Command.cs index d85f2710..4db5b147 100644 --- a/src/RapidField.SolidInstruments.Command/Command.cs +++ b/src/RapidField.SolidInstruments.Command/Command.cs @@ -3,6 +3,7 @@ // ================================================================================================================================= using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; using System; using System.Diagnostics; using System.Runtime.Serialization; @@ -26,7 +27,21 @@ public abstract class Command : ICommand /// protected Command() { - return; + CorrelationIdentifierField = null; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is equal to . + /// + protected Command(Guid correlationIdentifier) + { + CorrelationIdentifierField = correlationIdentifier.RejectIf().IsEqualToValue(Guid.Empty, nameof(correlationIdentifier)); } /// @@ -37,12 +52,37 @@ protected Command() /// public override String ToString() => $"{{ \"{nameof(ResultType)}\": \"{ResultType.FullName}\" }}"; + /// + /// Gets or sets a unique identifier that is assigned to related commands. + /// + [DataMember] + public Guid CorrelationIdentifier + { + get + { + if (CorrelationIdentifierField.HasValue == false) + { + CorrelationIdentifierField = Guid.NewGuid(); + } + + return CorrelationIdentifierField.Value; + } + set => CorrelationIdentifierField = value; + } + /// /// Gets the type of the result that is emitted when processing the command. /// [IgnoreDataMember] public Type ResultType => ResultTypeReference; + /// + /// Represents a unique identifier that is assigned to related commands. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + [IgnoreDataMember] + internal Guid? CorrelationIdentifierField; + /// /// Represents the type of the result that is emitted when processing the command. /// @@ -64,7 +104,21 @@ public abstract class Command : ICommand /// protected Command() { - return; + CorrelationIdentifierField = null; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is equal to . + /// + protected Command(Guid correlationIdentifier) + { + CorrelationIdentifierField = correlationIdentifier.RejectIf().IsEqualToValue(Guid.Empty, nameof(correlationIdentifier)); } /// @@ -75,10 +129,35 @@ protected Command() /// public override String ToString() => $"{{ \"{nameof(ResultType)}\": \"{ResultType.FullName}\" }}"; + /// + /// Gets or sets a unique identifier that is assigned to related commands. + /// + [DataMember] + public Guid CorrelationIdentifier + { + get + { + if (CorrelationIdentifierField.HasValue == false) + { + CorrelationIdentifierField = Guid.NewGuid(); + } + + return CorrelationIdentifierField.Value; + } + set => CorrelationIdentifierField = value; + } + /// /// Gets the type of the result that is emitted when processing the command. /// [IgnoreDataMember] public virtual Type ResultType => Nix.Type; + + /// + /// Represents a unique identifier that is assigned to related commands. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + [IgnoreDataMember] + private Guid? CorrelationIdentifierField; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/CommandHandler.cs b/src/RapidField.SolidInstruments.Command/CommandHandler.cs index 64e791d2..7f6ac856 100644 --- a/src/RapidField.SolidInstruments.Command/CommandHandler.cs +++ b/src/RapidField.SolidInstruments.Command/CommandHandler.cs @@ -74,7 +74,7 @@ public Nix Process(TCommand command) } /// - /// Releases all resources consumed by the current . + /// Releases all resources consumed by the current . /// /// /// A value indicating whether or not managed resources should be released. diff --git a/src/RapidField.SolidInstruments.Command/CreateDomainModelCommand.cs b/src/RapidField.SolidInstruments.Command/CreateDomainModelCommand.cs new file mode 100644 index 00000000..4fe32d45 --- /dev/null +++ b/src/RapidField.SolidInstruments.Command/CreateDomainModelCommand.cs @@ -0,0 +1,128 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace RapidField.SolidInstruments.Command +{ + /// + /// Represents a command to create an object that models a domain construct. + /// + /// + /// is the default implementation of + /// . + /// + /// + /// The type of the associated domain model. + /// + [DataContract] + public class CreateDomainModelCommand : DomainModelCommand, ICreateDomainModelCommand + where TModel : class, IDomainModel + { + /// + /// Initializes a new instance of the class. + /// + public CreateDomainModelCommand() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is equal to . + /// + public CreateDomainModelCommand(Guid correlationIdentifier) + : base(correlationIdentifier) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// is . + /// + public CreateDomainModelCommand(TModel model) + : this(model, Array.Empty()) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public CreateDomainModelCommand(TModel model, Guid correlationIdentifier) + : this(model, Array.Empty(), correlationIdentifier) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// is -or- is . + /// + public CreateDomainModelCommand(TModel model, IEnumerable labels) + : base(model, DomainModelCommandClassification.Created, labels) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public CreateDomainModelCommand(TModel model, IEnumerable labels, Guid correlationIdentifier) + : base(model, DomainModelCommandClassification.Created, labels, correlationIdentifier) + { + return; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/CreateDomainModelCommandHandler.cs b/src/RapidField.SolidInstruments.Command/CreateDomainModelCommandHandler.cs new file mode 100644 index 00000000..856773d1 --- /dev/null +++ b/src/RapidField.SolidInstruments.Command/CreateDomainModelCommandHandler.cs @@ -0,0 +1,83 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Concurrency; +using RapidField.SolidInstruments.Core.Domain; +using System; +using System.Collections.Generic; + +namespace RapidField.SolidInstruments.Command +{ + /// + /// Processes a single . + /// + /// + /// The type of the associated domain model. + /// + /// + /// The type of the command that is processed by the handler. + /// + public abstract class CreateDomainModelCommandHandler : DomainModelCommandHandler + where TModel : class, IDomainModel + where TCommand : class, ICreateDomainModelCommand + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// is . + /// + protected CreateDomainModelCommandHandler(ICommandMediator mediator) + : base(mediator) + { + return; + } + + /// + /// Creates the specified domain model. + /// + /// + /// The model to create. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// A unique identifier to assign to sub-commands. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + protected abstract void CreateDomainModel(TModel model, IEnumerable labels, Guid correlationIdentifier, ICommandMediator mediator, IConcurrencyControlToken controlToken); + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Processes the specified command. + /// + /// + /// The command to process. + /// + /// + /// A processing intermediary that is used to process sub-commands. Do not process using + /// , as doing so will generally result in infinite-looping. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + protected sealed override void Process(TCommand command, ICommandMediator mediator, IConcurrencyControlToken controlToken) => CreateDomainModel(command.Model, command.Labels, command.CorrelationIdentifier, mediator, controlToken); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/DeleteDomainModelCommand.cs b/src/RapidField.SolidInstruments.Command/DeleteDomainModelCommand.cs new file mode 100644 index 00000000..d1ebe18d --- /dev/null +++ b/src/RapidField.SolidInstruments.Command/DeleteDomainModelCommand.cs @@ -0,0 +1,128 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace RapidField.SolidInstruments.Command +{ + /// + /// Represents a command to delete an object that models a domain construct. + /// + /// + /// is the default implementation of + /// . + /// + /// + /// The type of the associated domain model. + /// + [DataContract] + public class DeleteDomainModelCommand : DomainModelCommand, IDeleteDomainModelCommand + where TModel : class, IDomainModel + { + /// + /// Initializes a new instance of the class. + /// + public DeleteDomainModelCommand() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is equal to . + /// + public DeleteDomainModelCommand(Guid correlationIdentifier) + : base(correlationIdentifier) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// is . + /// + public DeleteDomainModelCommand(TModel model) + : this(model, Array.Empty()) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public DeleteDomainModelCommand(TModel model, Guid correlationIdentifier) + : this(model, Array.Empty(), correlationIdentifier) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// is -or- is . + /// + public DeleteDomainModelCommand(TModel model, IEnumerable labels) + : base(model, DomainModelCommandClassification.Deleted, labels) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public DeleteDomainModelCommand(TModel model, IEnumerable labels, Guid correlationIdentifier) + : base(model, DomainModelCommandClassification.Deleted, labels, correlationIdentifier) + { + return; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/DeleteDomainModelCommandHandler.cs b/src/RapidField.SolidInstruments.Command/DeleteDomainModelCommandHandler.cs new file mode 100644 index 00000000..f2b18a26 --- /dev/null +++ b/src/RapidField.SolidInstruments.Command/DeleteDomainModelCommandHandler.cs @@ -0,0 +1,83 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Concurrency; +using RapidField.SolidInstruments.Core.Domain; +using System; +using System.Collections.Generic; + +namespace RapidField.SolidInstruments.Command +{ + /// + /// Processes a single . + /// + /// + /// The type of the associated domain model. + /// + /// + /// The type of the command that is processed by the handler. + /// + public abstract class DeleteDomainModelCommandHandler : DomainModelCommandHandler + where TModel : class, IDomainModel + where TCommand : class, IDeleteDomainModelCommand + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// is . + /// + protected DeleteDomainModelCommandHandler(ICommandMediator mediator) + : base(mediator) + { + return; + } + + /// + /// Deletes the specified domain model. + /// + /// + /// The model to delete. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// A unique identifier to assign to sub-commands. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + protected abstract void DeleteDomainModel(TModel model, IEnumerable labels, Guid correlationIdentifier, ICommandMediator mediator, IConcurrencyControlToken controlToken); + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Processes the specified command. + /// + /// + /// The command to process. + /// + /// + /// A processing intermediary that is used to process sub-commands. Do not process using + /// , as doing so will generally result in infinite-looping. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + protected sealed override void Process(TCommand command, ICommandMediator mediator, IConcurrencyControlToken controlToken) => DeleteDomainModel(command.Model, command.Labels, command.CorrelationIdentifier, mediator, controlToken); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/DomainCommand.cs b/src/RapidField.SolidInstruments.Command/DomainCommand.cs new file mode 100644 index 00000000..113d4db6 --- /dev/null +++ b/src/RapidField.SolidInstruments.Command/DomainCommand.cs @@ -0,0 +1,176 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.ArgumentValidation; +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace RapidField.SolidInstruments.Command +{ + /// + /// Represents a command to perform a domain action. + /// + /// + /// is the default implementation of . + /// + [DataContract] + public class DomainCommand : Command, IDomainCommand + { + /// + /// Initializes a new instance of the class. + /// + public DomainCommand() + : this(Array.Empty()) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is equal to . + /// + public DomainCommand(Guid correlationIdentifier) + : this(Array.Empty(), correlationIdentifier) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// is . + /// + public DomainCommand(IEnumerable labels) + : base() + { + Labels = new List((labels.RejectIf().IsNull(nameof(labels)).TargetArgument)); + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public DomainCommand(IEnumerable labels, Guid correlationIdentifier) + : base(correlationIdentifier) + { + Labels = new List((labels.RejectIf().IsNull(nameof(labels)).TargetArgument)); + } + + /// + /// Gets or sets a collection of textual labels that provide categorical and/or contextual information about the current + /// . + /// + [DataMember] + public ICollection Labels + { + get; + set; + } + } + + /// + /// Represents a command to perform a domain action. + /// + /// + /// is the default implementation of . + /// + /// + /// The type of the result that is emitted when processing the command. + /// + [DataContract] + public class DomainCommand : Command, IDomainCommand + { + /// + /// Initializes a new instance of the class. + /// + public DomainCommand() + : this(Array.Empty()) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is equal to . + /// + public DomainCommand(Guid correlationIdentifier) + : this(Array.Empty(), correlationIdentifier) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// is . + /// + public DomainCommand(IEnumerable labels) + : base() + { + Labels = new List((labels.RejectIf().IsNull(nameof(labels)).TargetArgument)); + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public DomainCommand(IEnumerable labels, Guid correlationIdentifier) + : base(correlationIdentifier) + { + Labels = new List((labels.RejectIf().IsNull(nameof(labels)).TargetArgument)); + } + + /// + /// Gets or sets a collection of textual labels that provide categorical and/or contextual information about the current + /// . + /// + [DataMember] + public ICollection Labels + { + get; + set; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/DomainCommandHandler.cs b/src/RapidField.SolidInstruments.Command/DomainCommandHandler.cs new file mode 100644 index 00000000..6f99011a --- /dev/null +++ b/src/RapidField.SolidInstruments.Command/DomainCommandHandler.cs @@ -0,0 +1,77 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; + +namespace RapidField.SolidInstruments.Command +{ + /// + /// Processes a single . + /// + /// + /// The type of the command that is processed by the handler. + /// + public abstract class DomainCommandHandler : CommandHandler + where TCommand : class, IDomainCommand + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// is . + /// + protected DomainCommandHandler(ICommandMediator mediator) + : base(mediator) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + } + + /// + /// Processes a single . + /// + /// + /// The type of the command that is processed by the handler. + /// + /// + /// The type of the result that is emitted by the handler when processing a command. + /// + public abstract class DomainCommandHandler : CommandHandler + where TCommand : class, IDomainCommand + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// is . + /// + protected DomainCommandHandler(ICommandMediator mediator) + : base(mediator) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/DomainModelAssociatedCommand.cs b/src/RapidField.SolidInstruments.Command/DomainModelAssociatedCommand.cs new file mode 100644 index 00000000..31371c74 --- /dev/null +++ b/src/RapidField.SolidInstruments.Command/DomainModelAssociatedCommand.cs @@ -0,0 +1,128 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace RapidField.SolidInstruments.Command +{ + /// + /// Represents a command to perform an action related to an object that models a domain construct. + /// + /// + /// is the default implementation of + /// . + /// + /// + /// The type of the associated domain model. + /// + [DataContract] + public class DomainModelAssociatedCommand : DomainModelCommand, IDomainModelAssociatedCommand + where TModel : class, IDomainModel + { + /// + /// Initializes a new instance of the class. + /// + public DomainModelAssociatedCommand() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is equal to . + /// + public DomainModelAssociatedCommand(Guid correlationIdentifier) + : base(correlationIdentifier) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// is . + /// + public DomainModelAssociatedCommand(TModel model) + : this(model, Array.Empty()) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public DomainModelAssociatedCommand(TModel model, Guid correlationIdentifier) + : this(model, Array.Empty(), correlationIdentifier) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// is -or- is . + /// + public DomainModelAssociatedCommand(TModel model, IEnumerable labels) + : base(model, DomainModelCommandClassification.Associated, labels) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public DomainModelAssociatedCommand(TModel model, IEnumerable labels, Guid correlationIdentifier) + : base(model, DomainModelCommandClassification.Associated, labels, correlationIdentifier) + { + return; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/DomainModelAssociatedCommandHandler.cs b/src/RapidField.SolidInstruments.Command/DomainModelAssociatedCommandHandler.cs new file mode 100644 index 00000000..f3e4a84b --- /dev/null +++ b/src/RapidField.SolidInstruments.Command/DomainModelAssociatedCommandHandler.cs @@ -0,0 +1,46 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; +using System; + +namespace RapidField.SolidInstruments.Command +{ + /// + /// Processes a single . + /// + /// + /// The type of the associated domain model. + /// + /// + /// The type of the command that is processed by the handler. + /// + public abstract class DomainModelAssociatedCommandHandler : DomainModelCommandHandler + where TModel : class, IDomainModel + where TCommand : class, IDomainModelAssociatedCommand + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// is . + /// + protected DomainModelAssociatedCommandHandler(ICommandMediator mediator) + : base(mediator) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/DomainModelCommand.cs b/src/RapidField.SolidInstruments.Command/DomainModelCommand.cs new file mode 100644 index 00000000..eec75bcd --- /dev/null +++ b/src/RapidField.SolidInstruments.Command/DomainModelCommand.cs @@ -0,0 +1,177 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Core.Domain; +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace RapidField.SolidInstruments.Command +{ + /// + /// Represents a command to perform an action related to an object that models a domain construct. + /// + /// + /// is the default implementation of . + /// + /// + /// The type of the associated domain model. + /// + [DataContract] + public class DomainModelCommand : DomainCommand, IDomainModelCommand + where TModel : class, IDomainModel + { + /// + /// Initializes a new instance of the class. + /// + public DomainModelCommand() + : base() + { + Model = null; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is equal to . + /// + public DomainModelCommand(Guid correlationIdentifier) + : base(correlationIdentifier) + { + Model = null; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// A classification that describes the effect of a the event upon . + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public DomainModelCommand(TModel model, DomainModelCommandClassification classification) + : this(model, classification, Array.Empty()) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// A classification that describes the effect of a the event upon . + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is . + /// + /// + /// is equal to -or- + /// is equal to . + /// + public DomainModelCommand(TModel model, DomainModelCommandClassification classification, Guid correlationIdentifier) + : this(model, classification, Array.Empty(), correlationIdentifier) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// A classification that describes the effect of a the event upon . + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public DomainModelCommand(TModel model, DomainModelCommandClassification classification, IEnumerable labels) + : base(labels) + { + Classification = classification.RejectIf().IsEqualToValue(DomainModelCommandClassification.Unspecified, nameof(classification)); + Model = model.RejectIf().IsNull(nameof(model)); + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// A classification that describes the effect of a the event upon . + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is -or- is . + /// + /// + /// is equal to -or- + /// is equal to . + /// + public DomainModelCommand(TModel model, DomainModelCommandClassification classification, IEnumerable labels, Guid correlationIdentifier) + : base(labels, correlationIdentifier) + { + Classification = classification.RejectIf().IsEqualToValue(DomainModelCommandClassification.Unspecified, nameof(classification)); + Model = model.RejectIf().IsNull(nameof(model)); + } + + /// + /// Gets or sets a classification that describes the effect of a the current upon + /// . + /// + [DataMember] + public DomainModelCommandClassification Classification + { + get; + set; + } + + /// + /// Gets or sets the desired state of the associated domain model. + /// + [DataMember] + public TModel Model + { + get; + set; + } + + /// + /// Gets the type of the associated domain model. + /// + [IgnoreDataMember] + public Type ModelType => typeof(TModel); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/DomainModelCommandHandler.cs b/src/RapidField.SolidInstruments.Command/DomainModelCommandHandler.cs new file mode 100644 index 00000000..7f762fc5 --- /dev/null +++ b/src/RapidField.SolidInstruments.Command/DomainModelCommandHandler.cs @@ -0,0 +1,46 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; +using System; + +namespace RapidField.SolidInstruments.Command +{ + /// + /// Processes a single . + /// + /// + /// The type of the associated domain model. + /// + /// + /// The type of the command that is processed by the handler. + /// + public abstract class DomainModelCommandHandler : DomainCommandHandler + where TModel : class, IDomainModel + where TCommand : class, IDomainModelCommand + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// is . + /// + protected DomainModelCommandHandler(ICommandMediator mediator) + : base(mediator) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/ICommandBase.cs b/src/RapidField.SolidInstruments.Command/ICommandBase.cs index c33750fb..11cde183 100644 --- a/src/RapidField.SolidInstruments.Command/ICommandBase.cs +++ b/src/RapidField.SolidInstruments.Command/ICommandBase.cs @@ -11,6 +11,15 @@ namespace RapidField.SolidInstruments.Command /// public interface ICommandBase { + /// + /// Gets or sets a unique identifier that is assigned to related commands. + /// + public Guid CorrelationIdentifier + { + get; + set; + } + /// /// Gets the type of the result that is emitted when processing the command. /// diff --git a/src/RapidField.SolidInstruments.Command/ICreateDomainModelCommand.cs b/src/RapidField.SolidInstruments.Command/ICreateDomainModelCommand.cs new file mode 100644 index 00000000..f28e6e55 --- /dev/null +++ b/src/RapidField.SolidInstruments.Command/ICreateDomainModelCommand.cs @@ -0,0 +1,19 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; + +namespace RapidField.SolidInstruments.Command +{ + /// + /// Represents a command to create an object that models a domain construct. + /// + /// + /// The type of the associated domain model. + /// + public interface ICreateDomainModelCommand : IDomainModelCommand + where TModel : class, IDomainModel + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/IDeleteDomainModelCommand.cs b/src/RapidField.SolidInstruments.Command/IDeleteDomainModelCommand.cs new file mode 100644 index 00000000..86d9314b --- /dev/null +++ b/src/RapidField.SolidInstruments.Command/IDeleteDomainModelCommand.cs @@ -0,0 +1,19 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; + +namespace RapidField.SolidInstruments.Command +{ + /// + /// Represents a command to delete an object that models a domain construct. + /// + /// + /// The type of the associated domain model. + /// + public interface IDeleteDomainModelCommand : IDomainModelCommand + where TModel : class, IDomainModel + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/IDomainCommand.cs b/src/RapidField.SolidInstruments.Command/IDomainCommand.cs index 002ef135..7989c90b 100644 --- a/src/RapidField.SolidInstruments.Command/IDomainCommand.cs +++ b/src/RapidField.SolidInstruments.Command/IDomainCommand.cs @@ -17,7 +17,7 @@ public interface IDomainCommand : ILabeledCommand /// /// The type of the result that is emitted when processing the command. /// - public interface IDomainommand : ILabeledCommand + public interface IDomainCommand : ILabeledCommand { } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/IDomainModelAssociatedCommand.cs b/src/RapidField.SolidInstruments.Command/IDomainModelAssociatedCommand.cs new file mode 100644 index 00000000..20e62fb9 --- /dev/null +++ b/src/RapidField.SolidInstruments.Command/IDomainModelAssociatedCommand.cs @@ -0,0 +1,19 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; + +namespace RapidField.SolidInstruments.Command +{ + /// + /// Represents a command to perform an action related to an object that models a domain construct. + /// + /// + /// The type of the associated domain model. + /// + public interface IDomainModelAssociatedCommand : IDomainModelCommand + where TModel : class, IDomainModel + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/IDomainModelCommand.cs b/src/RapidField.SolidInstruments.Command/IDomainModelCommand.cs new file mode 100644 index 00000000..a63e6940 --- /dev/null +++ b/src/RapidField.SolidInstruments.Command/IDomainModelCommand.cs @@ -0,0 +1,50 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; +using System; + +namespace RapidField.SolidInstruments.Command +{ + /// + /// Represents a command to perform an action related to an object that models a domain construct. + /// + /// + /// The type of the associated domain model. + /// + public interface IDomainModelCommand : IDomainModelCommand + where TModel : class, IDomainModel + { + /// + /// Gets the desired state of the associated domain model. + /// + public TModel Model + { + get; + } + } + + /// + /// Represents a command to perform an action related to an object that models a domain construct. + /// + public interface IDomainModelCommand : IDomainCommand + { + /// + /// Gets a classification that describes the effect of a the current upon the associated + /// model. + /// + public DomainModelCommandClassification Classification + { + get; + } + + /// + /// Gets the type of the associated domain model. + /// + public Type ModelType + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/IUpdateDomainModelCommand.cs b/src/RapidField.SolidInstruments.Command/IUpdateDomainModelCommand.cs new file mode 100644 index 00000000..1640a086 --- /dev/null +++ b/src/RapidField.SolidInstruments.Command/IUpdateDomainModelCommand.cs @@ -0,0 +1,19 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; + +namespace RapidField.SolidInstruments.Command +{ + /// + /// Represents a command to update an object that models a domain construct. + /// + /// + /// The type of the associated domain model. + /// + public interface IUpdateDomainModelCommand : IDomainModelCommand + where TModel : class, IDomainModel + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/UpdateDomainModelCommand.cs b/src/RapidField.SolidInstruments.Command/UpdateDomainModelCommand.cs new file mode 100644 index 00000000..a51a4f46 --- /dev/null +++ b/src/RapidField.SolidInstruments.Command/UpdateDomainModelCommand.cs @@ -0,0 +1,128 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Domain; +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace RapidField.SolidInstruments.Command +{ + /// + /// Represents a command to update an object that models a domain construct. + /// + /// + /// is the default implementation of + /// . + /// + /// + /// The type of the associated domain model. + /// + [DataContract] + public class UpdateDomainModelCommand : DomainModelCommand, IUpdateDomainModelCommand + where TModel : class, IDomainModel + { + /// + /// Initializes a new instance of the class. + /// + public UpdateDomainModelCommand() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is equal to . + /// + public UpdateDomainModelCommand(Guid correlationIdentifier) + : base(correlationIdentifier) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// is . + /// + public UpdateDomainModelCommand(TModel model) + : this(model, Array.Empty()) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public UpdateDomainModelCommand(TModel model, Guid correlationIdentifier) + : this(model, Array.Empty(), correlationIdentifier) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// is -or- is . + /// + public UpdateDomainModelCommand(TModel model, IEnumerable labels) + : base(model, DomainModelCommandClassification.Updated, labels) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public UpdateDomainModelCommand(TModel model, IEnumerable labels, Guid correlationIdentifier) + : base(model, DomainModelCommandClassification.Updated, labels, correlationIdentifier) + { + return; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/UpdateDomainModelCommandHandler.cs b/src/RapidField.SolidInstruments.Command/UpdateDomainModelCommandHandler.cs new file mode 100644 index 00000000..6a309058 --- /dev/null +++ b/src/RapidField.SolidInstruments.Command/UpdateDomainModelCommandHandler.cs @@ -0,0 +1,83 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Concurrency; +using RapidField.SolidInstruments.Core.Domain; +using System; +using System.Collections.Generic; + +namespace RapidField.SolidInstruments.Command +{ + /// + /// Processes a single . + /// + /// + /// The type of the associated domain model. + /// + /// + /// The type of the command that is processed by the handler. + /// + public abstract class UpdateDomainModelCommandHandler : DomainModelCommandHandler + where TModel : class, IDomainModel + where TCommand : class, IUpdateDomainModelCommand + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// is . + /// + protected UpdateDomainModelCommandHandler(ICommandMediator mediator) + : base(mediator) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Processes the specified command. + /// + /// + /// The command to process. + /// + /// + /// A processing intermediary that is used to process sub-commands. Do not process using + /// , as doing so will generally result in infinite-looping. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + protected sealed override void Process(TCommand command, ICommandMediator mediator, IConcurrencyControlToken controlToken) => UpdateDomainModel(command.Model, command.Labels, command.CorrelationIdentifier, mediator, controlToken); + + /// + /// Updates the specified domain model. + /// + /// + /// The model to update. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// A unique identifier to assign to sub-commands. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + protected abstract void UpdateDomainModel(TModel model, IEnumerable labels, Guid correlationIdentifier, ICommandMediator mediator, IConcurrencyControlToken controlToken); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Core/ArgumentValidation/ValidationResult.cs b/src/RapidField.SolidInstruments.Core/ArgumentValidation/ValidationResult.cs index 49d365c8..19fdb0dd 100644 --- a/src/RapidField.SolidInstruments.Core/ArgumentValidation/ValidationResult.cs +++ b/src/RapidField.SolidInstruments.Core/ArgumentValidation/ValidationResult.cs @@ -34,7 +34,7 @@ internal ValidationResult(ValidationTarget target) /// /// The object to cast from. /// - public static implicit operator TArgument(ValidationResult target) => target.TargetArgument; + public static implicit operator TArgument(ValidationResult target) => target is null ? default : target.TargetArgument; /// /// Returns the validation target to facilitate chained argument validation. diff --git a/src/RapidField.SolidInstruments.Core/AssemblyAttributes.cs b/src/RapidField.SolidInstruments.Core/AssemblyAttributes.cs index bdd21ef0..bb5a51ec 100644 --- a/src/RapidField.SolidInstruments.Core/AssemblyAttributes.cs +++ b/src/RapidField.SolidInstruments.Core/AssemblyAttributes.cs @@ -7,4 +7,5 @@ [assembly: DisablePrivateReflection()] [assembly: InternalsVisibleTo("RapidField.SolidInstruments.Collections.UnitTests")] [assembly: InternalsVisibleTo("RapidField.SolidInstruments.Core.UnitTests")] +[assembly: InternalsVisibleTo("RapidField.SolidInstruments.Cryptography")] [assembly: InternalsVisibleTo("RapidField.SolidInstruments.ObjectComposition")] \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControl.cs b/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControl.cs index a708e175..c0c66c4d 100644 --- a/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControl.cs +++ b/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControl.cs @@ -178,6 +178,15 @@ public void Exit(IConcurrencyControlToken token) { return; } + +#if DEBUG + if (Debugger.IsAttached) + { + // Attaching a debugger during test execution breaks the expected behavior of Exit implementations. Leave this + // here as a conditional exception to the safeguard defined above. + return; + } +#endif } throw new ConcurrencyControlOperationException("The specified token is not valid for release by the control. It was issued by a different control or it was already released."); diff --git a/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControlToken.cs b/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControlToken.cs index 6198ec79..456f0e90 100644 --- a/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControlToken.cs +++ b/src/RapidField.SolidInstruments.Core/Concurrency/ConcurrencyControlToken.cs @@ -260,9 +260,9 @@ public override Boolean Equals(Object obj) { return false; } - else if (obj is IConcurrencyControlToken) + else if (obj is IConcurrencyControlToken token) { - return Equals((IConcurrencyControlToken)obj); + return Equals(token); } return false; diff --git a/src/RapidField.SolidInstruments.Core/ReferenceManager.cs b/src/RapidField.SolidInstruments.Core/ReferenceManager.cs index 5e06227f..6645100d 100644 --- a/src/RapidField.SolidInstruments.Core/ReferenceManager.cs +++ b/src/RapidField.SolidInstruments.Core/ReferenceManager.cs @@ -216,9 +216,9 @@ public override Boolean Equals(Object obj) { return false; } - else if (obj is ManagedReference) + else if (obj is ManagedReference reference) { - return Equals((ManagedReference)obj); + return Equals(reference); } return false; diff --git a/src/RapidField.SolidInstruments.Core/SemanticVersion.cs b/src/RapidField.SolidInstruments.Core/SemanticVersion.cs index 07ab16cc..cff3e9c1 100644 --- a/src/RapidField.SolidInstruments.Core/SemanticVersion.cs +++ b/src/RapidField.SolidInstruments.Core/SemanticVersion.cs @@ -429,9 +429,9 @@ public override Boolean Equals(Object obj) { return false; } - else if (obj is ISemanticVersion) + else if (obj is ISemanticVersion version) { - return Equals((ISemanticVersion)obj); + return Equals(version); } return false; diff --git a/src/RapidField.SolidInstruments.Core/TimeOfDay.cs b/src/RapidField.SolidInstruments.Core/TimeOfDay.cs index 6120ff77..f065268e 100644 --- a/src/RapidField.SolidInstruments.Core/TimeOfDay.cs +++ b/src/RapidField.SolidInstruments.Core/TimeOfDay.cs @@ -373,7 +373,7 @@ public Int32 CompareTo(TimeOfDay other) /// Negative one if this instance is earlier than the specified instance; one if this instance is later than the supplied /// instance; zero if they are equal. /// - public Int32 CompareTo(Object obj) => obj is TimeOfDay ? CompareTo((TimeOfDay)obj) : GetType().FullName.CompareTo(obj.GetType().FullName); + public Int32 CompareTo(Object obj) => obj is TimeOfDay timeOfDay ? CompareTo(timeOfDay) : GetType().FullName.CompareTo(obj.GetType().FullName); /// /// Determines whether or not the current is equal to the specified . @@ -390,9 +390,9 @@ public override Boolean Equals(Object obj) { return false; } - else if (obj is TimeOfDay) + else if (obj is TimeOfDay timeOfDay) { - return Equals((TimeOfDay)obj); + return Equals(timeOfDay); } return false; @@ -626,7 +626,7 @@ public override String ToString() break; } - return $"{twelveHourClockFormatHourValue.Value}:{Minute.ToString("00")}:{Second.ToString("00")}.{Millisecond.ToString("000")} {meridiemStringFragment} {Zone.Id}"; + return $"{twelveHourClockFormatHourValue.Value}:{Minute:00}:{Second:00}.{Millisecond:000} {meridiemStringFragment} {Zone.Id}"; } /// diff --git a/src/RapidField.SolidInstruments.DataAccess/DataAccessCommand.cs b/src/RapidField.SolidInstruments.DataAccess/DataAccessCommand.cs index 300b3a8e..fc05a953 100644 --- a/src/RapidField.SolidInstruments.DataAccess/DataAccessCommand.cs +++ b/src/RapidField.SolidInstruments.DataAccess/DataAccessCommand.cs @@ -6,7 +6,7 @@ using System; using System.Diagnostics; using System.Runtime.Serialization; -using CommandNamespace = RapidField.SolidInstruments.Command; +using SolidInstrumentsCommand = RapidField.SolidInstruments.Command.Command; namespace RapidField.SolidInstruments.DataAccess { @@ -51,7 +51,7 @@ protected DataAccessCommand() /// is the default implementation of . /// [DataContract] - public abstract class DataAccessCommand : CommandNamespace.Command, IDataAccessCommand + public abstract class DataAccessCommand : SolidInstrumentsCommand, IDataAccessCommand { /// /// Initializes a new instance of the class. diff --git a/src/RapidField.SolidInstruments.EventAuthoring/ApplicationStateEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/ApplicationStateEvent.cs index 106607b0..7ec7fa3d 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/ApplicationStateEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/ApplicationStateEvent.cs @@ -29,6 +29,21 @@ public ApplicationStateEvent() return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to . + /// + public ApplicationStateEvent(Guid correlationIdentifier) + : this(DefaultApplicationIdentity, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -47,6 +62,30 @@ public ApplicationStateEvent(String applicationIdentity) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A name or value that uniquely identifies the associated application. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public ApplicationStateEvent(String applicationIdentity, Guid correlationIdentifier) + : this(applicationIdentity, DefaultVerbosity, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -71,6 +110,34 @@ public ApplicationStateEvent(String applicationIdentity, EventVerbosity verbosit return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A name or value that uniquely identifies the associated application. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// is equal to -or- + /// is equal to . + /// + public ApplicationStateEvent(String applicationIdentity, EventVerbosity verbosity, Guid correlationIdentifier) + : this(applicationIdentity, verbosity, null, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -98,6 +165,37 @@ public ApplicationStateEvent(String applicationIdentity, EventVerbosity verbosit return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A name or value that uniquely identifies the associated application. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A textual description of the event. This argument can be . + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// is equal to -or- + /// is equal to . + /// + public ApplicationStateEvent(String applicationIdentity, EventVerbosity verbosity, String description, Guid correlationIdentifier) + : this(applicationIdentity, verbosity, description, DefaultTimeStamp, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -129,6 +227,41 @@ public ApplicationStateEvent(String applicationIdentity, EventVerbosity verbosit Metadata = new Dictionary(); } + /// + /// Initializes a new instance of the class. + /// + /// + /// A name or value that uniquely identifies the associated application. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A textual description of the event. This argument can be . + /// + /// + /// A that indicates when the event occurred. The default value is . + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// is equal to -or- + /// is equal to . + /// + public ApplicationStateEvent(String applicationIdentity, EventVerbosity verbosity, String description, DateTime timeStamp, Guid correlationIdentifier) + : base(StaticCategory, verbosity, description, timeStamp, correlationIdentifier) + { + ApplicationIdentity = applicationIdentity.RejectIf().IsNullOrEmpty(nameof(applicationIdentity)); + Metadata = new Dictionary(); + } + /// /// Gets or sets a name or value that uniquely identifies the associated application. /// diff --git a/src/RapidField.SolidInstruments.EventAuthoring/DomainEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/DomainEvent.cs index 2553e655..5598bf3f 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/DomainEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/DomainEvent.cs @@ -29,6 +29,21 @@ public DomainEvent() return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to . + /// + public DomainEvent(Guid correlationIdentifier) + : this(Array.Empty(), correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -44,6 +59,27 @@ public DomainEvent(IEnumerable labels) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public DomainEvent(IEnumerable labels, Guid correlationIdentifier) + : this(labels, DefaultVerbosity, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -65,6 +101,31 @@ public DomainEvent(IEnumerable labels, EventVerbosity verbosity) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is . + /// + /// + /// is equal to -or- + /// is equal to . + /// + public DomainEvent(IEnumerable labels, EventVerbosity verbosity, Guid correlationIdentifier) + : this(labels, verbosity, null, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -89,6 +150,34 @@ public DomainEvent(IEnumerable labels, EventVerbosity verbosity, String return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A textual description of the event. This argument can be . + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is . + /// + /// + /// is equal to -or- + /// is equal to . + /// + public DomainEvent(IEnumerable labels, EventVerbosity verbosity, String description, Guid correlationIdentifier) + : this(labels, verbosity, description, DefaultTimeStamp, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -116,6 +205,37 @@ public DomainEvent(IEnumerable labels, EventVerbosity verbosity, String Labels = new List((labels.RejectIf().IsNull(nameof(labels)).TargetArgument)); } + /// + /// Initializes a new instance of the class. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A textual description of the event. This argument can be . + /// + /// + /// A that indicates when the event occurred. The default value is . + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is . + /// + /// + /// is equal to -or- + /// is equal to . + /// + public DomainEvent(IEnumerable labels, EventVerbosity verbosity, String description, DateTime timeStamp, Guid correlationIdentifier) + : base(StaticCategory, verbosity, description, timeStamp, correlationIdentifier) + { + Labels = new List((labels.RejectIf().IsNull(nameof(labels)).TargetArgument)); + } + /// /// Gets or sets a collection of textual labels that provide categorical and/or contextual information about the current /// . diff --git a/src/RapidField.SolidInstruments.EventAuthoring/DomainEventHandler.cs b/src/RapidField.SolidInstruments.EventAuthoring/DomainEventHandler.cs new file mode 100644 index 00000000..44435405 --- /dev/null +++ b/src/RapidField.SolidInstruments.EventAuthoring/DomainEventHandler.cs @@ -0,0 +1,42 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using System; + +namespace RapidField.SolidInstruments.EventAuthoring +{ + /// + /// Processes a single . + /// + /// + /// The type of the event that is processed by the handler. + /// + public abstract class DomainEventHandler : EventHandler + where TEvent : class, IDomainEvent + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// is . + /// + protected DomainEventHandler(ICommandMediator mediator) + : base(mediator) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring/DomainModelAssociatedEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/DomainModelAssociatedEvent.cs index 2d68497d..588ff904 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/DomainModelAssociatedEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/DomainModelAssociatedEvent.cs @@ -32,6 +32,21 @@ public DomainModelAssociatedEvent() return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to . + /// + public DomainModelAssociatedEvent(Guid correlationIdentifier) + : base(correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -47,6 +62,27 @@ public DomainModelAssociatedEvent(TModel model) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public DomainModelAssociatedEvent(TModel model, Guid correlationIdentifier) + : this(model, Array.Empty(), correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -65,6 +101,30 @@ public DomainModelAssociatedEvent(TModel model, IEnumerable labels) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public DomainModelAssociatedEvent(TModel model, IEnumerable labels, Guid correlationIdentifier) + : this(model, labels, DefaultVerbosity, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -88,5 +148,33 @@ public DomainModelAssociatedEvent(TModel model, IEnumerable labels, Even { return; } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is -or- is . + /// + /// + /// is equal to -or- + /// is equal to . + /// + public DomainModelAssociatedEvent(TModel model, IEnumerable labels, EventVerbosity verbosity, Guid correlationIdentifier) + : base(model, DomainModelEventClassification.Associated, labels, verbosity, correlationIdentifier) + { + return; + } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring/DomainModelAssociatedEventHandler.cs b/src/RapidField.SolidInstruments.EventAuthoring/DomainModelAssociatedEventHandler.cs new file mode 100644 index 00000000..5ba9bc61 --- /dev/null +++ b/src/RapidField.SolidInstruments.EventAuthoring/DomainModelAssociatedEventHandler.cs @@ -0,0 +1,47 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Domain; +using System; + +namespace RapidField.SolidInstruments.EventAuthoring +{ + /// + /// Processes a single . + /// + /// + /// The type of the associated domain model. + /// + /// + /// The type of the event that is processed by the handler. + /// + public abstract class DomainModelAssociatedEventHandler : DomainModelEventHandler + where TModel : class, IDomainModel + where TEvent : class, IDomainModelAssociatedEvent + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// is . + /// + protected DomainModelAssociatedEventHandler(ICommandMediator mediator) + : base(mediator) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring/DomainModelCreatedEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/DomainModelCreatedEvent.cs index 36e9d63f..1f328fad 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/DomainModelCreatedEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/DomainModelCreatedEvent.cs @@ -32,6 +32,21 @@ public DomainModelCreatedEvent() return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to . + /// + public DomainModelCreatedEvent(Guid correlationIdentifier) + : base(correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -47,6 +62,27 @@ public DomainModelCreatedEvent(TModel model) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public DomainModelCreatedEvent(TModel model, Guid correlationIdentifier) + : this(model, Array.Empty(), correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -65,6 +101,30 @@ public DomainModelCreatedEvent(TModel model, IEnumerable labels) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public DomainModelCreatedEvent(TModel model, IEnumerable labels, Guid correlationIdentifier) + : this(model, labels, DefaultVerbosity, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -88,5 +148,33 @@ public DomainModelCreatedEvent(TModel model, IEnumerable labels, EventVe { return; } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is -or- is . + /// + /// + /// is equal to -or- + /// is equal to . + /// + public DomainModelCreatedEvent(TModel model, IEnumerable labels, EventVerbosity verbosity, Guid correlationIdentifier) + : base(model, DomainModelEventClassification.Created, labels, verbosity, correlationIdentifier) + { + return; + } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring/DomainModelCreatedEventHandler.cs b/src/RapidField.SolidInstruments.EventAuthoring/DomainModelCreatedEventHandler.cs new file mode 100644 index 00000000..21b661dc --- /dev/null +++ b/src/RapidField.SolidInstruments.EventAuthoring/DomainModelCreatedEventHandler.cs @@ -0,0 +1,84 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Concurrency; +using RapidField.SolidInstruments.Core.Domain; +using System; +using System.Collections.Generic; + +namespace RapidField.SolidInstruments.EventAuthoring +{ + /// + /// Processes a single . + /// + /// + /// The type of the associated domain model. + /// + /// + /// The type of the event that is processed by the handler. + /// + public abstract class DomainModelCreatedEventHandler : DomainModelEventHandler + where TModel : class, IDomainModel + where TEvent : class, IDomainModelCreatedEvent + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// is . + /// + protected DomainModelCreatedEventHandler(ICommandMediator mediator) + : base(mediator) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Processes the specified domain model. + /// + /// + /// The model that was created. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// A unique identifier to assign to sub-commands. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + protected abstract void Process(TModel model, IEnumerable labels, Guid correlationIdentifier, ICommandMediator mediator, IConcurrencyControlToken controlToken); + + /// + /// Processes the specified command. + /// + /// + /// The command to process. + /// + /// + /// A processing intermediary that is used to process sub-commands. Do not process using + /// , as doing so will generally result in infinite-looping. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + protected sealed override void Process(TEvent command, ICommandMediator mediator, IConcurrencyControlToken controlToken) => Process(command.Model, command.Labels, command.CorrelationIdentifier, mediator, controlToken); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring/DomainModelDeletedEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/DomainModelDeletedEvent.cs index afb09f7b..d7cc1255 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/DomainModelDeletedEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/DomainModelDeletedEvent.cs @@ -32,6 +32,21 @@ public DomainModelDeletedEvent() return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to . + /// + public DomainModelDeletedEvent(Guid correlationIdentifier) + : base(correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -47,6 +62,27 @@ public DomainModelDeletedEvent(TModel model) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public DomainModelDeletedEvent(TModel model, Guid correlationIdentifier) + : this(model, Array.Empty(), correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -65,6 +101,30 @@ public DomainModelDeletedEvent(TModel model, IEnumerable labels) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public DomainModelDeletedEvent(TModel model, IEnumerable labels, Guid correlationIdentifier) + : this(model, labels, DefaultVerbosity, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -88,5 +148,33 @@ public DomainModelDeletedEvent(TModel model, IEnumerable labels, EventVe { return; } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is -or- is . + /// + /// + /// is equal to -or- + /// is equal to . + /// + public DomainModelDeletedEvent(TModel model, IEnumerable labels, EventVerbosity verbosity, Guid correlationIdentifier) + : base(model, DomainModelEventClassification.Deleted, labels, verbosity, correlationIdentifier) + { + return; + } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring/DomainModelDeletedEventHandler.cs b/src/RapidField.SolidInstruments.EventAuthoring/DomainModelDeletedEventHandler.cs new file mode 100644 index 00000000..13289c74 --- /dev/null +++ b/src/RapidField.SolidInstruments.EventAuthoring/DomainModelDeletedEventHandler.cs @@ -0,0 +1,84 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Concurrency; +using RapidField.SolidInstruments.Core.Domain; +using System; +using System.Collections.Generic; + +namespace RapidField.SolidInstruments.EventAuthoring +{ + /// + /// Processes a single . + /// + /// + /// The type of the associated domain model. + /// + /// + /// The type of the event that is processed by the handler. + /// + public abstract class DomainModelDeletedEventHandler : DomainModelEventHandler + where TModel : class, IDomainModel + where TEvent : class, IDomainModelDeletedEvent + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// is . + /// + protected DomainModelDeletedEventHandler(ICommandMediator mediator) + : base(mediator) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Processes the specified domain model. + /// + /// + /// The model that was deleted. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// A unique identifier to assign to sub-commands. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + protected abstract void Process(TModel model, IEnumerable labels, Guid correlationIdentifier, ICommandMediator mediator, IConcurrencyControlToken controlToken); + + /// + /// Processes the specified command. + /// + /// + /// The command to process. + /// + /// + /// A processing intermediary that is used to process sub-commands. Do not process using + /// , as doing so will generally result in infinite-looping. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + protected sealed override void Process(TEvent command, ICommandMediator mediator, IConcurrencyControlToken controlToken) => Process(command.Model, command.Labels, command.CorrelationIdentifier, mediator, controlToken); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring/DomainModelEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/DomainModelEvent.cs index 599d8071..fbaa10a4 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/DomainModelEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/DomainModelEvent.cs @@ -33,6 +33,21 @@ public DomainModelEvent() Model = null; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to . + /// + public DomainModelEvent(Guid correlationIdentifier) + : base(correlationIdentifier) + { + Model = null; + } + /// /// Initializes a new instance of the class. /// @@ -54,6 +69,31 @@ public DomainModelEvent(TModel model, DomainModelEventClassification classificat return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A classification that describes the effect of a the event upon . + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is . + /// + /// + /// is equal to -or- + /// is equal to . + /// + public DomainModelEvent(TModel model, DomainModelEventClassification classification, Guid correlationIdentifier) + : this(model, classification, Array.Empty(), correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -78,6 +118,34 @@ public DomainModelEvent(TModel model, DomainModelEventClassification classificat return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A classification that describes the effect of a the event upon . + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is -or- is . + /// + /// + /// is equal to -or- + /// is equal to . + /// + public DomainModelEvent(TModel model, DomainModelEventClassification classification, IEnumerable labels, Guid correlationIdentifier) + : this(model, classification, labels, DefaultVerbosity, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -106,6 +174,38 @@ public DomainModelEvent(TModel model, DomainModelEventClassification classificat return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A classification that describes the effect of a the event upon . + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is -or- is . + /// + /// + /// is equal to -or- + /// is equal to -or- + /// is equal to . + /// + public DomainModelEvent(TModel model, DomainModelEventClassification classification, IEnumerable labels, EventVerbosity verbosity, Guid correlationIdentifier) + : this(model, classification, labels, verbosity, GetDescription(model, classification), correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -138,6 +238,42 @@ public DomainModelEvent(TModel model, DomainModelEventClassification classificat Model = model.RejectIf().IsNull(nameof(model)); } + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A classification that describes the effect of a the event upon . + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A textual description of the event. This argument can be . + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is -or- is . + /// + /// + /// is equal to -or- + /// is equal to -or- + /// is equal to . + /// + public DomainModelEvent(TModel model, DomainModelEventClassification classification, IEnumerable labels, EventVerbosity verbosity, String description, Guid correlationIdentifier) + : base(labels, verbosity, description, correlationIdentifier) + { + Classification = classification.RejectIf().IsEqualToValue(DomainModelEventClassification.Unspecified, nameof(classification)); + Model = model.RejectIf().IsNull(nameof(model)); + } + /// /// Returns a description for the current . /// diff --git a/src/RapidField.SolidInstruments.EventAuthoring/DomainModelEventHandler.cs b/src/RapidField.SolidInstruments.EventAuthoring/DomainModelEventHandler.cs new file mode 100644 index 00000000..1ae8cdeb --- /dev/null +++ b/src/RapidField.SolidInstruments.EventAuthoring/DomainModelEventHandler.cs @@ -0,0 +1,47 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Domain; +using System; + +namespace RapidField.SolidInstruments.EventAuthoring +{ + /// + /// Processes a single . + /// + /// + /// The type of the associated domain model. + /// + /// + /// The type of the event that is processed by the handler. + /// + public abstract class DomainModelEventHandler : DomainEventHandler + where TModel : class, IDomainModel + where TEvent : class, IDomainModelEvent + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// is . + /// + protected DomainModelEventHandler(ICommandMediator mediator) + : base(mediator) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring/DomainModelUpdatedEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/DomainModelUpdatedEvent.cs index b38e9726..93d95954 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/DomainModelUpdatedEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/DomainModelUpdatedEvent.cs @@ -32,6 +32,21 @@ public DomainModelUpdatedEvent() return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to . + /// + public DomainModelUpdatedEvent(Guid correlationIdentifier) + : base(correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -47,6 +62,27 @@ public DomainModelUpdatedEvent(TModel model) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public DomainModelUpdatedEvent(TModel model, Guid correlationIdentifier) + : this(model, Array.Empty(), correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -65,6 +101,30 @@ public DomainModelUpdatedEvent(TModel model, IEnumerable labels) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public DomainModelUpdatedEvent(TModel model, IEnumerable labels, Guid correlationIdentifier) + : this(model, labels, DefaultVerbosity, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -88,5 +148,33 @@ public DomainModelUpdatedEvent(TModel model, IEnumerable labels, EventVe { return; } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is -or- is . + /// + /// + /// is equal to -or- + /// is equal to . + /// + public DomainModelUpdatedEvent(TModel model, IEnumerable labels, EventVerbosity verbosity, Guid correlationIdentifier) + : base(model, DomainModelEventClassification.Updated, labels, verbosity, correlationIdentifier) + { + return; + } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring/DomainModelUpdatedEventHandler.cs b/src/RapidField.SolidInstruments.EventAuthoring/DomainModelUpdatedEventHandler.cs new file mode 100644 index 00000000..c04db3c8 --- /dev/null +++ b/src/RapidField.SolidInstruments.EventAuthoring/DomainModelUpdatedEventHandler.cs @@ -0,0 +1,84 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Concurrency; +using RapidField.SolidInstruments.Core.Domain; +using System; +using System.Collections.Generic; + +namespace RapidField.SolidInstruments.EventAuthoring +{ + /// + /// Processes a single . + /// + /// + /// The type of the associated domain model. + /// + /// + /// The type of the event that is processed by the handler. + /// + public abstract class DomainModelUpdatedEventHandler : DomainModelEventHandler + where TModel : class, IDomainModel + where TEvent : class, IDomainModelUpdatedEvent + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// is . + /// + protected DomainModelUpdatedEventHandler(ICommandMediator mediator) + : base(mediator) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Processes the specified domain model. + /// + /// + /// The model that was updated. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// A unique identifier to assign to sub-commands. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + protected abstract void Process(TModel model, IEnumerable labels, Guid correlationIdentifier, ICommandMediator mediator, IConcurrencyControlToken controlToken); + + /// + /// Processes the specified command. + /// + /// + /// The command to process. + /// + /// + /// A processing intermediary that is used to process sub-commands. Do not process using + /// , as doing so will generally result in infinite-looping. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + protected sealed override void Process(TEvent command, ICommandMediator mediator, IConcurrencyControlToken controlToken) => Process(command.Model, command.Labels, command.CorrelationIdentifier, mediator, controlToken); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring/ErrorEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/ErrorEvent.cs index 53184862..18b74751 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/ErrorEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/ErrorEvent.cs @@ -28,6 +28,21 @@ public ErrorEvent() DiagnosticDetails = null; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to . + /// + public ErrorEvent(Guid correlationIdentifier) + : this(DefaultApplicationIdentity, correlationIdentifier) + { + DiagnosticDetails = null; + } + /// /// Initializes a new instance of the class. /// @@ -43,6 +58,27 @@ public ErrorEvent(Exception exception) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// An associated exception. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public ErrorEvent(Exception exception, Guid correlationIdentifier) + : this(DefaultApplicationIdentity, exception, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -61,6 +97,30 @@ public ErrorEvent(String applicationIdentity) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A name or value that uniquely identifies the application in which the associated error occurred. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public ErrorEvent(String applicationIdentity, Guid correlationIdentifier) + : this(applicationIdentity, diagnosticDetails: null, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -83,6 +143,34 @@ public ErrorEvent(String applicationIdentity, Exception exception) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A name or value that uniquely identifies the application in which the associated error occurred. + /// + /// + /// An associated exception. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is empty. + /// + /// + /// is -or- is + /// . + /// + /// + /// is equal to . + /// + public ErrorEvent(String applicationIdentity, Exception exception, Guid correlationIdentifier) + : this(applicationIdentity, exception.RejectIf().IsNull(nameof(exception)).TargetArgument.StackTrace, DefaultVerbosity, exception.Message, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -104,6 +192,33 @@ public ErrorEvent(String applicationIdentity, String diagnosticDetails) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A name or value that uniquely identifies the application in which the associated error occurred. + /// + /// + /// Textual diagnostic information about the associated error. This value can be . + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public ErrorEvent(String applicationIdentity, String diagnosticDetails, Guid correlationIdentifier) + : this(applicationIdentity, diagnosticDetails, DefaultVerbosity, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -131,6 +246,37 @@ public ErrorEvent(String applicationIdentity, String diagnosticDetails, EventVer return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A name or value that uniquely identifies the application in which the associated error occurred. + /// + /// + /// Textual diagnostic information about the associated error. This value can be . + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// is equal to -or- + /// is equal to . + /// + public ErrorEvent(String applicationIdentity, String diagnosticDetails, EventVerbosity verbosity, Guid correlationIdentifier) + : this(applicationIdentity, diagnosticDetails, verbosity, null, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -161,6 +307,40 @@ public ErrorEvent(String applicationIdentity, String diagnosticDetails, EventVer return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A name or value that uniquely identifies the application in which the associated error occurred. + /// + /// + /// Textual diagnostic information about the associated error. This value can be . + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A textual description of the event. This argument can be . + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// is equal to -or- + /// is equal to . + /// + public ErrorEvent(String applicationIdentity, String diagnosticDetails, EventVerbosity verbosity, String description, Guid correlationIdentifier) + : this(applicationIdentity, diagnosticDetails, verbosity, description, DefaultTimeStamp, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -195,6 +375,44 @@ public ErrorEvent(String applicationIdentity, String diagnosticDetails, EventVer DiagnosticDetails = diagnosticDetails; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A name or value that uniquely identifies the application in which the associated error occurred. + /// + /// + /// Textual diagnostic information about the associated error. This value can be . + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A textual description of the event. This argument can be . + /// + /// + /// A that indicates when the event occurred. The default value is . + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// is equal to -or- + /// is equal to . + /// + public ErrorEvent(String applicationIdentity, String diagnosticDetails, EventVerbosity verbosity, String description, DateTime timeStamp, Guid correlationIdentifier) + : base(StaticCategory, verbosity, description, timeStamp, correlationIdentifier) + { + ApplicationIdentity = applicationIdentity.RejectIf().IsNullOrEmpty(nameof(applicationIdentity)); + DiagnosticDetails = diagnosticDetails; + } + /// /// Gets or sets a name or value that uniquely identifies the application in which the associated error occurred. /// diff --git a/src/RapidField.SolidInstruments.EventAuthoring/Event.cs b/src/RapidField.SolidInstruments.EventAuthoring/Event.cs index 6610fb47..c80c09bc 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/Event.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/Event.cs @@ -9,6 +9,7 @@ using System; using System.Diagnostics; using System.Runtime.Serialization; +using SolidInstrumentsCommand = RapidField.SolidInstruments.Command.Command; namespace RapidField.SolidInstruments.EventAuthoring { @@ -19,20 +20,32 @@ namespace RapidField.SolidInstruments.EventAuthoring /// is the default implementation of . /// [DataContract] - public class Event : IEvent + public class Event : SolidInstrumentsCommand, IEvent { /// /// Initializes a new instance of the class. /// - /// - /// is the default implementation of . - /// public Event() : this(DefaultCategory) { return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to . + /// + public Event(Guid correlationIdentifier) + : this(DefaultCategory, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -48,6 +61,25 @@ public Event(EventCategory category) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The category of the event. The default value is . + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to -or- + /// is equal to . + /// + public Event(EventCategory category, Guid correlationIdentifier) + : this(category, DefaultVerbosity, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -67,6 +99,29 @@ public Event(EventCategory category, EventVerbosity verbosity) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The category of the event. The default value is . + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to -or- is + /// equal to -or- is equal to + /// . + /// + public Event(EventCategory category, EventVerbosity verbosity, Guid correlationIdentifier) + : this(category, verbosity, null, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -89,6 +144,32 @@ public Event(EventCategory category, EventVerbosity verbosity, String descriptio return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The category of the event. The default value is . + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A textual description of the event. This argument can be . + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to -or- is + /// equal to -or- is equal to + /// . + /// + public Event(EventCategory category, EventVerbosity verbosity, String description, Guid correlationIdentifier) + : this(category, verbosity, description, Core.TimeStamp.Current, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -109,6 +190,39 @@ public Event(EventCategory category, EventVerbosity verbosity, String descriptio /// equal to . /// public Event(EventCategory category, EventVerbosity verbosity, String description, DateTime timeStamp) + : base() + { + Category = category.RejectIf().IsEqualToValue(EventCategory.Unspecified, nameof(category)); + Description = description; + TimeStamp = timeStamp; + Verbosity = verbosity.RejectIf().IsEqualToValue(EventVerbosity.Unspecified, nameof(verbosity)); + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The category of the event. The default value is . + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A textual description of the event. This argument can be . + /// + /// + /// A that indicates when the event occurred. The default value is . + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to -or- is + /// equal to -or- is equal to + /// . + /// + public Event(EventCategory category, EventVerbosity verbosity, String description, DateTime timeStamp, Guid correlationIdentifier) + : base(correlationIdentifier) { Category = category.RejectIf().IsEqualToValue(EventCategory.Unspecified, nameof(category)); Description = description; @@ -281,9 +395,9 @@ public override Boolean Equals(Object obj) { return false; } - else if (obj is IEvent) + else if (obj is IEvent eventObject) { - return Equals((IEvent)obj); + return Equals(eventObject); } return false; diff --git a/src/RapidField.SolidInstruments.EventAuthoring/EventHandler.cs b/src/RapidField.SolidInstruments.EventAuthoring/EventHandler.cs new file mode 100644 index 00000000..21a2bdc0 --- /dev/null +++ b/src/RapidField.SolidInstruments.EventAuthoring/EventHandler.cs @@ -0,0 +1,42 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using System; + +namespace RapidField.SolidInstruments.EventAuthoring +{ + /// + /// Processes a single . + /// + /// + /// The type of the event that is processed by the handler. + /// + public abstract class EventHandler : CommandHandler + where TEvent : class, IEvent + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// is . + /// + protected EventHandler(ICommandMediator mediator) + : base(mediator) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring/ExceptionRaisedEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/ExceptionRaisedEvent.cs index 40eb913d..c9699bfc 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/ExceptionRaisedEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/ExceptionRaisedEvent.cs @@ -27,6 +27,21 @@ public ExceptionRaisedEvent() ExceptionTypeFullName = null; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to . + /// + public ExceptionRaisedEvent(Guid correlationIdentifier) + : base(correlationIdentifier) + { + ExceptionTypeFullName = null; + } + /// /// Initializes a new instance of the class. /// @@ -42,6 +57,27 @@ public ExceptionRaisedEvent(Exception exception) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// An associated exception. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public ExceptionRaisedEvent(Exception exception, Guid correlationIdentifier) + : this(DefaultApplicationIdentity, exception, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -64,6 +100,34 @@ public ExceptionRaisedEvent(String applicationIdentity, Exception exception) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A name or value that uniquely identifies the application in which the associated error occurred. + /// + /// + /// An associated exception. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is empty. + /// + /// + /// is -or- is + /// . + /// + /// + /// is equal to . + /// + public ExceptionRaisedEvent(String applicationIdentity, Exception exception, Guid correlationIdentifier) + : this(applicationIdentity, exception, DefaultVerbosity, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -92,6 +156,38 @@ public ExceptionRaisedEvent(String applicationIdentity, Exception exception, Eve return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A name or value that uniquely identifies the application in which the associated error occurred. + /// + /// + /// An associated exception. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is empty. + /// + /// + /// is -or- is + /// . + /// + /// + /// is equal to -or- + /// is equal to . + /// + public ExceptionRaisedEvent(String applicationIdentity, Exception exception, EventVerbosity verbosity, Guid correlationIdentifier) + : this(applicationIdentity, exception, verbosity, DefaultTimeStamp, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -123,6 +219,41 @@ public ExceptionRaisedEvent(String applicationIdentity, Exception exception, Eve ExceptionTypeFullName = exception?.GetType().FullName; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A name or value that uniquely identifies the application in which the associated error occurred. + /// + /// + /// An associated exception. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A that indicates when the event occurred. The default value is . + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is empty. + /// + /// + /// is -or- is + /// . + /// + /// + /// is equal to -or- + /// is equal to . + /// + public ExceptionRaisedEvent(String applicationIdentity, Exception exception, EventVerbosity verbosity, DateTime timeStamp, Guid correlationIdentifier) + : base(applicationIdentity, exception.RejectIf().IsNull(nameof(exception)).TargetArgument.StackTrace, verbosity, exception.Message, timeStamp, correlationIdentifier) + { + ExceptionTypeFullName = exception?.GetType().FullName; + } + /// /// Gets or sets the full name of the type of the associated that was raised. /// diff --git a/src/RapidField.SolidInstruments.EventAuthoring/GeneralInformationEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/GeneralInformationEvent.cs index 78f68653..79f66c32 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/GeneralInformationEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/GeneralInformationEvent.cs @@ -29,6 +29,21 @@ public GeneralInformationEvent() return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to . + /// + public GeneralInformationEvent(Guid correlationIdentifier) + : this(Array.Empty(), correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -44,6 +59,27 @@ public GeneralInformationEvent(IEnumerable labels) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public GeneralInformationEvent(IEnumerable labels, Guid correlationIdentifier) + : this(labels, DefaultVerbosity, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -65,6 +101,31 @@ public GeneralInformationEvent(IEnumerable labels, EventVerbosity verbos return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is . + /// + /// + /// is equal to -or- + /// is equal to . + /// + public GeneralInformationEvent(IEnumerable labels, EventVerbosity verbosity, Guid correlationIdentifier) + : this(labels, verbosity, null, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -89,6 +150,34 @@ public GeneralInformationEvent(IEnumerable labels, EventVerbosity verbos return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A textual description of the event. This argument can be . + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is . + /// + /// + /// is equal to -or- + /// is equal to . + /// + public GeneralInformationEvent(IEnumerable labels, EventVerbosity verbosity, String description, Guid correlationIdentifier) + : this(labels, verbosity, description, DefaultTimeStamp, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -117,6 +206,38 @@ public GeneralInformationEvent(IEnumerable labels, EventVerbosity verbos Metadata = new Dictionary(); } + /// + /// Initializes a new instance of the class. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A textual description of the event. This argument can be . + /// + /// + /// A that indicates when the event occurred. The default value is . + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is . + /// + /// + /// is equal to -or- + /// is equal to . + /// + public GeneralInformationEvent(IEnumerable labels, EventVerbosity verbosity, String description, DateTime timeStamp, Guid correlationIdentifier) + : base(StaticCategory, verbosity, description, timeStamp, correlationIdentifier) + { + Labels = new List((labels.RejectIf().IsNull(nameof(labels)).TargetArgument)); + Metadata = new Dictionary(); + } + /// /// Gets or sets a collection of textual labels that provide categorical and/or contextual information about the current /// . diff --git a/src/RapidField.SolidInstruments.EventAuthoring/IDomainModelEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/IDomainModelEvent.cs index 68952a8c..5ef4eed7 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/IDomainModelEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/IDomainModelEvent.cs @@ -13,22 +13,28 @@ namespace RapidField.SolidInstruments.EventAuthoring /// /// The type of the associated domain model. /// - public interface IDomainModelEvent : IDomainEvent + public interface IDomainModelEvent : IDomainModelEvent where TModel : class, IDomainModel { /// - /// Gets a classification that describes the effect of a the current upon - /// . + /// Gets the resulting state of the associated domain model. /// - public DomainModelEventClassification Classification + public TModel Model { get; } + } + /// + /// Represents information about an event related to an object that models a domain construct. + /// + public interface IDomainModelEvent : IDomainEvent + { /// - /// Gets the resulting state of the associated domain model. + /// Gets a classification that describes the effect of a the current upon the associated + /// model. /// - public TModel Model + public DomainModelEventClassification Classification { get; } diff --git a/src/RapidField.SolidInstruments.EventAuthoring/IEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/IEvent.cs index c932c364..5d56ca52 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/IEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/IEvent.cs @@ -2,6 +2,7 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= +using RapidField.SolidInstruments.Command; using System; namespace RapidField.SolidInstruments.EventAuthoring @@ -9,7 +10,7 @@ namespace RapidField.SolidInstruments.EventAuthoring /// /// Represents information about an event. /// - public interface IEvent : IComparable, IEquatable + public interface IEvent : ICommand, IComparable, IEquatable { /// /// Converts the current to an array of bytes. diff --git a/src/RapidField.SolidInstruments.EventAuthoring/RapidField.SolidInstruments.EventAuthoring.csproj b/src/RapidField.SolidInstruments.EventAuthoring/RapidField.SolidInstruments.EventAuthoring.csproj index d318c7b2..8f22ce89 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/RapidField.SolidInstruments.EventAuthoring.csproj +++ b/src/RapidField.SolidInstruments.EventAuthoring/RapidField.SolidInstruments.EventAuthoring.csproj @@ -37,6 +37,7 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in + diff --git a/src/RapidField.SolidInstruments.EventAuthoring/SecurityEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/SecurityEvent.cs index 48d069ba..1d2b3b99 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/SecurityEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/SecurityEvent.cs @@ -28,18 +28,55 @@ public SecurityEvent() return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to . + /// + public SecurityEvent(Guid correlationIdentifier) + : this(DefaultSeverity, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// /// - /// is equal to . + /// The severity of the event. The default value is . /// + /// + /// is equal to . + /// public SecurityEvent(SecurityEventSeverity severity) : this(severity, DefaultVerbosity) { return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The severity of the event. The default value is . + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to -or- + /// is equal to . + /// + public SecurityEvent(SecurityEventSeverity severity, Guid correlationIdentifier) + : this(severity, DefaultVerbosity, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -59,6 +96,29 @@ public SecurityEvent(SecurityEventSeverity severity, EventVerbosity verbosity) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The severity of the event. The default value is . + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to -or- + /// is equal to -or- + /// is equal to . + /// + public SecurityEvent(SecurityEventSeverity severity, EventVerbosity verbosity, Guid correlationIdentifier) + : this(severity, verbosity, null, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -81,6 +141,32 @@ public SecurityEvent(SecurityEventSeverity severity, EventVerbosity verbosity, S return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The severity of the event. The default value is . + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A textual description of the event. This argument can be . + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to -or- + /// is equal to -or- + /// is equal to . + /// + public SecurityEvent(SecurityEventSeverity severity, EventVerbosity verbosity, String description, Guid correlationIdentifier) + : this(severity, verbosity, description, DefaultTimeStamp, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -106,6 +192,35 @@ public SecurityEvent(SecurityEventSeverity severity, EventVerbosity verbosity, S Severity = severity.RejectIf().IsEqualToValue(SecurityEventSeverity.Unspecified, nameof(severity)); } + /// + /// Initializes a new instance of the class. + /// + /// + /// The severity of the event. The default value is . + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A textual description of the event. This argument can be . + /// + /// + /// A that indicates when the event occurred. The default value is . + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to -or- + /// is equal to -or- + /// is equal to . + /// + public SecurityEvent(SecurityEventSeverity severity, EventVerbosity verbosity, String description, DateTime timeStamp, Guid correlationIdentifier) + : base(StaticCategory, verbosity, description, timeStamp, correlationIdentifier) + { + Severity = severity.RejectIf().IsEqualToValue(SecurityEventSeverity.Unspecified, nameof(severity)); + } + /// /// Gets or sets the severity of the current . /// diff --git a/src/RapidField.SolidInstruments.EventAuthoring/SystemStateEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/SystemStateEvent.cs index 23d576fe..39020060 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/SystemStateEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/SystemStateEvent.cs @@ -29,6 +29,21 @@ public SystemStateEvent() return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to . + /// + public SystemStateEvent(Guid correlationIdentifier) + : this(DefaultSystemIdentity, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -47,6 +62,30 @@ public SystemStateEvent(String systemIdentity) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A name or value that uniquely identifies the associated system. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public SystemStateEvent(String systemIdentity, Guid correlationIdentifier) + : this(systemIdentity, DefaultVerbosity, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -71,6 +110,34 @@ public SystemStateEvent(String systemIdentity, EventVerbosity verbosity) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A name or value that uniquely identifies the associated system. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// is equal to -or- + /// is equal to . + /// + public SystemStateEvent(String systemIdentity, EventVerbosity verbosity, Guid correlationIdentifier) + : this(systemIdentity, verbosity, null, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -98,6 +165,37 @@ public SystemStateEvent(String systemIdentity, EventVerbosity verbosity, String return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A name or value that uniquely identifies the associated system. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A textual description of the event. This argument can be . + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// is equal to -or- + /// is equal to . + /// + public SystemStateEvent(String systemIdentity, EventVerbosity verbosity, String description, Guid correlationIdentifier) + : this(systemIdentity, verbosity, description, DefaultTimeStamp, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -129,6 +227,41 @@ public SystemStateEvent(String systemIdentity, EventVerbosity verbosity, String SystemIdentity = systemIdentity.RejectIf().IsNullOrEmpty(nameof(systemIdentity)); } + /// + /// Initializes a new instance of the class. + /// + /// + /// A name or value that uniquely identifies the associated system. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A textual description of the event. This argument can be . + /// + /// + /// A that indicates when the event occurred. The default value is . + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// is equal to -or- + /// is equal to . + /// + public SystemStateEvent(String systemIdentity, EventVerbosity verbosity, String description, DateTime timeStamp, Guid correlationIdentifier) + : base(StaticCategory, verbosity, description, timeStamp, correlationIdentifier) + { + Metadata = new Dictionary(); + SystemIdentity = systemIdentity.RejectIf().IsNullOrEmpty(nameof(systemIdentity)); + } + /// /// Gets a dictionary of metadata for the current . /// diff --git a/src/RapidField.SolidInstruments.EventAuthoring/TransactionEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/TransactionEvent.cs index 0713575b..13d1cdf6 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/TransactionEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/TransactionEvent.cs @@ -28,6 +28,21 @@ public TransactionEvent() return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to . + /// + public TransactionEvent(Guid correlationIdentifier) + : this(DefaultOutcome, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -40,6 +55,25 @@ public TransactionEvent(TransactionEventOutcome outcome) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// is equal to . + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to -or- + /// is equal to . + /// + public TransactionEvent(TransactionEventOutcome outcome, Guid correlationIdentifier) + : this(outcome, DefaultVerbosity, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -59,6 +93,29 @@ public TransactionEvent(TransactionEventOutcome outcome, EventVerbosity verbosit return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The outcome of the transaction. The default value is . + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to -or- + /// is equal to -or- + /// is equal to . + /// + public TransactionEvent(TransactionEventOutcome outcome, EventVerbosity verbosity, Guid correlationIdentifier) + : this(outcome, verbosity, null, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -81,6 +138,32 @@ public TransactionEvent(TransactionEventOutcome outcome, EventVerbosity verbosit return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The outcome of the transaction. The default value is . + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A textual description of the event. This argument can be . + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to -or- + /// is equal to -or- + /// is equal to . + /// + public TransactionEvent(TransactionEventOutcome outcome, EventVerbosity verbosity, String description, Guid correlationIdentifier) + : this(outcome, verbosity, description, DefaultTimeStamp, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -106,6 +189,35 @@ public TransactionEvent(TransactionEventOutcome outcome, EventVerbosity verbosit Outcome = outcome.RejectIf().IsEqualToValue(TransactionEventOutcome.Unspecified, nameof(outcome)); } + /// + /// Initializes a new instance of the class. + /// + /// + /// The outcome of the transaction. The default value is . + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A textual description of the event. This argument can be . + /// + /// + /// A that indicates when the event occurred. The default value is . + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to -or- + /// is equal to -or- + /// is equal to . + /// + public TransactionEvent(TransactionEventOutcome outcome, EventVerbosity verbosity, String description, DateTime timeStamp, Guid correlationIdentifier) + : base(StaticCategory, verbosity, description, timeStamp, correlationIdentifier) + { + Outcome = outcome.RejectIf().IsEqualToValue(TransactionEventOutcome.Unspecified, nameof(outcome)); + } + /// /// Gets or sets the outcome of the current . /// diff --git a/src/RapidField.SolidInstruments.EventAuthoring/UserActionEvent.cs b/src/RapidField.SolidInstruments.EventAuthoring/UserActionEvent.cs index 4abeeef0..51b9336a 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/UserActionEvent.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/UserActionEvent.cs @@ -28,6 +28,21 @@ public UserActionEvent() return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to . + /// + public UserActionEvent(Guid correlationIdentifier) + : this(DefaultOutcome, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -40,6 +55,25 @@ public UserActionEvent(UserActionEventOutcome outcome) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// is equal to . + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to -or- + /// is equal to . + /// + public UserActionEvent(UserActionEventOutcome outcome, Guid correlationIdentifier) + : this(outcome, DefaultVerbosity, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -59,6 +93,29 @@ public UserActionEvent(UserActionEventOutcome outcome, EventVerbosity verbosity) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The outcome of the user action. The default value is . + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to -or- + /// is equal to -or- + /// is equal to . + /// + public UserActionEvent(UserActionEventOutcome outcome, EventVerbosity verbosity, Guid correlationIdentifier) + : this(outcome, verbosity, null, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -81,6 +138,32 @@ public UserActionEvent(UserActionEventOutcome outcome, EventVerbosity verbosity, return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The outcome of the user action. The default value is . + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A textual description of the event. This argument can be . + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to -or- + /// is equal to -or- + /// is equal to . + /// + public UserActionEvent(UserActionEventOutcome outcome, EventVerbosity verbosity, String description, Guid correlationIdentifier) + : this(outcome, verbosity, description, DefaultTimeStamp, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -106,6 +189,35 @@ public UserActionEvent(UserActionEventOutcome outcome, EventVerbosity verbosity, Outcome = outcome.RejectIf().IsEqualToValue(UserActionEventOutcome.Unspecified, nameof(outcome)); } + /// + /// Initializes a new instance of the class. + /// + /// + /// The outcome of the user action. The default value is . + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A textual description of the event. This argument can be . + /// + /// + /// A that indicates when the event occurred. The default value is . + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to -or- + /// is equal to -or- + /// is equal to . + /// + public UserActionEvent(UserActionEventOutcome outcome, EventVerbosity verbosity, String description, DateTime timeStamp, Guid correlationIdentifier) + : base(StaticCategory, verbosity, description, timeStamp, correlationIdentifier) + { + Outcome = outcome.RejectIf().IsEqualToValue(UserActionEventOutcome.Unspecified, nameof(outcome)); + } + /// /// Gets or sets the outcome of the current . /// diff --git a/src/RapidField.SolidInstruments.Mathematics/Physics/Area.cs b/src/RapidField.SolidInstruments.Mathematics/Physics/Area.cs index f12a4afa..92fe60b9 100644 --- a/src/RapidField.SolidInstruments.Mathematics/Physics/Area.cs +++ b/src/RapidField.SolidInstruments.Mathematics/Physics/Area.cs @@ -401,9 +401,9 @@ public override Boolean Equals(Object obj) { return false; } - else if (obj is Area) + else if (obj is Area area) { - return Equals((Area)obj); + return Equals(area); } return false; diff --git a/src/RapidField.SolidInstruments.Mathematics/Physics/GeographicCoordinates.cs b/src/RapidField.SolidInstruments.Mathematics/Physics/GeographicCoordinates.cs index 6d65f2eb..abc65ce7 100644 --- a/src/RapidField.SolidInstruments.Mathematics/Physics/GeographicCoordinates.cs +++ b/src/RapidField.SolidInstruments.Mathematics/Physics/GeographicCoordinates.cs @@ -122,9 +122,9 @@ public override Boolean Equals(Object obj) { return false; } - else if (obj is GeographicCoordinates) + else if (obj is GeographicCoordinates coordinates) { - return Equals((GeographicCoordinates)obj); + return Equals(coordinates); } return false; diff --git a/src/RapidField.SolidInstruments.Mathematics/Physics/Length.cs b/src/RapidField.SolidInstruments.Mathematics/Physics/Length.cs index e622f6d3..44846618 100644 --- a/src/RapidField.SolidInstruments.Mathematics/Physics/Length.cs +++ b/src/RapidField.SolidInstruments.Mathematics/Physics/Length.cs @@ -402,9 +402,9 @@ public override Boolean Equals(Object obj) { return false; } - else if (obj is Length) + else if (obj is Length length) { - return Equals((Length)obj); + return Equals(length); } return false; diff --git a/src/RapidField.SolidInstruments.Mathematics/Physics/Mass.cs b/src/RapidField.SolidInstruments.Mathematics/Physics/Mass.cs index d55d6f6e..71ad9963 100644 --- a/src/RapidField.SolidInstruments.Mathematics/Physics/Mass.cs +++ b/src/RapidField.SolidInstruments.Mathematics/Physics/Mass.cs @@ -361,9 +361,9 @@ public override Boolean Equals(Object obj) { return false; } - else if (obj is Mass) + else if (obj is Mass mass) { - return Equals((Mass)obj); + return Equals(mass); } return false; diff --git a/src/RapidField.SolidInstruments.Mathematics/Physics/Temperature.cs b/src/RapidField.SolidInstruments.Mathematics/Physics/Temperature.cs index 2b147e40..2c11ebbb 100644 --- a/src/RapidField.SolidInstruments.Mathematics/Physics/Temperature.cs +++ b/src/RapidField.SolidInstruments.Mathematics/Physics/Temperature.cs @@ -335,9 +335,9 @@ public override Boolean Equals(Object obj) { return false; } - else if (obj is Temperature) + else if (obj is Temperature temperature) { - return Equals((Temperature)obj); + return Equals(temperature); } return false; diff --git a/src/RapidField.SolidInstruments.Mathematics/Physics/Volume.cs b/src/RapidField.SolidInstruments.Mathematics/Physics/Volume.cs index 4c333da5..b3189f55 100644 --- a/src/RapidField.SolidInstruments.Mathematics/Physics/Volume.cs +++ b/src/RapidField.SolidInstruments.Mathematics/Physics/Volume.cs @@ -412,9 +412,9 @@ public override Boolean Equals(Object obj) { return false; } - else if (obj is Volume) + else if (obj is Volume volume) { - return Equals((Volume)obj); + return Equals(volume); } return false; diff --git a/src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryClientFactory.cs b/src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryClientFactory.cs index b3088101..ac59d10e 100644 --- a/src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryClientFactory.cs +++ b/src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryClientFactory.cs @@ -182,14 +182,17 @@ private IMessageTopicClient CreateTopicClient(IMessageTransportConnect /// A task representing the asynchronous operation. /// [DebuggerHidden] - private Task EnsureQueueExistanceAsync(IMessagingEntityPath queuePath) => Task.Factory.StartNew(async () => + private Task EnsureQueueExistanceAsync(IMessagingEntityPath queuePath) => Task.Factory.StartNew(() => { - if (Transport.QueueExists(queuePath)) + lock (MessagingEntityClient.EnsureQueueExistenceSyncRoot) { - return; - } + if (Transport.QueueExists(queuePath)) + { + return; + } - await Transport.CreateQueueAsync(queuePath).ConfigureAwait(false); + Transport.CreateQueueAsync(queuePath).Wait(); + } }); /// @@ -205,16 +208,19 @@ private Task EnsureQueueExistanceAsync(IMessagingEntityPath queuePath) => Task.F /// A task representing the asynchronous operation. /// [DebuggerHidden] - private Task EnsureSubscriptionExistanceAsync(IMessagingEntityPath topicPath, String subscriptionName) => Task.Factory.StartNew(async () => + private Task EnsureSubscriptionExistanceAsync(IMessagingEntityPath topicPath, String subscriptionName) => Task.Factory.StartNew(() => { - await EnsureTopicExistanceAsync(topicPath).ConfigureAwait(false); + EnsureTopicExistanceAsync(topicPath).Wait(); - if (Transport.SubscriptionExists(topicPath, subscriptionName)) + lock (MessagingEntityClient.EnsureSubscriptionExistenceSyncRoot) { - return; - } + if (Transport.SubscriptionExists(topicPath, subscriptionName)) + { + return; + } - await Transport.CreateSubscriptionAsync(topicPath, subscriptionName).ConfigureAwait(false); + Transport.CreateSubscriptionAsync(topicPath, subscriptionName).Wait(); + } }); /// @@ -227,14 +233,17 @@ private Task EnsureSubscriptionExistanceAsync(IMessagingEntityPath topicPath, St /// A task representing the asynchronous operation. /// [DebuggerHidden] - private Task EnsureTopicExistanceAsync(IMessagingEntityPath topicPath) => Task.Factory.StartNew(async () => + private Task EnsureTopicExistanceAsync(IMessagingEntityPath topicPath) => Task.Factory.StartNew(() => { - if (Transport.TopicExists(topicPath)) + lock (MessagingEntityClient.EnsureTopicExistenceSyncRoot) { - return; - } + if (Transport.TopicExists(topicPath)) + { + return; + } - await Transport.CreateTopicAsync(topicPath).ConfigureAwait(false); + Transport.CreateTopicAsync(topicPath).Wait(); + } }); /// diff --git a/src/RapidField.SolidInstruments.Messaging/CommandMessages/CommandMessage.cs b/src/RapidField.SolidInstruments.Messaging/CommandMessages/CommandMessage.cs new file mode 100644 index 00000000..501d3891 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/CommandMessages/CommandMessage.cs @@ -0,0 +1,59 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.ArgumentValidation; +using System; +using System.Runtime.Serialization; +using SolidInstrumentsCommand = RapidField.SolidInstruments.Command.Command; + +namespace RapidField.SolidInstruments.Messaging.CommandMessages +{ + /// + /// Represents a message that contains a command. + /// + /// + /// is the default implementation of . + /// + /// + /// The type of the associated command. + /// + [DataContract] + public abstract class CommandMessage : Message, ICommandMessage + where TCommand : SolidInstrumentsCommand, new() + { + /// + /// Initializes a new instance of the class. + /// + protected CommandMessage() + : this(new TCommand()) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated command. + /// + /// + /// is . + /// + protected CommandMessage(TCommand commandObject) + : base(commandObject.CorrelationIdentifier, Guid.NewGuid()) + { + Command = commandObject.RejectIf().IsNull(nameof(commandObject)); + } + + /// + /// Gets or sets the associated command. + /// + [DataMember] + public TCommand Command + { + get; + set; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/CommandMessages/CommandMessageListener.cs b/src/RapidField.SolidInstruments.Messaging/CommandMessages/CommandMessageListener.cs new file mode 100644 index 00000000..8a0586d9 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/CommandMessages/CommandMessageListener.cs @@ -0,0 +1,62 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Concurrency; +using System; + +namespace RapidField.SolidInstruments.Messaging.CommandMessages +{ + /// + /// Processes command messages as a listener. + /// + /// + /// The type of the associated command. + /// + /// + /// The type of the message that is listened for. + /// + public class CommandMessageListener : QueueListener + where TCommand : class, ICommand + where TMessage : class, ICommandMessage + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// is . + /// + public CommandMessageListener(ICommandMediator mediator) + : base(mediator) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Processes the specified command. + /// + /// + /// The command to process. + /// + /// + /// A processing intermediary that is used to process sub-commands. Do not process using + /// , as doing so will generally result in infinite-looping. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + protected override void Process(TMessage command, ICommandMediator mediator, IConcurrencyControlToken controlToken) => mediator.Process(command.Command); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/CommandMessages/CommandMessageTransmitter.cs b/src/RapidField.SolidInstruments.Messaging/CommandMessages/CommandMessageTransmitter.cs new file mode 100644 index 00000000..8a0097d8 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/CommandMessages/CommandMessageTransmitter.cs @@ -0,0 +1,65 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Concurrency; +using System; + +namespace RapidField.SolidInstruments.Messaging.CommandMessages +{ + /// + /// Transmits command messages to a queue. + /// + /// + /// The type of the associated command. + /// + /// + /// The type of the message that is transmitted by the transmitter. + /// + public class CommandMessageTransmitter : QueueTransmitter + where TCommand : class, ICommand + where TMessage : class, ICommandMessage + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// An appliance that facilitates implementation-specific message transmission operations. + /// + /// + /// is -or- is . + /// + public CommandMessageTransmitter(ICommandMediator mediator, IMessageTransmittingFacade facade) + : base(mediator, facade) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Processes the specified command. + /// + /// + /// The command to process. + /// + /// + /// A processing intermediary that is used to process sub-commands. Do not process using + /// , as doing so will generally result in infinite-looping. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + protected override void Process(TMessage command, ICommandMediator mediator, IConcurrencyControlToken controlToken) => base.Process(command, mediator, controlToken); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/CommandMessages/CreateDomainModelCommandMessage.cs b/src/RapidField.SolidInstruments.Messaging/CommandMessages/CreateDomainModelCommandMessage.cs new file mode 100644 index 00000000..ad1746f8 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/CommandMessages/CreateDomainModelCommandMessage.cs @@ -0,0 +1,54 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Domain; +using System; +using System.Runtime.Serialization; + +namespace RapidField.SolidInstruments.Messaging.CommandMessages +{ + /// + /// Represents a message that contains a command to create an object that models a domain construct. + /// + /// + /// is the default implementation of + /// . + /// + /// + /// The type of the associated domain model. + /// + /// + /// The type of the associated command. + /// + [DataContract] + public abstract class CreateDomainModelCommandMessage : DomainModelCommandMessage, ICreateDomainModelCommandMessage + where TModel : class, IDomainModel + where TCommand : CreateDomainModelCommand, new() + { + /// + /// Initializes a new instance of the class. + /// + protected CreateDomainModelCommandMessage() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated command. + /// + /// + /// is . + /// + protected CreateDomainModelCommandMessage(TCommand commandObject) + : base(commandObject) + { + return; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/CommandMessages/DeleteDomainModelCommandMessage.cs b/src/RapidField.SolidInstruments.Messaging/CommandMessages/DeleteDomainModelCommandMessage.cs new file mode 100644 index 00000000..39686fc8 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/CommandMessages/DeleteDomainModelCommandMessage.cs @@ -0,0 +1,54 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Domain; +using System; +using System.Runtime.Serialization; + +namespace RapidField.SolidInstruments.Messaging.CommandMessages +{ + /// + /// Represents a message that contains a command to delete an object that models a domain construct. + /// + /// + /// is the default implementation of + /// . + /// + /// + /// The type of the associated domain model. + /// + /// + /// The type of the associated command. + /// + [DataContract] + public abstract class DeleteDomainModelCommandMessage : DomainModelCommandMessage, IDeleteDomainModelCommandMessage + where TModel : class, IDomainModel + where TCommand : DeleteDomainModelCommand, new() + { + /// + /// Initializes a new instance of the class. + /// + protected DeleteDomainModelCommandMessage() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated command. + /// + /// + /// is . + /// + protected DeleteDomainModelCommandMessage(TCommand commandObject) + : base(commandObject) + { + return; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/CommandMessages/DomainCommandMessage.cs b/src/RapidField.SolidInstruments.Messaging/CommandMessages/DomainCommandMessage.cs new file mode 100644 index 00000000..6553ba09 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/CommandMessages/DomainCommandMessage.cs @@ -0,0 +1,49 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using System; +using System.Runtime.Serialization; + +namespace RapidField.SolidInstruments.Messaging.CommandMessages +{ + /// + /// Represents a message that contains a command related to domain logic. + /// + /// + /// is the default implementation of + /// . + /// + /// + /// The type of the associated command. + /// + [DataContract] + public abstract class DomainCommandMessage : CommandMessage, IDomainCommandMessage + where TCommand : DomainCommand, new() + { + /// + /// Initializes a new instance of the class. + /// + protected DomainCommandMessage() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated command. + /// + /// + /// is . + /// + protected DomainCommandMessage(TCommand commandObject) + : base(commandObject) + { + return; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/CommandMessages/DomainModelAssociatedCommandMessage.cs b/src/RapidField.SolidInstruments.Messaging/CommandMessages/DomainModelAssociatedCommandMessage.cs new file mode 100644 index 00000000..ba6b598c --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/CommandMessages/DomainModelAssociatedCommandMessage.cs @@ -0,0 +1,54 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Domain; +using System; +using System.Runtime.Serialization; + +namespace RapidField.SolidInstruments.Messaging.CommandMessages +{ + /// + /// Represents a message that contains a command related to an object that models a domain construct. + /// + /// + /// is the default implementation of + /// . + /// + /// + /// The type of the associated domain model. + /// + /// + /// The type of the associated command. + /// + [DataContract] + public abstract class DomainModelAssociatedCommandMessage : DomainModelCommandMessage, IDomainModelAssociatedCommandMessage + where TModel : class, IDomainModel + where TCommand : DomainModelAssociatedCommand, new() + { + /// + /// Initializes a new instance of the class. + /// + protected DomainModelAssociatedCommandMessage() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated command. + /// + /// + /// is . + /// + protected DomainModelAssociatedCommandMessage(TCommand commandObject) + : base(commandObject) + { + return; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/CommandMessages/DomainModelCommandMessage.cs b/src/RapidField.SolidInstruments.Messaging/CommandMessages/DomainModelCommandMessage.cs new file mode 100644 index 00000000..cc5edcbd --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/CommandMessages/DomainModelCommandMessage.cs @@ -0,0 +1,60 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Domain; +using System; +using System.Runtime.Serialization; + +namespace RapidField.SolidInstruments.Messaging.CommandMessages +{ + /// + /// Represents a message that contains a command related to an object that models a domain construct. + /// + /// + /// is the default implementation of + /// . + /// + /// + /// The type of the associated domain model. + /// + /// + /// The type of the associated command. + /// + [DataContract] + public abstract class DomainModelCommandMessage : DomainCommandMessage, IDomainModelCommandMessage + where TModel : class, IDomainModel + where TCommand : DomainModelCommand, new() + { + /// + /// Initializes a new instance of the class. + /// + protected DomainModelCommandMessage() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated command. + /// + /// + /// is . + /// + protected DomainModelCommandMessage(TCommand commandObject) + : base(commandObject) + { + return; + } + + /// + /// Gets the desired state of the associated domain model. + /// + [IgnoreDataMember] + public TModel Model => Command.Model; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/CommandMessages/ICommandMessage.cs b/src/RapidField.SolidInstruments.Messaging/CommandMessages/ICommandMessage.cs new file mode 100644 index 00000000..d5f93281 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/CommandMessages/ICommandMessage.cs @@ -0,0 +1,34 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; + +namespace RapidField.SolidInstruments.Messaging.CommandMessages +{ + /// + /// Represents a message that contains a command. + /// + /// + /// The type of the associated command. + /// + public interface ICommandMessage : ICommandMessage + where TCommand : class, ICommand + { + /// + /// Gets or sets the associated command. + /// + public TCommand Command + { + get; + set; + } + } + + /// + /// Represents a message that contains a command. + /// + public interface ICommandMessage : IMessage + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/CommandMessages/ICreateDomainModelCommandMessage.cs b/src/RapidField.SolidInstruments.Messaging/CommandMessages/ICreateDomainModelCommandMessage.cs new file mode 100644 index 00000000..da637371 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/CommandMessages/ICreateDomainModelCommandMessage.cs @@ -0,0 +1,24 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Domain; + +namespace RapidField.SolidInstruments.Messaging.CommandMessages +{ + /// + /// Represents a message that contains a command to create an object that models a domain construct. + /// + /// + /// The type of the associated domain model. + /// + /// + /// The type of the associated command. + /// + public interface ICreateDomainModelCommandMessage : IDomainModelCommandMessage + where TModel : class, IDomainModel + where TCommand : class, ICreateDomainModelCommand + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/CommandMessages/IDeleteDomainModelCommandMessage.cs b/src/RapidField.SolidInstruments.Messaging/CommandMessages/IDeleteDomainModelCommandMessage.cs new file mode 100644 index 00000000..d596da18 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/CommandMessages/IDeleteDomainModelCommandMessage.cs @@ -0,0 +1,24 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Domain; + +namespace RapidField.SolidInstruments.Messaging.CommandMessages +{ + /// + /// Represents a message that contains a command to delete an object that models a domain construct. + /// + /// + /// The type of the associated domain model. + /// + /// + /// The type of the associated command. + /// + public interface IDeleteDomainModelCommandMessage : IDomainModelCommandMessage + where TModel : class, IDomainModel + where TCommand : class, IDeleteDomainModelCommand + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/CommandMessages/IDomainCommandMessage.cs b/src/RapidField.SolidInstruments.Messaging/CommandMessages/IDomainCommandMessage.cs new file mode 100644 index 00000000..6c029930 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/CommandMessages/IDomainCommandMessage.cs @@ -0,0 +1,19 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; + +namespace RapidField.SolidInstruments.Messaging.CommandMessages +{ + /// + /// Represents a message that contains a command related to domain logic. + /// + /// + /// The type of the associated command. + /// + public interface IDomainCommandMessage : ICommandMessage + where TCommand : class, IDomainCommand + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/CommandMessages/IDomainModelAssociatedCommandMessage.cs b/src/RapidField.SolidInstruments.Messaging/CommandMessages/IDomainModelAssociatedCommandMessage.cs new file mode 100644 index 00000000..b883e96b --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/CommandMessages/IDomainModelAssociatedCommandMessage.cs @@ -0,0 +1,24 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Domain; + +namespace RapidField.SolidInstruments.Messaging.CommandMessages +{ + /// + /// Represents a message that contains a command related to an object that models a domain construct. + /// + /// + /// The type of the associated domain model. + /// + /// + /// The type of the associated command. + /// + public interface IDomainModelAssociatedCommandMessage : IDomainModelCommandMessage + where TModel : class, IDomainModel + where TCommand : class, IDomainModelAssociatedCommand + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/CommandMessages/IDomainModelCommandMessage.cs b/src/RapidField.SolidInstruments.Messaging/CommandMessages/IDomainModelCommandMessage.cs new file mode 100644 index 00000000..38dcbd90 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/CommandMessages/IDomainModelCommandMessage.cs @@ -0,0 +1,31 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Domain; + +namespace RapidField.SolidInstruments.Messaging.CommandMessages +{ + /// + /// Represents a message that contains a command related to an object that models a domain construct. + /// + /// + /// The type of the associated domain model. + /// + /// + /// The type of the associated command. + /// + public interface IDomainModelCommandMessage : IDomainCommandMessage + where TModel : class, IDomainModel + where TCommand : class, IDomainModelCommand + { + /// + /// Gets the desired state of the associated domain model. + /// + public TModel Model + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/CommandMessages/IUpdateDomainModelCommandMessage.cs b/src/RapidField.SolidInstruments.Messaging/CommandMessages/IUpdateDomainModelCommandMessage.cs new file mode 100644 index 00000000..2f0dd62c --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/CommandMessages/IUpdateDomainModelCommandMessage.cs @@ -0,0 +1,24 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Domain; + +namespace RapidField.SolidInstruments.Messaging.CommandMessages +{ + /// + /// Represents a message that contains a command to update an object that models a domain construct. + /// + /// + /// The type of the associated domain model. + /// + /// + /// The type of the associated command. + /// + public interface IUpdateDomainModelCommandMessage : IDomainModelCommandMessage + where TModel : class, IDomainModel + where TCommand : class, IUpdateDomainModelCommand + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/CommandMessages/UpdateDomainModelCommandMessage.cs b/src/RapidField.SolidInstruments.Messaging/CommandMessages/UpdateDomainModelCommandMessage.cs new file mode 100644 index 00000000..e8756e51 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/CommandMessages/UpdateDomainModelCommandMessage.cs @@ -0,0 +1,54 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Domain; +using System; +using System.Runtime.Serialization; + +namespace RapidField.SolidInstruments.Messaging.CommandMessages +{ + /// + /// Represents a message that contains a command to update an object that models a domain construct. + /// + /// + /// is the default implementation of + /// . + /// + /// + /// The type of the associated domain model. + /// + /// + /// The type of the associated command. + /// + [DataContract] + public abstract class UpdateDomainModelCommandMessage : DomainModelCommandMessage, IUpdateDomainModelCommandMessage + where TModel : class, IDomainModel + where TCommand : UpdateDomainModelCommand, new() + { + /// + /// Initializes a new instance of the class. + /// + protected UpdateDomainModelCommandMessage() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated command. + /// + /// + /// is . + /// + protected UpdateDomainModelCommandMessage(TCommand commandObject) + : base(commandObject) + { + return; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/EventMessages/ApplicationPausedEventMessage.cs b/src/RapidField.SolidInstruments.Messaging/EventMessages/ApplicationPausedEventMessage.cs index 134b8253..62a29b58 100644 --- a/src/RapidField.SolidInstruments.Messaging/EventMessages/ApplicationPausedEventMessage.cs +++ b/src/RapidField.SolidInstruments.Messaging/EventMessages/ApplicationPausedEventMessage.cs @@ -40,52 +40,6 @@ public ApplicationPausedEventMessage(ApplicationPausedEvent eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - public ApplicationPausedEventMessage(ApplicationPausedEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// A unique identifier for the message. - /// - /// - /// is . - /// - /// - /// is equal to -or- is - /// equal to . - /// - public ApplicationPausedEventMessage(ApplicationPausedEvent eventObject, Guid correlationIdentifier, Guid identifier) - : base(eventObject, correlationIdentifier, identifier) - { - return; - } } /// @@ -125,51 +79,5 @@ protected ApplicationPausedEventMessage(TEvent eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - protected ApplicationPausedEventMessage(TEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// A unique identifier for the message. - /// - /// - /// is . - /// - /// - /// is equal to -or- is - /// equal to . - /// - protected ApplicationPausedEventMessage(TEvent eventObject, Guid correlationIdentifier, Guid identifier) - : base(eventObject, correlationIdentifier, identifier) - { - return; - } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/EventMessages/ApplicationResumedEventMessage.cs b/src/RapidField.SolidInstruments.Messaging/EventMessages/ApplicationResumedEventMessage.cs index 9bf0b6f0..235a7991 100644 --- a/src/RapidField.SolidInstruments.Messaging/EventMessages/ApplicationResumedEventMessage.cs +++ b/src/RapidField.SolidInstruments.Messaging/EventMessages/ApplicationResumedEventMessage.cs @@ -41,52 +41,6 @@ public ApplicationResumedEventMessage(ApplicationResumedEvent eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - public ApplicationResumedEventMessage(ApplicationResumedEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// A unique identifier for the message. - /// - /// - /// is . - /// - /// - /// is equal to -or- is - /// equal to . - /// - public ApplicationResumedEventMessage(ApplicationResumedEvent eventObject, Guid correlationIdentifier, Guid identifier) - : base(eventObject, correlationIdentifier, identifier) - { - return; - } } /// @@ -126,51 +80,5 @@ protected ApplicationResumedEventMessage(TEvent eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - protected ApplicationResumedEventMessage(TEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// A unique identifier for the message. - /// - /// - /// is . - /// - /// - /// is equal to -or- is - /// equal to . - /// - protected ApplicationResumedEventMessage(TEvent eventObject, Guid correlationIdentifier, Guid identifier) - : base(eventObject, correlationIdentifier, identifier) - { - return; - } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/EventMessages/ApplicationStartedEventMessage.cs b/src/RapidField.SolidInstruments.Messaging/EventMessages/ApplicationStartedEventMessage.cs index 8cbdb7c8..daa4dfb8 100644 --- a/src/RapidField.SolidInstruments.Messaging/EventMessages/ApplicationStartedEventMessage.cs +++ b/src/RapidField.SolidInstruments.Messaging/EventMessages/ApplicationStartedEventMessage.cs @@ -41,52 +41,6 @@ public ApplicationStartedEventMessage(ApplicationStartedEvent eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - public ApplicationStartedEventMessage(ApplicationStartedEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// A unique identifier for the message. - /// - /// - /// is . - /// - /// - /// is equal to -or- is - /// equal to . - /// - public ApplicationStartedEventMessage(ApplicationStartedEvent eventObject, Guid correlationIdentifier, Guid identifier) - : base(eventObject, correlationIdentifier, identifier) - { - return; - } } /// @@ -126,51 +80,5 @@ protected ApplicationStartedEventMessage(TEvent eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - protected ApplicationStartedEventMessage(TEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// A unique identifier for the message. - /// - /// - /// is . - /// - /// - /// is equal to -or- is - /// equal to . - /// - protected ApplicationStartedEventMessage(TEvent eventObject, Guid correlationIdentifier, Guid identifier) - : base(eventObject, correlationIdentifier, identifier) - { - return; - } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/EventMessages/ApplicationStateEventMessage.cs b/src/RapidField.SolidInstruments.Messaging/EventMessages/ApplicationStateEventMessage.cs index bfc6aec5..60a0606f 100644 --- a/src/RapidField.SolidInstruments.Messaging/EventMessages/ApplicationStateEventMessage.cs +++ b/src/RapidField.SolidInstruments.Messaging/EventMessages/ApplicationStateEventMessage.cs @@ -40,52 +40,6 @@ public ApplicationStateEventMessage(ApplicationStateEvent eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - public ApplicationStateEventMessage(ApplicationStateEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// A unique identifier for the message. - /// - /// - /// is . - /// - /// - /// is equal to -or- is - /// equal to . - /// - public ApplicationStateEventMessage(ApplicationStateEvent eventObject, Guid correlationIdentifier, Guid identifier) - : base(eventObject, correlationIdentifier, identifier) - { - return; - } } /// @@ -125,51 +79,5 @@ protected ApplicationStateEventMessage(TEvent eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - protected ApplicationStateEventMessage(TEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// A unique identifier for the message. - /// - /// - /// is . - /// - /// - /// is equal to -or- is - /// equal to . - /// - protected ApplicationStateEventMessage(TEvent eventObject, Guid correlationIdentifier, Guid identifier) - : base(eventObject, correlationIdentifier, identifier) - { - return; - } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/EventMessages/ApplicationStoppedEventMessage.cs b/src/RapidField.SolidInstruments.Messaging/EventMessages/ApplicationStoppedEventMessage.cs index 4b78c95f..b6e366a9 100644 --- a/src/RapidField.SolidInstruments.Messaging/EventMessages/ApplicationStoppedEventMessage.cs +++ b/src/RapidField.SolidInstruments.Messaging/EventMessages/ApplicationStoppedEventMessage.cs @@ -41,52 +41,6 @@ public ApplicationStoppedEventMessage(ApplicationStoppedEvent eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - public ApplicationStoppedEventMessage(ApplicationStoppedEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// A unique identifier for the message. - /// - /// - /// is . - /// - /// - /// is equal to -or- is - /// equal to . - /// - public ApplicationStoppedEventMessage(ApplicationStoppedEvent eventObject, Guid correlationIdentifier, Guid identifier) - : base(eventObject, correlationIdentifier, identifier) - { - return; - } } /// @@ -126,51 +80,5 @@ protected ApplicationStoppedEventMessage(TEvent eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - protected ApplicationStoppedEventMessage(TEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// A unique identifier for the message. - /// - /// - /// is . - /// - /// - /// is equal to -or- is - /// equal to . - /// - protected ApplicationStoppedEventMessage(TEvent eventObject, Guid correlationIdentifier, Guid identifier) - : base(eventObject, correlationIdentifier, identifier) - { - return; - } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/EventMessages/DomainEventMessage.cs b/src/RapidField.SolidInstruments.Messaging/EventMessages/DomainEventMessage.cs index 3cda2a23..6cffc8c5 100644 --- a/src/RapidField.SolidInstruments.Messaging/EventMessages/DomainEventMessage.cs +++ b/src/RapidField.SolidInstruments.Messaging/EventMessages/DomainEventMessage.cs @@ -40,52 +40,6 @@ public DomainEventMessage(DomainEvent eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - public DomainEventMessage(DomainEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// A unique identifier for the message. - /// - /// - /// is . - /// - /// - /// is equal to -or- is - /// equal to . - /// - public DomainEventMessage(DomainEvent eventObject, Guid correlationIdentifier, Guid identifier) - : base(eventObject, correlationIdentifier, identifier) - { - return; - } } /// @@ -124,51 +78,5 @@ protected DomainEventMessage(TEvent eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - protected DomainEventMessage(TEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// A unique identifier for the message. - /// - /// - /// is . - /// - /// - /// is equal to -or- is - /// equal to . - /// - protected DomainEventMessage(TEvent eventObject, Guid correlationIdentifier, Guid identifier) - : base(eventObject, correlationIdentifier, identifier) - { - return; - } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelAssociatedEventMessage.cs b/src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelAssociatedEventMessage.cs index b3f956fc..9150e460 100644 --- a/src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelAssociatedEventMessage.cs +++ b/src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelAssociatedEventMessage.cs @@ -50,51 +50,5 @@ protected DomainModelAssociatedEventMessage(TEvent eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - protected DomainModelAssociatedEventMessage(TEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// A unique identifier for the message. - /// - /// - /// is . - /// - /// - /// is equal to -or- is - /// equal to . - /// - protected DomainModelAssociatedEventMessage(TEvent eventObject, Guid correlationIdentifier, Guid identifier) - : base(eventObject, correlationIdentifier, identifier) - { - return; - } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelCreatedEventMessage.cs b/src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelCreatedEventMessage.cs index 80d46313..f2af4616 100644 --- a/src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelCreatedEventMessage.cs +++ b/src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelCreatedEventMessage.cs @@ -50,51 +50,5 @@ protected DomainModelCreatedEventMessage(TEvent eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - protected DomainModelCreatedEventMessage(TEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// A unique identifier for the message. - /// - /// - /// is . - /// - /// - /// is equal to -or- is - /// equal to . - /// - protected DomainModelCreatedEventMessage(TEvent eventObject, Guid correlationIdentifier, Guid identifier) - : base(eventObject, correlationIdentifier, identifier) - { - return; - } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelDeletedEventMessage.cs b/src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelDeletedEventMessage.cs index af8292f0..9ce89b57 100644 --- a/src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelDeletedEventMessage.cs +++ b/src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelDeletedEventMessage.cs @@ -50,51 +50,5 @@ protected DomainModelDeletedEventMessage(TEvent eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - protected DomainModelDeletedEventMessage(TEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// A unique identifier for the message. - /// - /// - /// is . - /// - /// - /// is equal to -or- is - /// equal to . - /// - protected DomainModelDeletedEventMessage(TEvent eventObject, Guid correlationIdentifier, Guid identifier) - : base(eventObject, correlationIdentifier, identifier) - { - return; - } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelEventMessage.cs b/src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelEventMessage.cs index 578b4d77..be10ba78 100644 --- a/src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelEventMessage.cs +++ b/src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelEventMessage.cs @@ -51,52 +51,6 @@ protected DomainModelEventMessage(TEvent eventObject) return; } - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - protected DomainModelEventMessage(TEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// A unique identifier for the message. - /// - /// - /// is . - /// - /// - /// is equal to -or- is - /// equal to . - /// - protected DomainModelEventMessage(TEvent eventObject, Guid correlationIdentifier, Guid identifier) - : base(eventObject, correlationIdentifier, identifier) - { - return; - } - /// /// Gets the resulting state of the associated domain model. /// diff --git a/src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelUpdatedEventMessage.cs b/src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelUpdatedEventMessage.cs index 83453463..5258ad38 100644 --- a/src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelUpdatedEventMessage.cs +++ b/src/RapidField.SolidInstruments.Messaging/EventMessages/DomainModelUpdatedEventMessage.cs @@ -50,51 +50,5 @@ protected DomainModelUpdatedEventMessage(TEvent eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - protected DomainModelUpdatedEventMessage(TEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// A unique identifier for the message. - /// - /// - /// is . - /// - /// - /// is equal to -or- is - /// equal to . - /// - protected DomainModelUpdatedEventMessage(TEvent eventObject, Guid correlationIdentifier, Guid identifier) - : base(eventObject, correlationIdentifier, identifier) - { - return; - } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/EventMessages/ErrorEventMessage.cs b/src/RapidField.SolidInstruments.Messaging/EventMessages/ErrorEventMessage.cs index 14fd8744..1a2f2c59 100644 --- a/src/RapidField.SolidInstruments.Messaging/EventMessages/ErrorEventMessage.cs +++ b/src/RapidField.SolidInstruments.Messaging/EventMessages/ErrorEventMessage.cs @@ -40,52 +40,6 @@ public ErrorEventMessage(ErrorEvent eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - public ErrorEventMessage(ErrorEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// A unique identifier for the message. - /// - /// - /// is . - /// - /// - /// is equal to -or- is - /// equal to . - /// - public ErrorEventMessage(ErrorEvent eventObject, Guid correlationIdentifier, Guid identifier) - : base(eventObject, correlationIdentifier, identifier) - { - return; - } } /// @@ -124,51 +78,5 @@ protected ErrorEventMessage(TEvent eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - protected ErrorEventMessage(TEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// A unique identifier for the message. - /// - /// - /// is . - /// - /// - /// is equal to -or- is - /// equal to . - /// - protected ErrorEventMessage(TEvent eventObject, Guid correlationIdentifier, Guid identifier) - : base(eventObject, correlationIdentifier, identifier) - { - return; - } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/EventMessages/EventMessage.cs b/src/RapidField.SolidInstruments.Messaging/EventMessages/EventMessage.cs index f09b2087..06155461 100644 --- a/src/RapidField.SolidInstruments.Messaging/EventMessages/EventMessage.cs +++ b/src/RapidField.SolidInstruments.Messaging/EventMessages/EventMessage.cs @@ -41,52 +41,6 @@ public EventMessage(Event eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - public EventMessage(Event eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// A unique identifier for the message. - /// - /// - /// is . - /// - /// - /// is equal to -or- is - /// equal to . - /// - public EventMessage(Event eventObject, Guid correlationIdentifier, Guid identifier) - : base(eventObject, correlationIdentifier, identifier) - { - return; - } } /// @@ -121,53 +75,7 @@ protected EventMessage() /// is . /// protected EventMessage(TEvent eventObject) - : this(eventObject, Guid.NewGuid()) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - protected EventMessage(TEvent eventObject, Guid correlationIdentifier) - : this(eventObject, correlationIdentifier, Guid.NewGuid()) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// A unique identifier for the message. - /// - /// - /// is . - /// - /// - /// is equal to -or- is - /// equal to . - /// - protected EventMessage(TEvent eventObject, Guid correlationIdentifier, Guid identifier) - : base(correlationIdentifier, identifier) + : base(eventObject.CorrelationIdentifier, Guid.NewGuid()) { Event = eventObject.RejectIf().IsNull(nameof(eventObject)); } diff --git a/src/RapidField.SolidInstruments.Messaging/EventMessages/EventMessageListener.cs b/src/RapidField.SolidInstruments.Messaging/EventMessages/EventMessageListener.cs new file mode 100644 index 00000000..f67be260 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/EventMessages/EventMessageListener.cs @@ -0,0 +1,63 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Concurrency; +using RapidField.SolidInstruments.EventAuthoring; +using System; + +namespace RapidField.SolidInstruments.Messaging.EventMessages +{ + /// + /// Processes event messages as a listener. + /// + /// + /// The type of the associated event. + /// + /// + /// The type of the message that is listened for. + /// + public class EventMessageListener : TopicListener + where TEvent : class, IEvent + where TMessage : class, IEventMessage + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// is . + /// + public EventMessageListener(ICommandMediator mediator) + : base(mediator) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Processes the specified command. + /// + /// + /// The command to process. + /// + /// + /// A processing intermediary that is used to process sub-commands. Do not process using + /// , as doing so will generally result in infinite-looping. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + protected override void Process(TMessage command, ICommandMediator mediator, IConcurrencyControlToken controlToken) => mediator.Process(command.Event); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/EventMessages/EventMessageTransmitter.cs b/src/RapidField.SolidInstruments.Messaging/EventMessages/EventMessageTransmitter.cs new file mode 100644 index 00000000..f692e4dc --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/EventMessages/EventMessageTransmitter.cs @@ -0,0 +1,66 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Concurrency; +using RapidField.SolidInstruments.EventAuthoring; +using System; + +namespace RapidField.SolidInstruments.Messaging.EventMessages +{ + /// + /// Transmits event messages to a topic. + /// + /// + /// The type of the associated event. + /// + /// + /// The type of the message that is transmitted by the transmitter. + /// + public class EventMessageTransmitter : TopicTransmitter + where TEvent : class, IEvent + where TMessage : class, IEventMessage + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// An appliance that facilitates implementation-specific message transmission operations. + /// + /// + /// is -or- is . + /// + public EventMessageTransmitter(ICommandMediator mediator, IMessageTransmittingFacade facade) + : base(mediator, facade) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Processes the specified command. + /// + /// + /// The command to process. + /// + /// + /// A processing intermediary that is used to process sub-commands. Do not process using + /// , as doing so will generally result in infinite-looping. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + protected override void Process(TMessage command, ICommandMediator mediator, IConcurrencyControlToken controlToken) => base.Process(command, mediator, controlToken); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/EventMessages/ExceptionRaisedEventMessage.cs b/src/RapidField.SolidInstruments.Messaging/EventMessages/ExceptionRaisedEventMessage.cs index c625f230..59ed5819 100644 --- a/src/RapidField.SolidInstruments.Messaging/EventMessages/ExceptionRaisedEventMessage.cs +++ b/src/RapidField.SolidInstruments.Messaging/EventMessages/ExceptionRaisedEventMessage.cs @@ -40,52 +40,6 @@ public ExceptionRaisedEventMessage(ExceptionRaisedEvent eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - public ExceptionRaisedEventMessage(ExceptionRaisedEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// A unique identifier for the message. - /// - /// - /// is . - /// - /// - /// is equal to -or- is - /// equal to . - /// - public ExceptionRaisedEventMessage(ExceptionRaisedEvent eventObject, Guid correlationIdentifier, Guid identifier) - : base(eventObject, correlationIdentifier, identifier) - { - return; - } } /// @@ -125,51 +79,5 @@ protected ExceptionRaisedEventMessage(TEvent eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - protected ExceptionRaisedEventMessage(TEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// A unique identifier for the message. - /// - /// - /// is . - /// - /// - /// is equal to -or- is - /// equal to . - /// - protected ExceptionRaisedEventMessage(TEvent eventObject, Guid correlationIdentifier, Guid identifier) - : base(eventObject, correlationIdentifier, identifier) - { - return; - } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/EventMessages/GeneralInformationEventMessage.cs b/src/RapidField.SolidInstruments.Messaging/EventMessages/GeneralInformationEventMessage.cs index 1d598909..41cf4658 100644 --- a/src/RapidField.SolidInstruments.Messaging/EventMessages/GeneralInformationEventMessage.cs +++ b/src/RapidField.SolidInstruments.Messaging/EventMessages/GeneralInformationEventMessage.cs @@ -41,52 +41,6 @@ public GeneralInformationEventMessage(GeneralInformationEvent eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - public GeneralInformationEventMessage(GeneralInformationEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// A unique identifier for the message. - /// - /// - /// is . - /// - /// - /// is equal to -or- is - /// equal to . - /// - public GeneralInformationEventMessage(GeneralInformationEvent eventObject, Guid correlationIdentifier, Guid identifier) - : base(eventObject, correlationIdentifier, identifier) - { - return; - } } /// @@ -126,51 +80,5 @@ protected GeneralInformationEventMessage(TEvent eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - protected GeneralInformationEventMessage(TEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// A unique identifier for the message. - /// - /// - /// is . - /// - /// - /// is equal to -or- is - /// equal to . - /// - protected GeneralInformationEventMessage(TEvent eventObject, Guid correlationIdentifier, Guid identifier) - : base(eventObject, correlationIdentifier, identifier) - { - return; - } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/EventMessages/SecurityEventMessage.cs b/src/RapidField.SolidInstruments.Messaging/EventMessages/SecurityEventMessage.cs index 01182971..d7948f5d 100644 --- a/src/RapidField.SolidInstruments.Messaging/EventMessages/SecurityEventMessage.cs +++ b/src/RapidField.SolidInstruments.Messaging/EventMessages/SecurityEventMessage.cs @@ -40,52 +40,6 @@ public SecurityEventMessage(SecurityEvent eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - public SecurityEventMessage(SecurityEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// A unique identifier for the message. - /// - /// - /// is . - /// - /// - /// is equal to -or- is - /// equal to . - /// - public SecurityEventMessage(SecurityEvent eventObject, Guid correlationIdentifier, Guid identifier) - : base(eventObject, correlationIdentifier, identifier) - { - return; - } } /// @@ -124,51 +78,5 @@ protected SecurityEventMessage(TEvent eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - protected SecurityEventMessage(TEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// A unique identifier for the message. - /// - /// - /// is . - /// - /// - /// is equal to -or- is - /// equal to . - /// - protected SecurityEventMessage(TEvent eventObject, Guid correlationIdentifier, Guid identifier) - : base(eventObject, correlationIdentifier, identifier) - { - return; - } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/EventMessages/SystemStateEventMessage.cs b/src/RapidField.SolidInstruments.Messaging/EventMessages/SystemStateEventMessage.cs index 654aa59d..30cef5b5 100644 --- a/src/RapidField.SolidInstruments.Messaging/EventMessages/SystemStateEventMessage.cs +++ b/src/RapidField.SolidInstruments.Messaging/EventMessages/SystemStateEventMessage.cs @@ -40,52 +40,6 @@ public SystemStateEventMessage(SystemStateEvent eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - public SystemStateEventMessage(SystemStateEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// A unique identifier for the message. - /// - /// - /// is . - /// - /// - /// is equal to -or- is - /// equal to . - /// - public SystemStateEventMessage(SystemStateEvent eventObject, Guid correlationIdentifier, Guid identifier) - : base(eventObject, correlationIdentifier, identifier) - { - return; - } } /// @@ -125,51 +79,5 @@ protected SystemStateEventMessage(TEvent eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - protected SystemStateEventMessage(TEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// A unique identifier for the message. - /// - /// - /// is . - /// - /// - /// is equal to -or- is - /// equal to . - /// - protected SystemStateEventMessage(TEvent eventObject, Guid correlationIdentifier, Guid identifier) - : base(eventObject, correlationIdentifier, identifier) - { - return; - } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/EventMessages/TransactionEventMessage.cs b/src/RapidField.SolidInstruments.Messaging/EventMessages/TransactionEventMessage.cs index 87854cf9..af881186 100644 --- a/src/RapidField.SolidInstruments.Messaging/EventMessages/TransactionEventMessage.cs +++ b/src/RapidField.SolidInstruments.Messaging/EventMessages/TransactionEventMessage.cs @@ -40,52 +40,6 @@ public TransactionEventMessage(TransactionEvent eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - public TransactionEventMessage(TransactionEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// A unique identifier for the message. - /// - /// - /// is . - /// - /// - /// is equal to -or- is - /// equal to . - /// - public TransactionEventMessage(TransactionEvent eventObject, Guid correlationIdentifier, Guid identifier) - : base(eventObject, correlationIdentifier, identifier) - { - return; - } } /// @@ -125,51 +79,5 @@ protected TransactionEventMessage(TEvent eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - protected TransactionEventMessage(TEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// A unique identifier for the message. - /// - /// - /// is . - /// - /// - /// is equal to -or- is - /// equal to . - /// - protected TransactionEventMessage(TEvent eventObject, Guid correlationIdentifier, Guid identifier) - : base(eventObject, correlationIdentifier, identifier) - { - return; - } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/EventMessages/UserActionEventMessage.cs b/src/RapidField.SolidInstruments.Messaging/EventMessages/UserActionEventMessage.cs index d040c11f..b1a7d457 100644 --- a/src/RapidField.SolidInstruments.Messaging/EventMessages/UserActionEventMessage.cs +++ b/src/RapidField.SolidInstruments.Messaging/EventMessages/UserActionEventMessage.cs @@ -40,52 +40,6 @@ public UserActionEventMessage(UserActionEvent eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - public UserActionEventMessage(UserActionEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// A unique identifier for the message. - /// - /// - /// is . - /// - /// - /// is equal to -or- is - /// equal to . - /// - public UserActionEventMessage(UserActionEvent eventObject, Guid correlationIdentifier, Guid identifier) - : base(eventObject, correlationIdentifier, identifier) - { - return; - } } /// @@ -125,51 +79,5 @@ protected UserActionEventMessage(TEvent eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - protected UserActionEventMessage(TEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// A unique identifier for the message. - /// - /// - /// is . - /// - /// - /// is equal to -or- is - /// equal to . - /// - protected UserActionEventMessage(TEvent eventObject, Guid correlationIdentifier, Guid identifier) - : base(eventObject, correlationIdentifier, identifier) - { - return; - } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/IMessageBase.cs b/src/RapidField.SolidInstruments.Messaging/IMessageBase.cs index a232e367..3ce00653 100644 --- a/src/RapidField.SolidInstruments.Messaging/IMessageBase.cs +++ b/src/RapidField.SolidInstruments.Messaging/IMessageBase.cs @@ -12,15 +12,6 @@ namespace RapidField.SolidInstruments.Messaging /// public interface IMessageBase : ICommandBase { - /// - /// Gets or sets a unique identifier that is assigned to related messages. - /// - public Guid CorrelationIdentifier - { - get; - set; - } - /// /// Gets or sets a unique identifier for the message. /// diff --git a/src/RapidField.SolidInstruments.Messaging/MessageListeningFacade.cs b/src/RapidField.SolidInstruments.Messaging/MessageListeningFacade.cs index cf4f7b4f..e77d2fb2 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessageListeningFacade.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessageListeningFacade.cs @@ -623,8 +623,8 @@ private Task ExecuteFailurePolicyAsync(Action messageHandler [DebuggerHidden] private Task TransmitReceiverExceptionAsync(Exception raisedException, Guid correlationIdentifier) { - var exceptionRaisedEvent = new ExceptionRaisedEvent(ApplicationIdentity, raisedException, ExceptionRaisedMessageEventVerbosity); - var exceptionRaisedEventMessage = new ExceptionRaisedEventMessage(exceptionRaisedEvent, correlationIdentifier); + var exceptionRaisedEvent = new ExceptionRaisedEvent(ApplicationIdentity, raisedException, ExceptionRaisedMessageEventVerbosity, correlationIdentifier); + var exceptionRaisedEventMessage = new ExceptionRaisedEventMessage(exceptionRaisedEvent); return TransmitReceiverExceptionAsync(exceptionRaisedEventMessage, ExceptionRaisedMessageEntityType); } diff --git a/src/RapidField.SolidInstruments.Messaging/MessagingEntityPath.cs b/src/RapidField.SolidInstruments.Messaging/MessagingEntityPath.cs index a54246ff..b4da416c 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessagingEntityPath.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessagingEntityPath.cs @@ -49,7 +49,8 @@ public MessagingEntityPath() /// A collection of labels, or an empty collection if there are no labels. /// /// - /// contains more than three elements. + /// contains more than three elements -or- an exception was raised while evaluating the data + /// contract information for . /// /// /// is . @@ -62,7 +63,7 @@ public MessagingEntityPath() /// [DebuggerHidden] internal MessagingEntityPath(Type messageType, String prefix, params String[] labels) - : this(messageType.RejectIf().IsNull(nameof(messageType)).TargetArgument.Name, prefix, ExtractLabel(labels, 0), ExtractLabel(labels, 1), ExtractLabel(labels, 2)) + : this(ExtractMessageTypeName(messageType), prefix, ExtractLabel(labels, 0), ExtractLabel(labels, 1), ExtractLabel(labels, 2)) { if (labels.IsNullOrEmpty()) { @@ -340,9 +341,9 @@ public override Boolean Equals(Object obj) { return false; } - else if (obj is IMessagingEntityPath) + else if (obj is IMessagingEntityPath path) { - return Equals((IMessagingEntityPath)obj); + return Equals(path); } return false; @@ -447,6 +448,43 @@ private static String ExtractLabel(String[] labels, Int32 index) return label.IsNullOrEmpty() ? null : label; } + /// + /// Determines an appropriate message type name for the specified message type. + /// + /// + /// The message type. + /// + /// + /// The resulting message type name. + /// + /// + /// An exception was raised while evaluating the data contract information for . + /// + /// + /// is . + /// + [DebuggerHidden] + private static String ExtractMessageTypeName(Type messageType) + { + try + { + if (messageType.RejectIf().IsNull(nameof(messageType)).TargetArgument.GetCustomAttributes(typeof(DataContractAttribute), false).FirstOrDefault() is DataContractAttribute dataContractAttribute && dataContractAttribute.Name.IsNullOrEmpty() == false) + { + return dataContractAttribute.Name.Compress(); + } + } + catch (ArgumentNullException) + { + throw; + } + catch (Exception exception) + { + throw new ArgumentException($"An exception was raised while evaluating the data contract information for {messageType.FullName}. See inner exception.", nameof(messageType), exception); + } + + return messageType.Name; + } + /// /// Converts the specified representation of a messaging entity path to its /// equivalent. diff --git a/src/RapidField.SolidInstruments.Messaging/Service/HeartbeatScheduleItem.cs b/src/RapidField.SolidInstruments.Messaging/Service/HeartbeatScheduleItem.cs index 921211cf..7f81e4a9 100644 --- a/src/RapidField.SolidInstruments.Messaging/Service/HeartbeatScheduleItem.cs +++ b/src/RapidField.SolidInstruments.Messaging/Service/HeartbeatScheduleItem.cs @@ -240,9 +240,9 @@ public override Boolean Equals(Object obj) { return false; } - else if (obj is IHeartbeatScheduleItem) + else if (obj is IHeartbeatScheduleItem item) { - return Equals((IHeartbeatScheduleItem)obj); + return Equals(item); } return false; diff --git a/src/RapidField.SolidInstruments.Messaging/Service/MessagingServiceExecutor.cs b/src/RapidField.SolidInstruments.Messaging/Service/MessagingServiceExecutor.cs index b168edcc..83b292f7 100644 --- a/src/RapidField.SolidInstruments.Messaging/Service/MessagingServiceExecutor.cs +++ b/src/RapidField.SolidInstruments.Messaging/Service/MessagingServiceExecutor.cs @@ -45,11 +45,54 @@ public abstract class MessagingServiceExecutor is . /// protected MessagingServiceExecutor(String serviceName) + : this(serviceName, DefaultRunsContinuouslyValue) + { + return; + } + + /// + /// Initializes a new instance of the + /// class. + /// + /// + /// The name of the service. + /// + /// + /// A value indicating whether or not the service should schedule heartbeat messages and stay running indefinitely. The + /// default value is . + /// + /// + /// is empty. + /// + /// + /// is . + /// + protected MessagingServiceExecutor(String serviceName, Boolean runsContinuously) : base(serviceName) { LazyHeartbeatSchedule = new Lazy(CreateHeartbeatSchedule, LazyThreadSafetyMode.ExecutionAndPublication); LazyHeartbeatTimers = new Lazy>(() => new List(), LazyThreadSafetyMode.ExecutionAndPublication); LazyListeningProfile = new Lazy(CreateListeningProfile, LazyThreadSafetyMode.ExecutionAndPublication); + RunsContinuously = runsContinuously; + } + + /// + /// Unlocks waiting threads and ends execution of the service. + /// + /// + /// The object is disposed. + /// + [DebuggerHidden] + internal void EndExecution() + { + if (ExecutionLifetime is null) + { + return; + } + else if (ExecutionLifetime.IsAlive) + { + ExecutionLifetime.End(); + } } /// @@ -113,12 +156,19 @@ protected sealed override void Execute(IDependencyScope dependencyScope, IConfig try { AddSubscriptions(ListeningProfile, ApplicationConfiguration); - StartHeartbeats(); - executionLifetime.KeepAlive(); + + if (RunsContinuously) + { + StartHeartbeats(); + executionLifetime.KeepAlive(); + } } finally { - StopHeartbeats(); + if (RunsContinuously) + { + StopHeartbeats(); + } using (var childScope = dependencyScope.CreateChildScope()) { @@ -293,6 +343,13 @@ private Task TransmitHeartbeatMessageAsync(IHeartbeatScheduleItem scheduleItem) [DebuggerBrowsable(DebuggerBrowsableState.Never)] private IMessageListeningProfile ListeningProfile => LazyListeningProfile.Value; + /// + /// Represents a default value indicating whether or not the service should schedule heartbeat messages and stay running + /// indefinitely. The + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Boolean DefaultRunsContinuouslyValue = true; + /// /// Represents the lazily-initialized heartbeat message schedule for the service. /// @@ -310,5 +367,12 @@ private Task TransmitHeartbeatMessageAsync(IHeartbeatScheduleItem scheduleItem) /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Lazy LazyListeningProfile; + + /// + /// Represents a value indicating whether or not the service should schedule heartbeat messages and stay running + /// indefinitely. The + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly Boolean RunsContinuously; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageLockToken.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageLockToken.cs index 809b9763..5721fbfb 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageLockToken.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageLockToken.cs @@ -208,9 +208,9 @@ public override Boolean Equals(Object obj) { return false; } - else if (obj is MessageLockToken) + else if (obj is MessageLockToken token) { - return Equals((MessageLockToken)obj); + return Equals(token); } return false; diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageQueueClient.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageQueueClient.cs index 2fa7bdd7..a35d1922 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageQueueClient.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageQueueClient.cs @@ -4,7 +4,6 @@ using System; using System.Diagnostics; -using System.Threading.Tasks; namespace RapidField.SolidInstruments.Messaging.TransportPrimitives { @@ -44,12 +43,9 @@ internal MessageQueueClient(IMessageTransportConnection connection, IMessagingEn /// /// An action to perform upon message receipt. /// - protected override void RegisterMessageHandler(IMessageTransportConnection connection, Action handleMessageAction) => Task.Factory.StartNew(async () => + protected override void RegisterMessageHandler(IMessageTransportConnection connection, Action handleMessageAction) => EnsureQueueExistanceAsync(Path).ContinueWith(ensureQueueExistenceTask => { - await EnsureQueueExistanceAsync(Path).ContinueWith(ensureQueueExistenceTask => - { - connection.RegisterQueueHandler(Path, handleMessageAction); - }).ConfigureAwait(false); + connection.RegisterQueueHandler(Path, handleMessageAction); }).Wait(); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageSubscriptionClient.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageSubscriptionClient.cs index 611e5838..0e538bb0 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageSubscriptionClient.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageSubscriptionClient.cs @@ -6,7 +6,6 @@ using RapidField.SolidInstruments.Core.ArgumentValidation; using System; using System.Diagnostics; -using System.Threading.Tasks; namespace RapidField.SolidInstruments.Messaging.TransportPrimitives { @@ -53,12 +52,9 @@ internal MessageSubscriptionClient(IMessageTransportConnection connection, IMess /// /// An action to perform upon message receipt. /// - protected override void RegisterMessageHandler(IMessageTransportConnection connection, Action handleMessageAction) => Task.Factory.StartNew(async () => + protected override void RegisterMessageHandler(IMessageTransportConnection connection, Action handleMessageAction) => EnsureSubscriptionExistanceAsync(Path, SubscriptionName).ContinueWith(ensureSubscriptionExistenceTask => { - await EnsureSubscriptionExistanceAsync(Path, SubscriptionName).ContinueWith(ensureSubscriptionExistenceTask => - { - connection.RegisterSubscriptionHandler(Path, SubscriptionName, handleMessageAction); - }).ConfigureAwait(false); + connection.RegisterSubscriptionHandler(Path, SubscriptionName, handleMessageAction); }).Wait(); /// diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransport.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransport.cs index d5717fef..888d66b0 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransport.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransport.cs @@ -568,7 +568,7 @@ public Task DestroyTopicAsync(IMessagingEntityPath path) => Task.Factory.StartNe public Boolean QueueExists(IMessagingEntityPath path) { RejectIfDisposed(); - return QueuePaths.Any(queuePath => queuePath == path.RejectIf().IsNull(nameof(path)).TargetArgument); + return QueueDictionary.ContainsKey(path.RejectIf().IsNull(nameof(path)).TargetArgument); } /// @@ -772,7 +772,7 @@ public Boolean SubscriptionExists(IMessagingEntityPath path, String subscription public Boolean TopicExists(IMessagingEntityPath path) { RejectIfDisposed(); - return TopicPaths.Any(topciPath => topciPath == path.RejectIf().IsNull(nameof(path)).TargetArgument); + return TopicDictionary.ContainsKey(path.RejectIf().IsNull(nameof(path)).TargetArgument); } /// diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransportConnection.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransportConnection.cs index 767a2914..6a413e3f 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransportConnection.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransportConnection.cs @@ -192,7 +192,7 @@ private void BeginPolling() private Timer InitializePollTimer() { var timerCallback = new TimerCallback((state) => Poll(state as ICollection)); - return new Timer(timerCallback, Handlers, TimeSpan.Zero, TimeSpan.FromSeconds(PollingIntervalInMilliseconds)); + return new Timer(timerCallback, Handlers, TimeSpan.FromMilliseconds(PollingIntervalInMilliseconds), TimeSpan.FromMilliseconds(PollingIntervalInMilliseconds)); } /// @@ -211,9 +211,13 @@ private void Poll(IEnumerable handlers) { try { - var pollQueuesTask = Task.Factory.StartNew(async () => await PollQueuesAsync(handlers.Where(handler => handler.EntityType == MessagingEntityType.Queue)).ConfigureAwait(false)); - var pollTopicsTask = Task.Factory.StartNew(async () => await PollTopicsAsync(handlers.Where(handler => handler.EntityType == MessagingEntityType.Topic)).ConfigureAwait(false)); - Task.WaitAll(pollQueuesTask, pollTopicsTask); + using (var pollQueuesTask = PollQueuesAsync(handlers.Where(handler => handler.EntityType == MessagingEntityType.Queue).ToArray())) + { + using (var pollTopicsTask = PollTopicsAsync(handlers.Where(handler => handler.EntityType == MessagingEntityType.Topic).ToArray())) + { + Task.WaitAll(pollQueuesTask, pollTopicsTask); + } + } } catch (AggregateException exception) { @@ -250,9 +254,9 @@ private void Poll(IEnumerable handlers) /// The operation timed out. /// [DebuggerHidden] - private async Task PollQueueAsync(IMessagingEntityPath queuePath, IEnumerable> handleMessageActions) + private Task PollQueueAsync(IMessagingEntityPath queuePath, IEnumerable> handleMessageActions) => Transport.ReceiveFromQueueAsync(queuePath, MessageReceiptBatchSize).ContinueWith((receiveFromQueueTask) => { - var messageBatch = await Transport.ReceiveFromQueueAsync(queuePath, MessageReceiptBatchSize).ConfigureAwait(false); + var messageBatch = receiveFromQueueTask.Result; if (messageBatch.Any()) { @@ -269,9 +273,9 @@ private async Task PollQueueAsync(IMessagingEntityPath queuePath, IEnumerable /// Asynchronously polls available queues and performs message handling operations against received messages, if any. @@ -298,23 +302,21 @@ private async Task PollQueueAsync(IMessagingEntityPath queuePath, IEnumerable [DebuggerHidden] - private async Task PollQueuesAsync(IEnumerable queueHandlers) + private Task PollQueuesAsync(IEnumerable queueHandlers) { if (queueHandlers.Any()) { - var handlerGroups = queueHandlers.GroupBy(handler => handler.Path); var pollTasks = new List(); - foreach (var handlerGroup in handlerGroups) + foreach (var handlerGroup in queueHandlers.GroupBy(handler => handler.Path)) { - pollTasks.Add(Task.Factory.StartNew(async () => - { - await PollQueueAsync(handlerGroup.Key, handlerGroup.Select(handler => handler.HandleMessageAction)).ConfigureAwait(false); - })); + pollTasks.Add(PollQueueAsync(handlerGroup.Key, handlerGroup.Select(handler => handler.HandleMessageAction))); } - await Task.WhenAll(pollTasks.ToArray()).ConfigureAwait(false); + return Task.WhenAll(pollTasks.ToArray()); } + + return Task.CompletedTask; } /// @@ -349,9 +351,9 @@ private async Task PollQueuesAsync(IEnumerable queueHandlers) /// The operation timed out. /// [DebuggerHidden] - private async Task PollSubscriptionAsync(IMessagingEntityPath topicPath, String subscriptionName, IEnumerable> handleMessageActions) + private Task PollSubscriptionAsync(IMessagingEntityPath topicPath, String subscriptionName, IEnumerable> handleMessageActions) => Transport.ReceiveFromTopicAsync(topicPath, subscriptionName, MessageReceiptBatchSize).ContinueWith((receiveFromTopicTask) => { - var messageBatch = await Transport.ReceiveFromTopicAsync(topicPath, subscriptionName, MessageReceiptBatchSize).ConfigureAwait(false); + var messageBatch = receiveFromTopicTask.Result; if (messageBatch.Any()) { @@ -368,9 +370,9 @@ private async Task PollSubscriptionAsync(IMessagingEntityPath topicPath, String } } - await Task.WhenAll(handleMessageTasks.ToArray()).ConfigureAwait(false); + Task.WaitAll(handleMessageTasks.ToArray()); } - } + }); /// /// Asynchronously polls the specified topic and performs messaging handling operations against received messages, if any. @@ -400,20 +402,16 @@ private async Task PollSubscriptionAsync(IMessagingEntityPath topicPath, String /// The operation timed out. /// [DebuggerHidden] - private async Task PollTopicAsync(IMessagingEntityPath topicPath, IEnumerable subscriptionHandlers) + private Task PollTopicAsync(IMessagingEntityPath topicPath, IEnumerable subscriptionHandlers) { - var handlerGroups = subscriptionHandlers.GroupBy(handler => handler.SubscriptionName); var pollTasks = new List(); - foreach (var handlerGroup in handlerGroups) + foreach (var handlerGroup in subscriptionHandlers.GroupBy(handler => handler.SubscriptionName)) { - pollTasks.Add(Task.Factory.StartNew(async () => - { - await PollSubscriptionAsync(topicPath, handlerGroup.Key, handlerGroup.Select(handler => handler.HandleMessageAction)).ConfigureAwait(false); - })); + pollTasks.Add(PollSubscriptionAsync(topicPath, handlerGroup.Key, handlerGroup.Select(handler => handler.HandleMessageAction))); } - await Task.WhenAll(pollTasks.ToArray()).ConfigureAwait(false); + return Task.WhenAll(pollTasks.ToArray()); } /// @@ -441,23 +439,21 @@ private async Task PollTopicAsync(IMessagingEntityPath topicPath, IEnumerable [DebuggerHidden] - private async Task PollTopicsAsync(IEnumerable subscriptionHandlers) + private Task PollTopicsAsync(IEnumerable subscriptionHandlers) { if (subscriptionHandlers.Any()) { - var handlerGroups = subscriptionHandlers.GroupBy(handler => handler.Path); var pollTasks = new List(); - foreach (var handlerGroup in handlerGroups) + foreach (var handlerGroup in subscriptionHandlers.GroupBy(handler => handler.Path)) { - pollTasks.Add(Task.Factory.StartNew(async () => - { - await PollTopicAsync(handlerGroup.Key, handlerGroup).ConfigureAwait(false); - })); + pollTasks.Add(PollTopicAsync(handlerGroup.Key, handlerGroup)); } - await Task.WhenAll(pollTasks.ToArray()).ConfigureAwait(false); + return Task.WhenAll(pollTasks.ToArray()); } + + return Task.CompletedTask; } /// @@ -501,7 +497,7 @@ public MessageTransportConnectionState State /// Represents the interval, in milliseconds, at which is polled for receive operations. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const Int32 PollingIntervalInMilliseconds = 233; + private const Int32 PollingIntervalInMilliseconds = 987; /// /// Represents a collection of actions that is performed upon message receipt from specific entities. diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessagingEntityClient.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessagingEntityClient.cs index de028c8c..7a8e0890 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessagingEntityClient.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessagingEntityClient.cs @@ -5,6 +5,7 @@ using RapidField.SolidInstruments.Core; using RapidField.SolidInstruments.Core.ArgumentValidation; using System; +using System.Diagnostics; using System.Threading.Tasks; namespace RapidField.SolidInstruments.Messaging.TransportPrimitives @@ -167,22 +168,25 @@ public void RegisterMessageHandler(Action handleMessageAction) /// /// The transport connection is closed. /// - protected Task EnsureQueueExistanceAsync(IMessagingEntityPath queuePath) + protected Task EnsureQueueExistanceAsync(IMessagingEntityPath queuePath) => Task.Factory.StartNew(() => { - try + lock (EnsureQueueExistenceSyncRoot) { - if (Connection.Transport.QueueExists(queuePath)) + try { - return Task.CompletedTask; - } + if (Connection.Transport.QueueExists(queuePath)) + { + return; + } - return Connection.Transport.CreateQueueAsync(queuePath); - } - catch (ObjectDisposedException exception) - { - throw new MessageTransportConnectionClosedException("Failed to ensure queue existence. The connection is closed.", exception); + Connection.Transport.CreateQueueAsync(queuePath).Wait(); + } + catch (ObjectDisposedException exception) + { + throw new MessageTransportConnectionClosedException("Failed to ensure queue existence. The connection is closed.", exception); + } } - } + }); /// /// Asynchronously creates the specified subscription if it does not exist. @@ -206,20 +210,23 @@ protected Task EnsureQueueExistanceAsync(IMessagingEntityPath queuePath) /// /// The transport connection is closed. /// - protected Task EnsureSubscriptionExistanceAsync(IMessagingEntityPath topicPath, String subscriptionName) => EnsureTopicExistanceAsync(topicPath).ContinueWith(async ensureTopicExistenceTask => + protected Task EnsureSubscriptionExistanceAsync(IMessagingEntityPath topicPath, String subscriptionName) => EnsureTopicExistanceAsync(topicPath).ContinueWith(ensureTopicExistenceTask => { - try + lock (EnsureSubscriptionExistenceSyncRoot) { - if (Connection.Transport.SubscriptionExists(topicPath, subscriptionName)) + try { - return; - } + if (Connection.Transport.SubscriptionExists(topicPath, subscriptionName)) + { + return; + } - await Connection.Transport.CreateSubscriptionAsync(topicPath, subscriptionName).ConfigureAwait(false); - } - catch (ObjectDisposedException exception) - { - throw new MessageTransportConnectionClosedException("Failed to ensure subscription existence. The connection is closed.", exception); + Connection.Transport.CreateSubscriptionAsync(topicPath, subscriptionName).Wait(); + } + catch (ObjectDisposedException exception) + { + throw new MessageTransportConnectionClosedException("Failed to ensure subscription existence. The connection is closed.", exception); + } } }); @@ -238,22 +245,25 @@ protected Task EnsureSubscriptionExistanceAsync(IMessagingEntityPath topicPath, /// /// The transport connection is closed. /// - protected Task EnsureTopicExistanceAsync(IMessagingEntityPath topicPath) + protected Task EnsureTopicExistanceAsync(IMessagingEntityPath topicPath) => Task.Factory.StartNew(() => { - try + lock (EnsureTopicExistenceSyncRoot) { - if (Connection.Transport.TopicExists(topicPath)) + try { - return Task.CompletedTask; - } + if (Connection.Transport.TopicExists(topicPath)) + { + return; + } - return Connection.Transport.CreateTopicAsync(topicPath); - } - catch (ObjectDisposedException exception) - { - throw new MessageTransportConnectionClosedException("Failed to ensure topic existence. The connection is closed.", exception); + Connection.Transport.CreateTopicAsync(topicPath).Wait(); + } + catch (ObjectDisposedException exception) + { + throw new MessageTransportConnectionClosedException("Failed to ensure topic existence. The connection is closed.", exception); + } } - } + }); /// /// Registers the specified message handler for the associated . @@ -289,5 +299,26 @@ public IMessagingEntityPath Path { get; } + + /// + /// Represents an object that is used to synchronize access to + /// . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal static readonly Object EnsureQueueExistenceSyncRoot = new Object(); + + /// + /// Represents an object that is used to synchronize access to + /// . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal static readonly Object EnsureSubscriptionExistenceSyncRoot = new Object(); + + /// + /// Represents an object that is used to synchronize access to + /// . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal static readonly Object EnsureTopicExistenceSyncRoot = new Object(); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.ObjectComposition/ObjectContainerDefinition.cs b/src/RapidField.SolidInstruments.ObjectComposition/ObjectContainerDefinition.cs index 625d227a..d31cb8b5 100644 --- a/src/RapidField.SolidInstruments.ObjectComposition/ObjectContainerDefinition.cs +++ b/src/RapidField.SolidInstruments.ObjectComposition/ObjectContainerDefinition.cs @@ -189,9 +189,9 @@ public override Boolean Equals(Object obj) { return false; } - else if (obj is IObjectContainerDefinition) + else if (obj is IObjectContainerDefinition definition) { - return Equals((IObjectContainerDefinition)obj); + return Equals(definition); } return false; diff --git a/src/RapidField.SolidInstruments.Service/AssemblyAttributes.cs b/src/RapidField.SolidInstruments.Service/AssemblyAttributes.cs index 076a1e90..af65bb8d 100644 --- a/src/RapidField.SolidInstruments.Service/AssemblyAttributes.cs +++ b/src/RapidField.SolidInstruments.Service/AssemblyAttributes.cs @@ -5,4 +5,5 @@ using System.Runtime.CompilerServices; [assembly: DisablePrivateReflection()] +[assembly: InternalsVisibleTo("RapidField.SolidInstruments.Messaging")] [assembly: InternalsVisibleTo("RapidField.SolidInstruments.Service.UnitTests")] \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Service/ServiceExecutor.cs b/src/RapidField.SolidInstruments.Service/ServiceExecutor.cs index f4b9f84e..e24a1527 100644 --- a/src/RapidField.SolidInstruments.Service/ServiceExecutor.cs +++ b/src/RapidField.SolidInstruments.Service/ServiceExecutor.cs @@ -50,6 +50,7 @@ public abstract class ServiceExecutor(CreateApplicationConfiguration, LazyThreadSafetyMode.ExecutionAndPublication); LazyDependencyEngine = new Lazy(CreateDependencyEngine, LazyThreadSafetyMode.ExecutionAndPublication); LazyRootDependencyScope = new Lazy(DependencyEngine.Container.CreateScope, LazyThreadSafetyMode.ExecutionAndPublication); @@ -69,6 +70,8 @@ public void Execute() { using (var executionLifetime = new ServiceExecutionLifetime()) { + ExecutionLifetime = executionLifetime; + using (var dependencyScope = CreateDependencyScope()) { Execute(dependencyScope, ApplicationConfiguration, executionLifetime); @@ -197,6 +200,18 @@ public String ServiceName get; } + /// + /// Gets the execution lifetime object for the current + /// , or + /// if execution has not yet started. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal IServiceExecutionLifetime ExecutionLifetime + { + get; + private set; + } + /// /// Gets configuration information for the service. /// diff --git a/src/RapidField.SolidInstruments.TextEncoding/EnhancedReadabilityGuid.cs b/src/RapidField.SolidInstruments.TextEncoding/EnhancedReadabilityGuid.cs index 4beff969..c6d19e77 100644 --- a/src/RapidField.SolidInstruments.TextEncoding/EnhancedReadabilityGuid.cs +++ b/src/RapidField.SolidInstruments.TextEncoding/EnhancedReadabilityGuid.cs @@ -243,9 +243,9 @@ public override Boolean Equals(Object obj) { return false; } - else if (obj is EnhancedReadabilityGuid) + else if (obj is EnhancedReadabilityGuid guid) { - return Equals((EnhancedReadabilityGuid)obj); + return Equals(guid); } return false; diff --git a/test/RapidField.SolidInstruments.DataAccess.UnitTests/SimulatedEntity.cs b/test/RapidField.SolidInstruments.DataAccess.UnitTests/SimulatedEntity.cs index 3e7659d0..17d413c7 100644 --- a/test/RapidField.SolidInstruments.DataAccess.UnitTests/SimulatedEntity.cs +++ b/test/RapidField.SolidInstruments.DataAccess.UnitTests/SimulatedEntity.cs @@ -109,9 +109,9 @@ public override Boolean Equals(Object obj) { return false; } - else if (obj is SimulatedEntity) + else if (obj is SimulatedEntity entity) { - return Equals((SimulatedEntity)obj); + return Equals(entity); } return false; diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/CommandHandlers/ModelState/Customer/CreateDomainModelCommandHandler.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/CommandHandlers/ModelState/Customer/CreateDomainModelCommandHandler.cs new file mode 100644 index 00000000..ac90eb36 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/CommandHandlers/ModelState/Customer/CreateDomainModelCommandHandler.cs @@ -0,0 +1,64 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Concurrency; +using System; +using System.Collections.Generic; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer.DomainModel; +using DomainModelCommand = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.Customer.CreateDomainModelCommand; +using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.Customer.DomainModelCreatedEvent; +using DomainModelEventMessage = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Event.ModelState.Customer.DomainModelCreatedEventMessage; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.CommandHandlers.ModelState.Customer +{ + /// + /// Processes a single . + /// + internal class CreateDomainModelCommandHandler : CreateDomainModelCommandHandler + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// is . + /// + public CreateDomainModelCommandHandler(ICommandMediator mediator) + : base(mediator) + { + return; + } + + /// + /// Creates the specified domain model. + /// + /// + /// The model to create. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// A unique identifier to assign to sub-commands. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + protected override void CreateDomainModel(DomainModel model, IEnumerable labels, Guid correlationIdentifier, ICommandMediator mediator, IConcurrencyControlToken controlToken) => mediator.Process(new DomainModelEventMessage(new DomainModelEvent(model, labels, correlationIdentifier))); + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/CommandHandlers/ModelState/Customer/DeleteDomainModelCommandHandler.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/CommandHandlers/ModelState/Customer/DeleteDomainModelCommandHandler.cs new file mode 100644 index 00000000..f8bd300a --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/CommandHandlers/ModelState/Customer/DeleteDomainModelCommandHandler.cs @@ -0,0 +1,64 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Concurrency; +using System; +using System.Collections.Generic; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer.DomainModel; +using DomainModelCommand = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.Customer.DeleteDomainModelCommand; +using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.Customer.DomainModelDeletedEvent; +using DomainModelEventMessage = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Event.ModelState.Customer.DomainModelDeletedEventMessage; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.CommandHandlers.ModelState.Customer +{ + /// + /// Processes a single . + /// + internal class DeleteDomainModelCommandHandler : DeleteDomainModelCommandHandler + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// is . + /// + public DeleteDomainModelCommandHandler(ICommandMediator mediator) + : base(mediator) + { + return; + } + + /// + /// Deletes the specified domain model. + /// + /// + /// The model to delete. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// A unique identifier to assign to sub-commands. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + protected override void DeleteDomainModel(DomainModel model, IEnumerable labels, Guid correlationIdentifier, ICommandMediator mediator, IConcurrencyControlToken controlToken) => mediator.Process(new DomainModelEventMessage(new DomainModelEvent(model, labels, correlationIdentifier))); + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/CommandHandlers/ModelState/Customer/UpdateDomainModelCommandHandler.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/CommandHandlers/ModelState/Customer/UpdateDomainModelCommandHandler.cs new file mode 100644 index 00000000..43c7b0f5 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/CommandHandlers/ModelState/Customer/UpdateDomainModelCommandHandler.cs @@ -0,0 +1,64 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Concurrency; +using System; +using System.Collections.Generic; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer.DomainModel; +using DomainModelCommand = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.Customer.UpdateDomainModelCommand; +using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.Customer.DomainModelUpdatedEvent; +using DomainModelEventMessage = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Event.ModelState.Customer.DomainModelUpdatedEventMessage; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.CommandHandlers.ModelState.Customer +{ + /// + /// Processes a single . + /// + internal class UpdateDomainModelCommandHandler : UpdateDomainModelCommandHandler + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// is . + /// + public UpdateDomainModelCommandHandler(ICommandMediator mediator) + : base(mediator) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Updates the specified domain model. + /// + /// + /// The model to update. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// A unique identifier to assign to sub-commands. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + protected override void UpdateDomainModel(DomainModel model, IEnumerable labels, Guid correlationIdentifier, ICommandMediator mediator, IConcurrencyControlToken controlToken) => mediator.Process(new DomainModelEventMessage(new DomainModelEvent(model, labels, correlationIdentifier))); + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/CommandHandlers/ModelState/CustomerOrder/CreateDomainModelCommandHandler.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/CommandHandlers/ModelState/CustomerOrder/CreateDomainModelCommandHandler.cs new file mode 100644 index 00000000..a04bb5a6 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/CommandHandlers/ModelState/CustomerOrder/CreateDomainModelCommandHandler.cs @@ -0,0 +1,64 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Concurrency; +using System; +using System.Collections.Generic; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.CustomerOrder.DomainModel; +using DomainModelCommand = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.CustomerOrder.CreateDomainModelCommand; +using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.CustomerOrder.DomainModelCreatedEvent; +using DomainModelEventMessage = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Event.ModelState.CustomerOrder.DomainModelCreatedEventMessage; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.CommandHandlers.ModelState.CustomerOrder +{ + /// + /// Processes a single . + /// + internal class CreateDomainModelCommandHandler : CreateDomainModelCommandHandler + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// is . + /// + public CreateDomainModelCommandHandler(ICommandMediator mediator) + : base(mediator) + { + return; + } + + /// + /// Creates the specified domain model. + /// + /// + /// The model to create. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// A unique identifier to assign to sub-commands. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + protected override void CreateDomainModel(DomainModel model, IEnumerable labels, Guid correlationIdentifier, ICommandMediator mediator, IConcurrencyControlToken controlToken) => mediator.Process(new DomainModelEventMessage(new DomainModelEvent(model, labels, correlationIdentifier))); + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/CommandHandlers/ModelState/CustomerOrder/DeleteDomainModelCommandHandler.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/CommandHandlers/ModelState/CustomerOrder/DeleteDomainModelCommandHandler.cs new file mode 100644 index 00000000..670c75e9 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/CommandHandlers/ModelState/CustomerOrder/DeleteDomainModelCommandHandler.cs @@ -0,0 +1,64 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Concurrency; +using System; +using System.Collections.Generic; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.CustomerOrder.DomainModel; +using DomainModelCommand = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.CustomerOrder.DeleteDomainModelCommand; +using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.CustomerOrder.DomainModelDeletedEvent; +using DomainModelEventMessage = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Event.ModelState.CustomerOrder.DomainModelDeletedEventMessage; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.CommandHandlers.ModelState.CustomerOrder +{ + /// + /// Processes a single . + /// + internal class DeleteDomainModelCommandHandler : DeleteDomainModelCommandHandler + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// is . + /// + public DeleteDomainModelCommandHandler(ICommandMediator mediator) + : base(mediator) + { + return; + } + + /// + /// Deletes the specified domain model. + /// + /// + /// The model to delete. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// A unique identifier to assign to sub-commands. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + protected override void DeleteDomainModel(DomainModel model, IEnumerable labels, Guid correlationIdentifier, ICommandMediator mediator, IConcurrencyControlToken controlToken) => mediator.Process(new DomainModelEventMessage(new DomainModelEvent(model, labels, correlationIdentifier))); + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/CommandHandlers/ModelState/CustomerOrder/UpdateDomainModelCommandHandler.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/CommandHandlers/ModelState/CustomerOrder/UpdateDomainModelCommandHandler.cs new file mode 100644 index 00000000..44918975 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/CommandHandlers/ModelState/CustomerOrder/UpdateDomainModelCommandHandler.cs @@ -0,0 +1,64 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Concurrency; +using System; +using System.Collections.Generic; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.CustomerOrder.DomainModel; +using DomainModelCommand = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.CustomerOrder.UpdateDomainModelCommand; +using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.CustomerOrder.DomainModelUpdatedEvent; +using DomainModelEventMessage = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Event.ModelState.CustomerOrder.DomainModelUpdatedEventMessage; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.CommandHandlers.ModelState.CustomerOrder +{ + /// + /// Processes a single . + /// + internal class UpdateDomainModelCommandHandler : UpdateDomainModelCommandHandler + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// is . + /// + public UpdateDomainModelCommandHandler(ICommandMediator mediator) + : base(mediator) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Updates the specified domain model. + /// + /// + /// The model to update. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// A unique identifier to assign to sub-commands. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + protected override void UpdateDomainModel(DomainModel model, IEnumerable labels, Guid correlationIdentifier, ICommandMediator mediator, IConcurrencyControlToken controlToken) => mediator.Process(new DomainModelEventMessage(new DomainModelEvent(model, labels, correlationIdentifier))); + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/CommandHandlers/ModelState/Product/CreateDomainModelCommandHandler.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/CommandHandlers/ModelState/Product/CreateDomainModelCommandHandler.cs new file mode 100644 index 00000000..69c1aaac --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/CommandHandlers/ModelState/Product/CreateDomainModelCommandHandler.cs @@ -0,0 +1,64 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Concurrency; +using System; +using System.Collections.Generic; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Product.DomainModel; +using DomainModelCommand = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.Product.CreateDomainModelCommand; +using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.Product.DomainModelCreatedEvent; +using DomainModelEventMessage = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Event.ModelState.Product.DomainModelCreatedEventMessage; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.CommandHandlers.ModelState.Product +{ + /// + /// Processes a single . + /// + internal class CreateDomainModelCommandHandler : CreateDomainModelCommandHandler + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// is . + /// + public CreateDomainModelCommandHandler(ICommandMediator mediator) + : base(mediator) + { + return; + } + + /// + /// Creates the specified domain model. + /// + /// + /// The model to create. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// A unique identifier to assign to sub-commands. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + protected override void CreateDomainModel(DomainModel model, IEnumerable labels, Guid correlationIdentifier, ICommandMediator mediator, IConcurrencyControlToken controlToken) => mediator.Process(new DomainModelEventMessage(new DomainModelEvent(model, labels, correlationIdentifier))); + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/CommandHandlers/ModelState/Product/DeleteDomainModelCommandHandler.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/CommandHandlers/ModelState/Product/DeleteDomainModelCommandHandler.cs new file mode 100644 index 00000000..dffbe2c3 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/CommandHandlers/ModelState/Product/DeleteDomainModelCommandHandler.cs @@ -0,0 +1,64 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Concurrency; +using System; +using System.Collections.Generic; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Product.DomainModel; +using DomainModelCommand = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.Product.DeleteDomainModelCommand; +using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.Product.DomainModelDeletedEvent; +using DomainModelEventMessage = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Event.ModelState.Product.DomainModelDeletedEventMessage; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.CommandHandlers.ModelState.Product +{ + /// + /// Processes a single . + /// + internal class DeleteDomainModelCommandHandler : DeleteDomainModelCommandHandler + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// is . + /// + public DeleteDomainModelCommandHandler(ICommandMediator mediator) + : base(mediator) + { + return; + } + + /// + /// Deletes the specified domain model. + /// + /// + /// The model to delete. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// A unique identifier to assign to sub-commands. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + protected override void DeleteDomainModel(DomainModel model, IEnumerable labels, Guid correlationIdentifier, ICommandMediator mediator, IConcurrencyControlToken controlToken) => mediator.Process(new DomainModelEventMessage(new DomainModelEvent(model, labels, correlationIdentifier))); + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/CommandHandlers/ModelState/Product/UpdateDomainModelCommandHandler.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/CommandHandlers/ModelState/Product/UpdateDomainModelCommandHandler.cs new file mode 100644 index 00000000..f89d4f86 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/CommandHandlers/ModelState/Product/UpdateDomainModelCommandHandler.cs @@ -0,0 +1,64 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Concurrency; +using System; +using System.Collections.Generic; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Product.DomainModel; +using DomainModelCommand = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.Product.UpdateDomainModelCommand; +using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.Product.DomainModelUpdatedEvent; +using DomainModelEventMessage = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Event.ModelState.Product.DomainModelUpdatedEventMessage; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.CommandHandlers.ModelState.Product +{ + /// + /// Processes a single . + /// + internal class UpdateDomainModelCommandHandler : UpdateDomainModelCommandHandler + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// is . + /// + public UpdateDomainModelCommandHandler(ICommandMediator mediator) + : base(mediator) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Updates the specified domain model. + /// + /// + /// The model to update. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// A unique identifier to assign to sub-commands. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + protected override void UpdateDomainModel(DomainModel model, IEnumerable labels, Guid correlationIdentifier, ICommandMediator mediator, IConcurrencyControlToken controlToken) => mediator.Process(new DomainModelEventMessage(new DomainModelEvent(model, labels, correlationIdentifier))); + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Customer/CreateDomainModelCommand.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Customer/CreateDomainModelCommand.cs index 47ce1f1a..b3182fd0 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Customer/CreateDomainModelCommand.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Customer/CreateDomainModelCommand.cs @@ -2,12 +2,120 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= +using RapidField.SolidInstruments.Command; +using System; +using System.Collections.Generic; using System.Runtime.Serialization; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer.DomainModel; namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.Customer { - [DataContract] - internal sealed class CreateDomainModelCommand + /// + /// Represents a command to create a . + /// + [DataContract(Name = "CreateCustomerEvent")] + internal sealed class CreateDomainModelCommand : CreateDomainModelCommand { + /// + /// Initializes a new instance of the class. + /// + public CreateDomainModelCommand() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is equal to . + /// + public CreateDomainModelCommand(Guid correlationIdentifier) + : base(correlationIdentifier) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// is . + /// + public CreateDomainModelCommand(DomainModel model) + : base(model) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public CreateDomainModelCommand(DomainModel model, Guid correlationIdentifier) + : base(model, correlationIdentifier) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// is -or- is . + /// + public CreateDomainModelCommand(DomainModel model, IEnumerable labels) + : base(model, labels) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public CreateDomainModelCommand(DomainModel model, IEnumerable labels, Guid correlationIdentifier) + : base(model, labels, correlationIdentifier) + { + return; + } } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Customer/DeleteDomainModelCommand.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Customer/DeleteDomainModelCommand.cs new file mode 100644 index 00000000..1fe510ff --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Customer/DeleteDomainModelCommand.cs @@ -0,0 +1,121 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer.DomainModel; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.Customer +{ + /// + /// Represents a command to delete a . + /// + [DataContract(Name = "DeleteCustomerEvent")] + internal sealed class DeleteDomainModelCommand : DeleteDomainModelCommand + { + /// + /// Initializes a new instance of the class. + /// + public DeleteDomainModelCommand() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is equal to . + /// + public DeleteDomainModelCommand(Guid correlationIdentifier) + : base(correlationIdentifier) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// is . + /// + public DeleteDomainModelCommand(DomainModel model) + : base(model) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public DeleteDomainModelCommand(DomainModel model, Guid correlationIdentifier) + : base(model, correlationIdentifier) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// is -or- is . + /// + public DeleteDomainModelCommand(DomainModel model, IEnumerable labels) + : base(model, labels) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public DeleteDomainModelCommand(DomainModel model, IEnumerable labels, Guid correlationIdentifier) + : base(model, labels, correlationIdentifier) + { + return; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Customer/UpdateDomainModelCommand.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Customer/UpdateDomainModelCommand.cs new file mode 100644 index 00000000..18500b5d --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Customer/UpdateDomainModelCommand.cs @@ -0,0 +1,121 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer.DomainModel; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.Customer +{ + /// + /// Represents a command to update a . + /// + [DataContract(Name = "UpdateCustomerEvent")] + internal sealed class UpdateDomainModelCommand : UpdateDomainModelCommand + { + /// + /// Initializes a new instance of the class. + /// + public UpdateDomainModelCommand() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is equal to . + /// + public UpdateDomainModelCommand(Guid correlationIdentifier) + : base(correlationIdentifier) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// is . + /// + public UpdateDomainModelCommand(DomainModel model) + : base(model) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public UpdateDomainModelCommand(DomainModel model, Guid correlationIdentifier) + : base(model, correlationIdentifier) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// is -or- is . + /// + public UpdateDomainModelCommand(DomainModel model, IEnumerable labels) + : base(model, labels) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public UpdateDomainModelCommand(DomainModel model, IEnumerable labels, Guid correlationIdentifier) + : base(model, labels, correlationIdentifier) + { + return; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/CustomerOrder/CreateDomainModelCommand.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/CustomerOrder/CreateDomainModelCommand.cs new file mode 100644 index 00000000..21685855 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/CustomerOrder/CreateDomainModelCommand.cs @@ -0,0 +1,121 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.CustomerOrder.DomainModel; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.CustomerOrder +{ + /// + /// Represents a command to create a . + /// + [DataContract(Name = "CreateCustomerOrderEvent")] + internal sealed class CreateDomainModelCommand : CreateDomainModelCommand + { + /// + /// Initializes a new instance of the class. + /// + public CreateDomainModelCommand() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is equal to . + /// + public CreateDomainModelCommand(Guid correlationIdentifier) + : base(correlationIdentifier) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// is . + /// + public CreateDomainModelCommand(DomainModel model) + : base(model) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public CreateDomainModelCommand(DomainModel model, Guid correlationIdentifier) + : base(model, correlationIdentifier) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// is -or- is . + /// + public CreateDomainModelCommand(DomainModel model, IEnumerable labels) + : base(model, labels) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public CreateDomainModelCommand(DomainModel model, IEnumerable labels, Guid correlationIdentifier) + : base(model, labels, correlationIdentifier) + { + return; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/CustomerOrder/DeleteDomainModelCommand.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/CustomerOrder/DeleteDomainModelCommand.cs new file mode 100644 index 00000000..d9fcf560 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/CustomerOrder/DeleteDomainModelCommand.cs @@ -0,0 +1,121 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.CustomerOrder.DomainModel; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.CustomerOrder +{ + /// + /// Represents a command to delete a . + /// + [DataContract(Name = "DeleteCustomerOrderEvent")] + internal sealed class DeleteDomainModelCommand : DeleteDomainModelCommand + { + /// + /// Initializes a new instance of the class. + /// + public DeleteDomainModelCommand() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is equal to . + /// + public DeleteDomainModelCommand(Guid correlationIdentifier) + : base(correlationIdentifier) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// is . + /// + public DeleteDomainModelCommand(DomainModel model) + : base(model) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public DeleteDomainModelCommand(DomainModel model, Guid correlationIdentifier) + : base(model, correlationIdentifier) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// is -or- is . + /// + public DeleteDomainModelCommand(DomainModel model, IEnumerable labels) + : base(model, labels) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public DeleteDomainModelCommand(DomainModel model, IEnumerable labels, Guid correlationIdentifier) + : base(model, labels, correlationIdentifier) + { + return; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/CustomerOrder/UpdateDomainModelCommand.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/CustomerOrder/UpdateDomainModelCommand.cs new file mode 100644 index 00000000..8810d7b3 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/CustomerOrder/UpdateDomainModelCommand.cs @@ -0,0 +1,121 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.CustomerOrder.DomainModel; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.CustomerOrder +{ + /// + /// Represents a command to update a . + /// + [DataContract(Name = "UpdateCustomerOrderEvent")] + internal sealed class UpdateDomainModelCommand : UpdateDomainModelCommand + { + /// + /// Initializes a new instance of the class. + /// + public UpdateDomainModelCommand() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is equal to . + /// + public UpdateDomainModelCommand(Guid correlationIdentifier) + : base(correlationIdentifier) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// is . + /// + public UpdateDomainModelCommand(DomainModel model) + : base(model) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public UpdateDomainModelCommand(DomainModel model, Guid correlationIdentifier) + : base(model, correlationIdentifier) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// is -or- is . + /// + public UpdateDomainModelCommand(DomainModel model, IEnumerable labels) + : base(model, labels) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public UpdateDomainModelCommand(DomainModel model, IEnumerable labels, Guid correlationIdentifier) + : base(model, labels, correlationIdentifier) + { + return; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Product/CreateDomainModelCommand.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Product/CreateDomainModelCommand.cs new file mode 100644 index 00000000..aa10ce55 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Product/CreateDomainModelCommand.cs @@ -0,0 +1,121 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Product.DomainModel; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.Product +{ + /// + /// Represents a command to create a . + /// + [DataContract(Name = "CreateProductEvent")] + internal sealed class CreateDomainModelCommand : CreateDomainModelCommand + { + /// + /// Initializes a new instance of the class. + /// + public CreateDomainModelCommand() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is equal to . + /// + public CreateDomainModelCommand(Guid correlationIdentifier) + : base(correlationIdentifier) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// is . + /// + public CreateDomainModelCommand(DomainModel model) + : base(model) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public CreateDomainModelCommand(DomainModel model, Guid correlationIdentifier) + : base(model, correlationIdentifier) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// is -or- is . + /// + public CreateDomainModelCommand(DomainModel model, IEnumerable labels) + : base(model, labels) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public CreateDomainModelCommand(DomainModel model, IEnumerable labels, Guid correlationIdentifier) + : base(model, labels, correlationIdentifier) + { + return; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Product/DeleteDomainModelCommand.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Product/DeleteDomainModelCommand.cs new file mode 100644 index 00000000..07b3dcf1 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Product/DeleteDomainModelCommand.cs @@ -0,0 +1,121 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Product.DomainModel; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.Product +{ + /// + /// Represents a command to delete a . + /// + [DataContract(Name = "DeleteProductEvent")] + internal sealed class DeleteDomainModelCommand : DeleteDomainModelCommand + { + /// + /// Initializes a new instance of the class. + /// + public DeleteDomainModelCommand() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is equal to . + /// + public DeleteDomainModelCommand(Guid correlationIdentifier) + : base(correlationIdentifier) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// is . + /// + public DeleteDomainModelCommand(DomainModel model) + : base(model) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public DeleteDomainModelCommand(DomainModel model, Guid correlationIdentifier) + : base(model, correlationIdentifier) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// is -or- is . + /// + public DeleteDomainModelCommand(DomainModel model, IEnumerable labels) + : base(model, labels) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public DeleteDomainModelCommand(DomainModel model, IEnumerable labels, Guid correlationIdentifier) + : base(model, labels, correlationIdentifier) + { + return; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Product/UpdateDomainModelCommand.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Product/UpdateDomainModelCommand.cs new file mode 100644 index 00000000..2664d945 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Product/UpdateDomainModelCommand.cs @@ -0,0 +1,121 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Product.DomainModel; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.Product +{ + /// + /// Represents a command to update a . + /// + [DataContract(Name = "UpdateProductEvent")] + internal sealed class UpdateDomainModelCommand : UpdateDomainModelCommand + { + /// + /// Initializes a new instance of the class. + /// + public UpdateDomainModelCommand() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is equal to . + /// + public UpdateDomainModelCommand(Guid correlationIdentifier) + : base(correlationIdentifier) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// is . + /// + public UpdateDomainModelCommand(DomainModel model) + : base(model) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public UpdateDomainModelCommand(DomainModel model, Guid correlationIdentifier) + : base(model, correlationIdentifier) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// is -or- is . + /// + public UpdateDomainModelCommand(DomainModel model, IEnumerable labels) + : base(model, labels) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The desired state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the command. + /// + /// + /// A unique identifier that is assigned to related commands. + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public UpdateDomainModelCommand(DomainModel model, IEnumerable labels, Guid correlationIdentifier) + : base(model, labels, correlationIdentifier) + { + return; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/EventHandlers/ModelState/Customer/DomainModelCreatedEventHandler.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/EventHandlers/ModelState/Customer/DomainModelCreatedEventHandler.cs new file mode 100644 index 00000000..e9d0323c --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/EventHandlers/ModelState/Customer/DomainModelCreatedEventHandler.cs @@ -0,0 +1,63 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Concurrency; +using RapidField.SolidInstruments.EventAuthoring; +using System; +using System.Collections.Generic; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer.DomainModel; +using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.Customer.DomainModelCreatedEvent; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.EventHandlers.ModelState.Customer +{ + /// + /// Processes a single . + /// + internal class DomainModelCreatedEventHandler : DomainModelCreatedEventHandler + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// is . + /// + public DomainModelCreatedEventHandler(ICommandMediator mediator) + : base(mediator) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Processes the specified domain model. + /// + /// + /// The model that was created. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// A unique identifier to assign to sub-commands. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + protected override void Process(DomainModel model, IEnumerable labels, Guid correlationIdentifier, ICommandMediator mediator, IConcurrencyControlToken controlToken) => SimulatedServiceState.Customers.Add(model); + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/EventHandlers/ModelState/Customer/DomainModelDeletedEventHandler.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/EventHandlers/ModelState/Customer/DomainModelDeletedEventHandler.cs new file mode 100644 index 00000000..0793247a --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/EventHandlers/ModelState/Customer/DomainModelDeletedEventHandler.cs @@ -0,0 +1,74 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Concurrency; +using RapidField.SolidInstruments.EventAuthoring; +using System; +using System.Collections.Generic; +using System.Linq; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer.DomainModel; +using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.Customer.DomainModelDeletedEvent; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.EventHandlers.ModelState.Customer +{ + /// + /// Processes a single . + /// + internal class DomainModelDeletedEventHandler : DomainModelDeletedEventHandler + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// is . + /// + public DomainModelDeletedEventHandler(ICommandMediator mediator) + : base(mediator) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Processes the specified domain model. + /// + /// + /// The model that was deleted. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// A unique identifier to assign to sub-commands. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + protected override void Process(DomainModel model, IEnumerable labels, Guid correlationIdentifier, ICommandMediator mediator, IConcurrencyControlToken controlToken) + { + var existingModel = SimulatedServiceState.Customers.Where(entity => entity.Identifier == model.Identifier).FirstOrDefault(); + + if (existingModel is null) + { + return; + } + + SimulatedServiceState.Customers.Remove(existingModel); + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/EventHandlers/ModelState/Customer/DomainModelUpdatedEventHandler.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/EventHandlers/ModelState/Customer/DomainModelUpdatedEventHandler.cs new file mode 100644 index 00000000..b1a429a7 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/EventHandlers/ModelState/Customer/DomainModelUpdatedEventHandler.cs @@ -0,0 +1,75 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Concurrency; +using RapidField.SolidInstruments.EventAuthoring; +using System; +using System.Collections.Generic; +using System.Linq; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer.DomainModel; +using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.Customer.DomainModelUpdatedEvent; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.EventHandlers.ModelState.Customer +{ + /// + /// Processes a single . + /// + internal class DomainModelUpdatedEventHandler : DomainModelUpdatedEventHandler + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// is . + /// + public DomainModelUpdatedEventHandler(ICommandMediator mediator) + : base(mediator) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Processes the specified domain model. + /// + /// + /// The model that was updated. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// A unique identifier to assign to sub-commands. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + protected override void Process(DomainModel model, IEnumerable labels, Guid correlationIdentifier, ICommandMediator mediator, IConcurrencyControlToken controlToken) + { + var existingModel = SimulatedServiceState.Customers.Where(entity => entity.Identifier == model.Identifier).FirstOrDefault(); + + if (existingModel is null) + { + return; + } + + SimulatedServiceState.Customers.Remove(existingModel); + SimulatedServiceState.Customers.Add(model); + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/EventHandlers/ModelState/CustomerOrder/DomainModelCreatedEventHandler.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/EventHandlers/ModelState/CustomerOrder/DomainModelCreatedEventHandler.cs new file mode 100644 index 00000000..53531fd7 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/EventHandlers/ModelState/CustomerOrder/DomainModelCreatedEventHandler.cs @@ -0,0 +1,63 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Concurrency; +using RapidField.SolidInstruments.EventAuthoring; +using System; +using System.Collections.Generic; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.CustomerOrder.DomainModel; +using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.CustomerOrder.DomainModelCreatedEvent; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.EventHandlers.ModelState.CustomerOrder +{ + /// + /// Processes a single . + /// + internal class DomainModelCreatedEventHandler : DomainModelCreatedEventHandler + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// is . + /// + public DomainModelCreatedEventHandler(ICommandMediator mediator) + : base(mediator) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Processes the specified domain model. + /// + /// + /// The model that was created. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// A unique identifier to assign to sub-commands. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + protected override void Process(DomainModel model, IEnumerable labels, Guid correlationIdentifier, ICommandMediator mediator, IConcurrencyControlToken controlToken) => SimulatedServiceState.CustomerOrders.Add(model); + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/EventHandlers/ModelState/CustomerOrder/DomainModelDeletedEventHandler.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/EventHandlers/ModelState/CustomerOrder/DomainModelDeletedEventHandler.cs new file mode 100644 index 00000000..d79e9ffb --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/EventHandlers/ModelState/CustomerOrder/DomainModelDeletedEventHandler.cs @@ -0,0 +1,74 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Concurrency; +using RapidField.SolidInstruments.EventAuthoring; +using System; +using System.Collections.Generic; +using System.Linq; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.CustomerOrder.DomainModel; +using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.CustomerOrder.DomainModelDeletedEvent; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.EventHandlers.ModelState.CustomerOrder +{ + /// + /// Processes a single . + /// + internal class DomainModelDeletedEventHandler : DomainModelDeletedEventHandler + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// is . + /// + public DomainModelDeletedEventHandler(ICommandMediator mediator) + : base(mediator) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Processes the specified domain model. + /// + /// + /// The model that was deleted. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// A unique identifier to assign to sub-commands. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + protected override void Process(DomainModel model, IEnumerable labels, Guid correlationIdentifier, ICommandMediator mediator, IConcurrencyControlToken controlToken) + { + var existingModel = SimulatedServiceState.CustomerOrders.Where(entity => entity.Identifier == model.Identifier).FirstOrDefault(); + + if (existingModel is null) + { + return; + } + + SimulatedServiceState.CustomerOrders.Remove(existingModel); + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/EventHandlers/ModelState/CustomerOrder/DomainModelUpdatedEventHandler.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/EventHandlers/ModelState/CustomerOrder/DomainModelUpdatedEventHandler.cs new file mode 100644 index 00000000..357ee05f --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/EventHandlers/ModelState/CustomerOrder/DomainModelUpdatedEventHandler.cs @@ -0,0 +1,75 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Concurrency; +using RapidField.SolidInstruments.EventAuthoring; +using System; +using System.Collections.Generic; +using System.Linq; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.CustomerOrder.DomainModel; +using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.CustomerOrder.DomainModelUpdatedEvent; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.EventHandlers.ModelState.CustomerOrder +{ + /// + /// Processes a single . + /// + internal class DomainModelUpdatedEventHandler : DomainModelUpdatedEventHandler + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// is . + /// + public DomainModelUpdatedEventHandler(ICommandMediator mediator) + : base(mediator) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Processes the specified domain model. + /// + /// + /// The model that was updated. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// A unique identifier to assign to sub-commands. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + protected override void Process(DomainModel model, IEnumerable labels, Guid correlationIdentifier, ICommandMediator mediator, IConcurrencyControlToken controlToken) + { + var existingModel = SimulatedServiceState.CustomerOrders.Where(entity => entity.Identifier == model.Identifier).FirstOrDefault(); + + if (existingModel is null) + { + return; + } + + SimulatedServiceState.CustomerOrders.Remove(existingModel); + SimulatedServiceState.CustomerOrders.Add(model); + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/EventHandlers/ModelState/Product/DomainModelCreatedEventHandler.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/EventHandlers/ModelState/Product/DomainModelCreatedEventHandler.cs new file mode 100644 index 00000000..10febfea --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/EventHandlers/ModelState/Product/DomainModelCreatedEventHandler.cs @@ -0,0 +1,63 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Concurrency; +using RapidField.SolidInstruments.EventAuthoring; +using System; +using System.Collections.Generic; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Product.DomainModel; +using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.Product.DomainModelCreatedEvent; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.EventHandlers.ModelState.Product +{ + /// + /// Processes a single . + /// + internal class DomainModelCreatedEventHandler : DomainModelCreatedEventHandler + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// is . + /// + public DomainModelCreatedEventHandler(ICommandMediator mediator) + : base(mediator) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Processes the specified domain model. + /// + /// + /// The model that was created. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// A unique identifier to assign to sub-commands. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + protected override void Process(DomainModel model, IEnumerable labels, Guid correlationIdentifier, ICommandMediator mediator, IConcurrencyControlToken controlToken) => SimulatedServiceState.Products.Add(model); + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/EventHandlers/ModelState/Product/DomainModelDeletedEventHandler.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/EventHandlers/ModelState/Product/DomainModelDeletedEventHandler.cs new file mode 100644 index 00000000..8a685288 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/EventHandlers/ModelState/Product/DomainModelDeletedEventHandler.cs @@ -0,0 +1,74 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Concurrency; +using RapidField.SolidInstruments.EventAuthoring; +using System; +using System.Collections.Generic; +using System.Linq; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Product.DomainModel; +using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.Product.DomainModelDeletedEvent; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.EventHandlers.ModelState.Product +{ + /// + /// Processes a single . + /// + internal class DomainModelDeletedEventHandler : DomainModelDeletedEventHandler + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// is . + /// + public DomainModelDeletedEventHandler(ICommandMediator mediator) + : base(mediator) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Processes the specified domain model. + /// + /// + /// The model that was deleted. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// A unique identifier to assign to sub-commands. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + protected override void Process(DomainModel model, IEnumerable labels, Guid correlationIdentifier, ICommandMediator mediator, IConcurrencyControlToken controlToken) + { + var existingModel = SimulatedServiceState.Products.Where(entity => entity.Identifier == model.Identifier).FirstOrDefault(); + + if (existingModel is null) + { + return; + } + + SimulatedServiceState.Products.Remove(existingModel); + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/EventHandlers/ModelState/Product/DomainModelUpdatedEventHandler.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/EventHandlers/ModelState/Product/DomainModelUpdatedEventHandler.cs new file mode 100644 index 00000000..caa736ca --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/EventHandlers/ModelState/Product/DomainModelUpdatedEventHandler.cs @@ -0,0 +1,75 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Concurrency; +using RapidField.SolidInstruments.EventAuthoring; +using System; +using System.Collections.Generic; +using System.Linq; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Product.DomainModel; +using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.Product.DomainModelUpdatedEvent; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.EventHandlers.ModelState.Product +{ + /// + /// Processes a single . + /// + internal class DomainModelUpdatedEventHandler : DomainModelUpdatedEventHandler + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// is . + /// + public DomainModelUpdatedEventHandler(ICommandMediator mediator) + : base(mediator) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Processes the specified domain model. + /// + /// + /// The model that was updated. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// A unique identifier to assign to sub-commands. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + protected override void Process(DomainModel model, IEnumerable labels, Guid correlationIdentifier, ICommandMediator mediator, IConcurrencyControlToken controlToken) + { + var existingModel = SimulatedServiceState.Products.Where(entity => entity.Identifier == model.Identifier).FirstOrDefault(); + + if (existingModel is null) + { + return; + } + + SimulatedServiceState.Products.Remove(existingModel); + SimulatedServiceState.Products.Add(model); + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Customer/DomainModelCreatedEvent.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Customer/DomainModelCreatedEvent.cs index 268b6335..03b3186b 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Customer/DomainModelCreatedEvent.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Customer/DomainModelCreatedEvent.cs @@ -25,6 +25,21 @@ public DomainModelCreatedEvent() return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to . + /// + public DomainModelCreatedEvent(Guid correlationIdentifier) + : base(correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -40,6 +55,27 @@ public DomainModelCreatedEvent(DomainModel model) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public DomainModelCreatedEvent(DomainModel model, Guid correlationIdentifier) + : base(model, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -58,6 +94,30 @@ public DomainModelCreatedEvent(DomainModel model, IEnumerable labels) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public DomainModelCreatedEvent(DomainModel model, IEnumerable labels, Guid correlationIdentifier) + : base(model, labels, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -81,5 +141,33 @@ public DomainModelCreatedEvent(DomainModel model, IEnumerable labels, Ev { return; } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is -or- is . + /// + /// + /// is equal to -or- + /// is equal to . + /// + public DomainModelCreatedEvent(DomainModel model, IEnumerable labels, EventVerbosity verbosity, Guid correlationIdentifier) + : base(model, labels, verbosity, correlationIdentifier) + { + return; + } } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Customer/DomainModelDeletedEvent.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Customer/DomainModelDeletedEvent.cs index 9cb0ba38..d17a2e1c 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Customer/DomainModelDeletedEvent.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Customer/DomainModelDeletedEvent.cs @@ -25,6 +25,21 @@ public DomainModelDeletedEvent() return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to . + /// + public DomainModelDeletedEvent(Guid correlationIdentifier) + : base(correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -40,6 +55,27 @@ public DomainModelDeletedEvent(DomainModel model) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public DomainModelDeletedEvent(DomainModel model, Guid correlationIdentifier) + : base(model, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -58,6 +94,30 @@ public DomainModelDeletedEvent(DomainModel model, IEnumerable labels) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public DomainModelDeletedEvent(DomainModel model, IEnumerable labels, Guid correlationIdentifier) + : base(model, labels, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -81,5 +141,33 @@ public DomainModelDeletedEvent(DomainModel model, IEnumerable labels, Ev { return; } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is -or- is . + /// + /// + /// is equal to -or- + /// is equal to . + /// + public DomainModelDeletedEvent(DomainModel model, IEnumerable labels, EventVerbosity verbosity, Guid correlationIdentifier) + : base(model, labels, verbosity, correlationIdentifier) + { + return; + } } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Customer/DomainModelUpdatedEvent.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Customer/DomainModelUpdatedEvent.cs index 339124d7..47fc6e3a 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Customer/DomainModelUpdatedEvent.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Customer/DomainModelUpdatedEvent.cs @@ -25,6 +25,21 @@ public DomainModelUpdatedEvent() return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to . + /// + public DomainModelUpdatedEvent(Guid correlationIdentifier) + : base(correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -40,6 +55,27 @@ public DomainModelUpdatedEvent(DomainModel model) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public DomainModelUpdatedEvent(DomainModel model, Guid correlationIdentifier) + : base(model, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -58,6 +94,30 @@ public DomainModelUpdatedEvent(DomainModel model, IEnumerable labels) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public DomainModelUpdatedEvent(DomainModel model, IEnumerable labels, Guid correlationIdentifier) + : base(model, labels, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -81,5 +141,33 @@ public DomainModelUpdatedEvent(DomainModel model, IEnumerable labels, Ev { return; } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is -or- is . + /// + /// + /// is equal to -or- + /// is equal to . + /// + public DomainModelUpdatedEvent(DomainModel model, IEnumerable labels, EventVerbosity verbosity, Guid correlationIdentifier) + : base(model, labels, verbosity, correlationIdentifier) + { + return; + } } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/CustomerOrder/DomainModelCreatedEvent.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/CustomerOrder/DomainModelCreatedEvent.cs index c7e8a0c5..bca4767b 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/CustomerOrder/DomainModelCreatedEvent.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/CustomerOrder/DomainModelCreatedEvent.cs @@ -25,6 +25,21 @@ public DomainModelCreatedEvent() return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to . + /// + public DomainModelCreatedEvent(Guid correlationIdentifier) + : base(correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -40,6 +55,27 @@ public DomainModelCreatedEvent(DomainModel model) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public DomainModelCreatedEvent(DomainModel model, Guid correlationIdentifier) + : base(model, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -58,6 +94,30 @@ public DomainModelCreatedEvent(DomainModel model, IEnumerable labels) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public DomainModelCreatedEvent(DomainModel model, IEnumerable labels, Guid correlationIdentifier) + : base(model, labels, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -81,5 +141,33 @@ public DomainModelCreatedEvent(DomainModel model, IEnumerable labels, Ev { return; } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is -or- is . + /// + /// + /// is equal to -or- + /// is equal to . + /// + public DomainModelCreatedEvent(DomainModel model, IEnumerable labels, EventVerbosity verbosity, Guid correlationIdentifier) + : base(model, labels, verbosity, correlationIdentifier) + { + return; + } } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/CustomerOrder/DomainModelDeletedEvent.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/CustomerOrder/DomainModelDeletedEvent.cs index 2eef8924..61b8e6d6 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/CustomerOrder/DomainModelDeletedEvent.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/CustomerOrder/DomainModelDeletedEvent.cs @@ -25,6 +25,21 @@ public DomainModelDeletedEvent() return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to . + /// + public DomainModelDeletedEvent(Guid correlationIdentifier) + : base(correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -40,6 +55,27 @@ public DomainModelDeletedEvent(DomainModel model) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public DomainModelDeletedEvent(DomainModel model, Guid correlationIdentifier) + : base(model, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -58,6 +94,30 @@ public DomainModelDeletedEvent(DomainModel model, IEnumerable labels) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public DomainModelDeletedEvent(DomainModel model, IEnumerable labels, Guid correlationIdentifier) + : base(model, labels, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -81,5 +141,33 @@ public DomainModelDeletedEvent(DomainModel model, IEnumerable labels, Ev { return; } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is -or- is . + /// + /// + /// is equal to -or- + /// is equal to . + /// + public DomainModelDeletedEvent(DomainModel model, IEnumerable labels, EventVerbosity verbosity, Guid correlationIdentifier) + : base(model, labels, verbosity, correlationIdentifier) + { + return; + } } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/CustomerOrder/DomainModelUpdatedEvent.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/CustomerOrder/DomainModelUpdatedEvent.cs index 88e028c4..389ba971 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/CustomerOrder/DomainModelUpdatedEvent.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/CustomerOrder/DomainModelUpdatedEvent.cs @@ -25,6 +25,21 @@ public DomainModelUpdatedEvent() return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to . + /// + public DomainModelUpdatedEvent(Guid correlationIdentifier) + : base(correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -40,6 +55,27 @@ public DomainModelUpdatedEvent(DomainModel model) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public DomainModelUpdatedEvent(DomainModel model, Guid correlationIdentifier) + : base(model, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -58,6 +94,30 @@ public DomainModelUpdatedEvent(DomainModel model, IEnumerable labels) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public DomainModelUpdatedEvent(DomainModel model, IEnumerable labels, Guid correlationIdentifier) + : base(model, labels, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -81,5 +141,33 @@ public DomainModelUpdatedEvent(DomainModel model, IEnumerable labels, Ev { return; } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is -or- is . + /// + /// + /// is equal to -or- + /// is equal to . + /// + public DomainModelUpdatedEvent(DomainModel model, IEnumerable labels, EventVerbosity verbosity, Guid correlationIdentifier) + : base(model, labels, verbosity, correlationIdentifier) + { + return; + } } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Product/DomainModelCreatedEvent.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Product/DomainModelCreatedEvent.cs index c040758b..b4a85a1d 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Product/DomainModelCreatedEvent.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Product/DomainModelCreatedEvent.cs @@ -25,6 +25,21 @@ public DomainModelCreatedEvent() return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to . + /// + public DomainModelCreatedEvent(Guid correlationIdentifier) + : base(correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -40,6 +55,27 @@ public DomainModelCreatedEvent(DomainModel model) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public DomainModelCreatedEvent(DomainModel model, Guid correlationIdentifier) + : base(model, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -58,6 +94,30 @@ public DomainModelCreatedEvent(DomainModel model, IEnumerable labels) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public DomainModelCreatedEvent(DomainModel model, IEnumerable labels, Guid correlationIdentifier) + : base(model, labels, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -81,5 +141,33 @@ public DomainModelCreatedEvent(DomainModel model, IEnumerable labels, Ev { return; } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is -or- is . + /// + /// + /// is equal to -or- + /// is equal to . + /// + public DomainModelCreatedEvent(DomainModel model, IEnumerable labels, EventVerbosity verbosity, Guid correlationIdentifier) + : base(model, labels, verbosity, correlationIdentifier) + { + return; + } } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Product/DomainModelDeletedEvent.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Product/DomainModelDeletedEvent.cs index 97765e48..bf1450f0 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Product/DomainModelDeletedEvent.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Product/DomainModelDeletedEvent.cs @@ -25,6 +25,21 @@ public DomainModelDeletedEvent() return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to . + /// + public DomainModelDeletedEvent(Guid correlationIdentifier) + : base(correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -40,6 +55,27 @@ public DomainModelDeletedEvent(DomainModel model) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public DomainModelDeletedEvent(DomainModel model, Guid correlationIdentifier) + : base(model, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -58,6 +94,30 @@ public DomainModelDeletedEvent(DomainModel model, IEnumerable labels) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public DomainModelDeletedEvent(DomainModel model, IEnumerable labels, Guid correlationIdentifier) + : base(model, labels, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -81,5 +141,33 @@ public DomainModelDeletedEvent(DomainModel model, IEnumerable labels, Ev { return; } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is -or- is . + /// + /// + /// is equal to -or- + /// is equal to . + /// + public DomainModelDeletedEvent(DomainModel model, IEnumerable labels, EventVerbosity verbosity, Guid correlationIdentifier) + : base(model, labels, verbosity, correlationIdentifier) + { + return; + } } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Product/DomainModelUpdatedEvent.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Product/DomainModelUpdatedEvent.cs index a097ba97..c52daa51 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Product/DomainModelUpdatedEvent.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Product/DomainModelUpdatedEvent.cs @@ -25,6 +25,21 @@ public DomainModelUpdatedEvent() return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is equal to . + /// + public DomainModelUpdatedEvent(Guid correlationIdentifier) + : base(correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -40,6 +55,27 @@ public DomainModelUpdatedEvent(DomainModel model) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + public DomainModelUpdatedEvent(DomainModel model, Guid correlationIdentifier) + : base(model, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -58,6 +94,30 @@ public DomainModelUpdatedEvent(DomainModel model, IEnumerable labels) return; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is -or- is . + /// + /// + /// is equal to . + /// + public DomainModelUpdatedEvent(DomainModel model, IEnumerable labels, Guid correlationIdentifier) + : base(model, labels, correlationIdentifier) + { + return; + } + /// /// Initializes a new instance of the class. /// @@ -81,5 +141,33 @@ public DomainModelUpdatedEvent(DomainModel model, IEnumerable labels, Ev { return; } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The resulting state of the associated domain model. + /// + /// + /// A collection of textual labels that provide categorical and/or contextual information about the event. + /// + /// + /// The verbosity level of the event. The default value is + /// + /// + /// A unique identifier that is assigned to related events. + /// + /// + /// is -or- is . + /// + /// + /// is equal to -or- + /// is equal to . + /// + public DomainModelUpdatedEvent(DomainModel model, IEnumerable labels, EventVerbosity verbosity, Guid correlationIdentifier) + : base(model, labels, verbosity, correlationIdentifier) + { + return; + } } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/InMemoryClientFactoryTests.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/InMemoryClientFactoryTests.cs index 73d0a382..1c687597 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/InMemoryClientFactoryTests.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/InMemoryClientFactoryTests.cs @@ -2,9 +2,13 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= +using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using RapidField.SolidInstruments.Messaging.TransportPrimitives; using RapidField.SolidInstruments.Serialization; +using System; +using System.Linq; +using System.Runtime.Serialization; namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests { @@ -12,10 +16,92 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests public class InMemoryClientFactoryTests { [TestMethod] - public void GetQueuePath_Should() + public void GetQueuePath_ShouldProduceDesiredResults_ForPathWithOneLabel() + { + // Arrange. + var entityType = MessagingEntityType.Queue; + + // Act. + GetPath_ShouldProduceDesiredResults_ForPathWithOneLabel(entityType); + } + + [TestMethod] + public void GetQueuePath_ShouldProduceDesiredResults_ForPathWithoutLabels() + { + // Arrange. + var entityType = MessagingEntityType.Queue; + + // Act. + GetPath_ShouldProduceDesiredResults_ForPathWithoutLabels(entityType); + } + + [TestMethod] + public void GetQueuePath_ShouldProduceDesiredResults_ForPathWithThreeLabels() + { + // Arrange. + var entityType = MessagingEntityType.Queue; + + // Act. + GetPath_ShouldProduceDesiredResults_ForPathWithThreeLabels(entityType); + } + + [TestMethod] + public void GetQueuePath_ShouldProduceDesiredResults_ForPathWithTwoLabels() + { + // Arrange. + var entityType = MessagingEntityType.Queue; + + // Act. + GetPath_ShouldProduceDesiredResults_ForPathWithTwoLabels(entityType); + } + + [TestMethod] + public void GetTopicPath_ShouldProduceDesiredResults_ForPathWithOneLabel() + { + // Arrange. + var entityType = MessagingEntityType.Topic; + + // Act. + GetPath_ShouldProduceDesiredResults_ForPathWithOneLabel(entityType); + } + + [TestMethod] + public void GetTopicPath_ShouldProduceDesiredResults_ForPathWithoutLabels() + { + // Arrange. + var entityType = MessagingEntityType.Topic; + + // Act. + GetPath_ShouldProduceDesiredResults_ForPathWithoutLabels(entityType); + } + + [TestMethod] + public void GetTopicPath_ShouldProduceDesiredResults_ForPathWithThreeLabels() + { + // Arrange. + var entityType = MessagingEntityType.Topic; + + // Act. + GetPath_ShouldProduceDesiredResults_ForPathWithThreeLabels(entityType); + } + + [TestMethod] + public void GetTopicPath_ShouldProduceDesiredResults_ForPathWithTwoLabels() + { + // Arrange. + var entityType = MessagingEntityType.Topic; + + // Act. + GetPath_ShouldProduceDesiredResults_ForPathWithTwoLabels(entityType); + } + + private static void GetPath_ShouldProduceDesiredResults(MessagingEntityType entityType, String[] pathLabels) + where TMessage : class, IMessage { // Arrange. var serializationFormat = SerializationFormat.CompressedJson; + var messageType = typeof(TMessage); + var messageTypeName = (messageType.GetCustomAttributes(typeof(DataContractAttribute), false).FirstOrDefault() as DataContractAttribute)?.Name ?? messageType.Name; using (var transport = new MessageTransport(serializationFormat)) { @@ -24,9 +110,66 @@ public void GetQueuePath_Should() using (var target = new InMemoryClientFactory(connection)) { // Act. + var path = entityType switch + { + MessagingEntityType.Queue => target.GetQueuePath(pathLabels), + MessagingEntityType.Topic => target.GetTopicPath(pathLabels), + _ => null + }; + + // Assert. + path.Should().NotBeNull(); + path.LabelOne.Should().Be(pathLabels.Length > 0 ? pathLabels[0] : null); + path.LabelTwo.Should().Be(pathLabels.Length > 1 ? pathLabels[1] : null); + path.LabelThree.Should().Be(pathLabels.Length > 2 ? pathLabels[2] : null); + path.MessageType.Should().NotBeNullOrEmpty(); + path.MessageType.Should().Be(messageTypeName); + path.Prefix.Should().BeNull(); + + // Act. + var pathString = path.ToString(); + + // Assert. + pathString.Should().NotBeNullOrEmpty(); } } } } + + private static void GetPath_ShouldProduceDesiredResults_ForPathWithOneLabel(MessagingEntityType entityType) + { + // Arrange. + var pathLabels = new String[] { "foo" }; + + // Act. + GetPath_ShouldProduceDesiredResults(entityType, pathLabels); + } + + private static void GetPath_ShouldProduceDesiredResults_ForPathWithoutLabels(MessagingEntityType entityType) + { + // Arrange. + var pathLabels = Array.Empty(); + + // Act. + GetPath_ShouldProduceDesiredResults(entityType, pathLabels); + } + + private static void GetPath_ShouldProduceDesiredResults_ForPathWithThreeLabels(MessagingEntityType entityType) + { + // Arrange. + var pathLabels = new String[] { "foo", "bar", "baz" }; + + // Act. + GetPath_ShouldProduceDesiredResults(entityType, pathLabels); + } + + private static void GetPath_ShouldProduceDesiredResults_ForPathWithTwoLabels(MessagingEntityType entityType) + { + // Arrange. + var pathLabels = new String[] { "foo", "bar" }; + + // Act. + GetPath_ShouldProduceDesiredResults(entityType, pathLabels); + } } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Customer/CreateDomainModelCommandMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Customer/CreateDomainModelCommandMessage.cs new file mode 100644 index 00000000..65844b86 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Customer/CreateDomainModelCommandMessage.cs @@ -0,0 +1,44 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Runtime.Serialization; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer.DomainModel; +using DomainModelCommand = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.Customer.CreateDomainModelCommand; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Command.ModelState.Customer +{ + using DomainModelCommandMessage = CommandMessages.CreateDomainModelCommandMessage; + + /// + /// Represents a message that contains a command to create a . + /// + [DataContract(Name = "CreateCustomerCommandMessage")] + internal sealed class CreateDomainModelCommandMessage : DomainModelCommandMessage + { + /// + /// Initializes a new instance of the class. + /// + public CreateDomainModelCommandMessage() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated command. + /// + /// + /// is . + /// + public CreateDomainModelCommandMessage(DomainModelCommand commandObject) + : base(commandObject) + { + return; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Customer/DeleteDomainModelCommandMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Customer/DeleteDomainModelCommandMessage.cs new file mode 100644 index 00000000..2d154e1b --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Customer/DeleteDomainModelCommandMessage.cs @@ -0,0 +1,44 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Runtime.Serialization; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer.DomainModel; +using DomainModelCommand = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.Customer.DeleteDomainModelCommand; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Command.ModelState.Customer +{ + using DomainModelCommandMessage = CommandMessages.DeleteDomainModelCommandMessage; + + /// + /// Represents a message that contains a command to delete a . + /// + [DataContract(Name = "DeleteCustomerCommandMessage")] + internal sealed class DeleteDomainModelCommandMessage : DomainModelCommandMessage + { + /// + /// Initializes a new instance of the class. + /// + public DeleteDomainModelCommandMessage() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated command. + /// + /// + /// is . + /// + public DeleteDomainModelCommandMessage(DomainModelCommand commandObject) + : base(commandObject) + { + return; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Customer/UpdateDomainModelCommandMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Customer/UpdateDomainModelCommandMessage.cs new file mode 100644 index 00000000..af24ecc4 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Customer/UpdateDomainModelCommandMessage.cs @@ -0,0 +1,44 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Runtime.Serialization; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer.DomainModel; +using DomainModelCommand = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.Customer.UpdateDomainModelCommand; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Command.ModelState.Customer +{ + using DomainModelCommandMessage = CommandMessages.UpdateDomainModelCommandMessage; + + /// + /// Represents a message that contains a command to update a . + /// + [DataContract(Name = "UpdateCustomerCommandMessage")] + internal sealed class UpdateDomainModelCommandMessage : DomainModelCommandMessage + { + /// + /// Initializes a new instance of the class. + /// + public UpdateDomainModelCommandMessage() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated command. + /// + /// + /// is . + /// + public UpdateDomainModelCommandMessage(DomainModelCommand commandObject) + : base(commandObject) + { + return; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/CustomerOrder/CreateDomainModelCommandMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/CustomerOrder/CreateDomainModelCommandMessage.cs new file mode 100644 index 00000000..ae66b0df --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/CustomerOrder/CreateDomainModelCommandMessage.cs @@ -0,0 +1,44 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Runtime.Serialization; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.CustomerOrder.DomainModel; +using DomainModelCommand = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.CustomerOrder.CreateDomainModelCommand; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Command.ModelState.CustomerOrder +{ + using DomainModelCommandMessage = CommandMessages.CreateDomainModelCommandMessage; + + /// + /// Represents a message that contains a command to create a . + /// + [DataContract(Name = "CreateCustomerOrderCommandMessage")] + internal sealed class CreateDomainModelCommandMessage : DomainModelCommandMessage + { + /// + /// Initializes a new instance of the class. + /// + public CreateDomainModelCommandMessage() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated command. + /// + /// + /// is . + /// + public CreateDomainModelCommandMessage(DomainModelCommand commandObject) + : base(commandObject) + { + return; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/CustomerOrder/DeleteDomainModelCommandMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/CustomerOrder/DeleteDomainModelCommandMessage.cs new file mode 100644 index 00000000..f148b15a --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/CustomerOrder/DeleteDomainModelCommandMessage.cs @@ -0,0 +1,44 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Runtime.Serialization; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.CustomerOrder.DomainModel; +using DomainModelCommand = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.CustomerOrder.DeleteDomainModelCommand; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Command.ModelState.CustomerOrder +{ + using DomainModelCommandMessage = CommandMessages.DeleteDomainModelCommandMessage; + + /// + /// Represents a message that contains a command to delete a . + /// + [DataContract(Name = "DeleteCustomerOrderCommandMessage")] + internal sealed class DeleteDomainModelCommandMessage : DomainModelCommandMessage + { + /// + /// Initializes a new instance of the class. + /// + public DeleteDomainModelCommandMessage() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated command. + /// + /// + /// is . + /// + public DeleteDomainModelCommandMessage(DomainModelCommand commandObject) + : base(commandObject) + { + return; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/CustomerOrder/UpdateDomainModelCommandMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/CustomerOrder/UpdateDomainModelCommandMessage.cs new file mode 100644 index 00000000..06ed823f --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/CustomerOrder/UpdateDomainModelCommandMessage.cs @@ -0,0 +1,44 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Runtime.Serialization; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.CustomerOrder.DomainModel; +using DomainModelCommand = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.CustomerOrder.UpdateDomainModelCommand; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Command.ModelState.CustomerOrder +{ + using DomainModelCommandMessage = CommandMessages.UpdateDomainModelCommandMessage; + + /// + /// Represents a message that contains a command to update a . + /// + [DataContract(Name = "UpdateCustomerOrderCommandMessage")] + internal sealed class UpdateDomainModelCommandMessage : DomainModelCommandMessage + { + /// + /// Initializes a new instance of the class. + /// + public UpdateDomainModelCommandMessage() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated command. + /// + /// + /// is . + /// + public UpdateDomainModelCommandMessage(DomainModelCommand commandObject) + : base(commandObject) + { + return; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Product/CreateDomainModelCommandMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Product/CreateDomainModelCommandMessage.cs new file mode 100644 index 00000000..f8852532 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Product/CreateDomainModelCommandMessage.cs @@ -0,0 +1,44 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Runtime.Serialization; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Product.DomainModel; +using DomainModelCommand = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.Product.CreateDomainModelCommand; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Command.ModelState.Product +{ + using DomainModelCommandMessage = CommandMessages.CreateDomainModelCommandMessage; + + /// + /// Represents a message that contains a command to create a . + /// + [DataContract(Name = "CreateProductCommandMessage")] + internal sealed class CreateDomainModelCommandMessage : DomainModelCommandMessage + { + /// + /// Initializes a new instance of the class. + /// + public CreateDomainModelCommandMessage() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated command. + /// + /// + /// is . + /// + public CreateDomainModelCommandMessage(DomainModelCommand commandObject) + : base(commandObject) + { + return; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Product/DeleteDomainModelCommandMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Product/DeleteDomainModelCommandMessage.cs new file mode 100644 index 00000000..ba4cab48 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Product/DeleteDomainModelCommandMessage.cs @@ -0,0 +1,44 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Runtime.Serialization; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Product.DomainModel; +using DomainModelCommand = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.Product.DeleteDomainModelCommand; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Command.ModelState.Product +{ + using DomainModelCommandMessage = CommandMessages.DeleteDomainModelCommandMessage; + + /// + /// Represents a message that contains a command to delete a . + /// + [DataContract(Name = "DeleteProductCommandMessage")] + internal sealed class DeleteDomainModelCommandMessage : DomainModelCommandMessage + { + /// + /// Initializes a new instance of the class. + /// + public DeleteDomainModelCommandMessage() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated command. + /// + /// + /// is . + /// + public DeleteDomainModelCommandMessage(DomainModelCommand commandObject) + : base(commandObject) + { + return; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Product/UpdateDomainModelCommandMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Product/UpdateDomainModelCommandMessage.cs new file mode 100644 index 00000000..c2eb71cc --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Product/UpdateDomainModelCommandMessage.cs @@ -0,0 +1,44 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Runtime.Serialization; +using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Product.DomainModel; +using DomainModelCommand = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.Product.UpdateDomainModelCommand; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Command.ModelState.Product +{ + using DomainModelCommandMessage = CommandMessages.UpdateDomainModelCommandMessage; + + /// + /// Represents a message that contains a command to update a . + /// + [DataContract(Name = "UpdateProductCommandMessage")] + internal sealed class UpdateDomainModelCommandMessage : DomainModelCommandMessage + { + /// + /// Initializes a new instance of the class. + /// + public UpdateDomainModelCommandMessage() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated command. + /// + /// + /// is . + /// + public UpdateDomainModelCommandMessage(DomainModelCommand commandObject) + : base(commandObject) + { + return; + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Customer/DomainModelCreatedEventMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Customer/DomainModelCreatedEventMessage.cs index 8342969e..5a899e98 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Customer/DomainModelCreatedEventMessage.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Customer/DomainModelCreatedEventMessage.cs @@ -6,10 +6,11 @@ using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer.DomainModel; using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.Customer.DomainModelCreatedEvent; -using DomainModelEventMessage = RapidField.SolidInstruments.Messaging.EventMessages.DomainModelCreatedEventMessage; namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Event.ModelState.Customer { + using DomainModelEventMessage = EventMessages.DomainModelCreatedEventMessage; + /// /// Represents a message that provides notification about the creation of a . /// @@ -39,26 +40,5 @@ public DomainModelCreatedEventMessage(DomainModelEvent eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - public DomainModelCreatedEventMessage(DomainModelEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Customer/DomainModelDeletedEventMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Customer/DomainModelDeletedEventMessage.cs index 507b39c1..657d98f8 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Customer/DomainModelDeletedEventMessage.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Customer/DomainModelDeletedEventMessage.cs @@ -6,10 +6,11 @@ using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer.DomainModel; using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.Customer.DomainModelDeletedEvent; -using DomainModelEventMessage = RapidField.SolidInstruments.Messaging.EventMessages.DomainModelDeletedEventMessage; namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Event.ModelState.Customer { + using DomainModelEventMessage = EventMessages.DomainModelDeletedEventMessage; + /// /// Represents a message that provides notification about the deletion of a . /// @@ -39,26 +40,5 @@ public DomainModelDeletedEventMessage(DomainModelEvent eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - public DomainModelDeletedEventMessage(DomainModelEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Customer/DomainModelUpdatedEventMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Customer/DomainModelUpdatedEventMessage.cs index f98905d5..7ab8c5a9 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Customer/DomainModelUpdatedEventMessage.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Customer/DomainModelUpdatedEventMessage.cs @@ -6,10 +6,11 @@ using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer.DomainModel; using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.Customer.DomainModelUpdatedEvent; -using DomainModelEventMessage = RapidField.SolidInstruments.Messaging.EventMessages.DomainModelUpdatedEventMessage; namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Event.ModelState.Customer { + using DomainModelEventMessage = EventMessages.DomainModelUpdatedEventMessage; + /// /// Represents a message that provides notification about an update to a . /// @@ -39,26 +40,5 @@ public DomainModelUpdatedEventMessage(DomainModelEvent eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - public DomainModelUpdatedEventMessage(DomainModelEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/CustomerOrder/DomainModelCreatedEventMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/CustomerOrder/DomainModelCreatedEventMessage.cs index 3e8f2aaa..5842fadd 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/CustomerOrder/DomainModelCreatedEventMessage.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/CustomerOrder/DomainModelCreatedEventMessage.cs @@ -6,10 +6,11 @@ using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.CustomerOrder.DomainModel; using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.CustomerOrder.DomainModelCreatedEvent; -using DomainModelEventMessage = RapidField.SolidInstruments.Messaging.EventMessages.DomainModelCreatedEventMessage; namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Event.ModelState.CustomerOrder { + using DomainModelEventMessage = EventMessages.DomainModelCreatedEventMessage; + /// /// Represents a message that provides notification about the creation of a . /// @@ -39,26 +40,5 @@ public DomainModelCreatedEventMessage(DomainModelEvent eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - public DomainModelCreatedEventMessage(DomainModelEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/CustomerOrder/DomainModelDeletedEventMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/CustomerOrder/DomainModelDeletedEventMessage.cs index 3df77587..b401b7dd 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/CustomerOrder/DomainModelDeletedEventMessage.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/CustomerOrder/DomainModelDeletedEventMessage.cs @@ -6,10 +6,11 @@ using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.CustomerOrder.DomainModel; using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.CustomerOrder.DomainModelDeletedEvent; -using DomainModelEventMessage = RapidField.SolidInstruments.Messaging.EventMessages.DomainModelDeletedEventMessage; namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Event.ModelState.CustomerOrder { + using DomainModelEventMessage = EventMessages.DomainModelDeletedEventMessage; + /// /// Represents a message that provides notification about the deletion of a . /// @@ -39,26 +40,5 @@ public DomainModelDeletedEventMessage(DomainModelEvent eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - public DomainModelDeletedEventMessage(DomainModelEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/CustomerOrder/DomainModelUpdatedEventMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/CustomerOrder/DomainModelUpdatedEventMessage.cs index bb5e8041..c8da1d1e 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/CustomerOrder/DomainModelUpdatedEventMessage.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/CustomerOrder/DomainModelUpdatedEventMessage.cs @@ -6,10 +6,11 @@ using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.CustomerOrder.DomainModel; using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.CustomerOrder.DomainModelUpdatedEvent; -using DomainModelEventMessage = RapidField.SolidInstruments.Messaging.EventMessages.DomainModelUpdatedEventMessage; namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Event.ModelState.CustomerOrder { + using DomainModelEventMessage = EventMessages.DomainModelUpdatedEventMessage; + /// /// Represents a message that provides notification about an update to a . /// @@ -39,26 +40,5 @@ public DomainModelUpdatedEventMessage(DomainModelEvent eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - public DomainModelUpdatedEventMessage(DomainModelEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Product/DomainModelCreatedEventMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Product/DomainModelCreatedEventMessage.cs index d9cbdabf..69c53952 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Product/DomainModelCreatedEventMessage.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Product/DomainModelCreatedEventMessage.cs @@ -6,10 +6,11 @@ using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Product.DomainModel; using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.Product.DomainModelCreatedEvent; -using DomainModelEventMessage = RapidField.SolidInstruments.Messaging.EventMessages.DomainModelCreatedEventMessage; namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Event.ModelState.Product { + using DomainModelEventMessage = EventMessages.DomainModelCreatedEventMessage; + /// /// Represents a message that provides notification about the creation of a . /// @@ -39,26 +40,5 @@ public DomainModelCreatedEventMessage(DomainModelEvent eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - public DomainModelCreatedEventMessage(DomainModelEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Product/DomainModelDeletedEventMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Product/DomainModelDeletedEventMessage.cs index d4eb334d..4837a715 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Product/DomainModelDeletedEventMessage.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Product/DomainModelDeletedEventMessage.cs @@ -6,10 +6,11 @@ using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Product.DomainModel; using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.Product.DomainModelDeletedEvent; -using DomainModelEventMessage = RapidField.SolidInstruments.Messaging.EventMessages.DomainModelDeletedEventMessage; namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Event.ModelState.Product { + using DomainModelEventMessage = EventMessages.DomainModelDeletedEventMessage; + /// /// Represents a message that provides notification about the deletion of a . /// @@ -39,26 +40,5 @@ public DomainModelDeletedEventMessage(DomainModelEvent eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - public DomainModelDeletedEventMessage(DomainModelEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Product/DomainModelUpdatedEventMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Product/DomainModelUpdatedEventMessage.cs index 7ca116ff..eae5c1e9 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Product/DomainModelUpdatedEventMessage.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Product/DomainModelUpdatedEventMessage.cs @@ -6,10 +6,11 @@ using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Product.DomainModel; using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.Product.DomainModelUpdatedEvent; -using DomainModelEventMessage = RapidField.SolidInstruments.Messaging.EventMessages.DomainModelUpdatedEventMessage; namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Event.ModelState.Product { + using DomainModelEventMessage = EventMessages.DomainModelUpdatedEventMessage; + /// /// Represents a message that provides notification about an update to a . /// @@ -39,26 +40,5 @@ public DomainModelUpdatedEventMessage(DomainModelEvent eventObject) { return; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The associated event. - /// - /// - /// A unique identifier that is assigned to related messages. - /// - /// - /// is . - /// - /// - /// is equal to . - /// - public DomainModelUpdatedEventMessage(DomainModelEvent eventObject, Guid correlationIdentifier) - : base(eventObject, correlationIdentifier) - { - return; - } } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/RapidField.SolidInstruments.Messaging.InMemory.UnitTests.csproj b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/RapidField.SolidInstruments.Messaging.InMemory.UnitTests.csproj index 7c1aaa73..f7a60df8 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/RapidField.SolidInstruments.Messaging.InMemory.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/RapidField.SolidInstruments.Messaging.InMemory.UnitTests.csproj @@ -25,16 +25,11 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in + - - - - - - \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/SimulatedDependencyModule.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/SimulatedDependencyModule.cs new file mode 100644 index 00000000..62b37b33 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/SimulatedDependencyModule.cs @@ -0,0 +1,134 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.InversionOfControl.DotNetNative; +using RapidField.SolidInstruments.Messaging.CommandMessages; +using RapidField.SolidInstruments.Messaging.EventMessages; +using RapidField.SolidInstruments.Messaging.TransportPrimitives; +using System; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests +{ + /// + /// Encapsulates container configuration for test dependencies. + /// + public class SimulatedDependencyModule : DotNetNativeDependencyModule + { + /// + /// Initializes a new instance of the class. + /// + /// + /// Configuration information for the application. + /// + /// + /// is . + /// + public SimulatedDependencyModule(IConfiguration applicationConfiguration) + : base(applicationConfiguration) + { + return; + } + + /// + /// Configures the module. + /// + /// + /// An object that configures containers. + /// + /// + /// Configuration information for the application. + /// + protected override void Configure(ServiceCollection configurator, IConfiguration applicationConfiguration) + { + // Register the configuration. + configurator.AddSingleton(applicationConfiguration); + + // Register messaging types. + var messageTransport = MessageTransport.Instance; + var messageTransportConnection = messageTransport.CreateConnection(); + configurator.AddSingleton(messageTransport); + configurator.AddSingleton((serviceProvider) => serviceProvider.GetService()); + configurator.AddSingleton(messageTransportConnection); + configurator.AddScoped(); + configurator.AddScoped, InMemoryMessageAdapter>((serviceProvider) => serviceProvider.GetService()); + configurator.AddScoped(); + configurator.AddScoped, InMemoryClientFactory>((serviceProvider) => serviceProvider.GetService()); + configurator.AddScoped(); + configurator.AddScoped((serviceProvider) => serviceProvider.GetService()); + configurator.AddSingleton(); + configurator.AddSingleton((serviceProvider) => serviceProvider.GetService()); + configurator.AddSingleton(); + configurator.AddSingleton((serviceProvider) => serviceProvider.GetService()); + + // Register queue transmitters. + configurator.AddTransient, CommandMessageTransmitter>(); + configurator.AddTransient, CommandMessageTransmitter>(); + configurator.AddTransient, CommandMessageTransmitter>(); + configurator.AddTransient, CommandMessageTransmitter>(); + configurator.AddTransient, CommandMessageTransmitter>(); + configurator.AddTransient, CommandMessageTransmitter>(); + configurator.AddTransient, CommandMessageTransmitter>(); + configurator.AddTransient, CommandMessageTransmitter>(); + configurator.AddTransient, CommandMessageTransmitter>(); + + // Register topic transmitters. + configurator.AddTransient, EventMessageTransmitter>(); + configurator.AddTransient, EventMessageTransmitter>(); + configurator.AddTransient, EventMessageTransmitter>(); + configurator.AddTransient, EventMessageTransmitter>(); + configurator.AddTransient, EventMessageTransmitter>(); + configurator.AddTransient, EventMessageTransmitter>(); + configurator.AddTransient, EventMessageTransmitter>(); + configurator.AddTransient, EventMessageTransmitter>(); + configurator.AddTransient, EventMessageTransmitter>(); + + // Register queue listeners. + configurator.AddTransient, CommandMessageListener>(); + configurator.AddTransient, CommandMessageListener>(); + configurator.AddTransient, CommandMessageListener>(); + configurator.AddTransient, CommandMessageListener>(); + configurator.AddTransient, CommandMessageListener>(); + configurator.AddTransient, CommandMessageListener>(); + configurator.AddTransient, CommandMessageListener>(); + configurator.AddTransient, CommandMessageListener>(); + configurator.AddTransient, CommandMessageListener>(); + + // Register topic listeners. + configurator.AddTransient, EventMessageListener>(); + configurator.AddTransient, EventMessageListener>(); + configurator.AddTransient, EventMessageListener>(); + configurator.AddTransient, EventMessageListener>(); + configurator.AddTransient, EventMessageListener>(); + configurator.AddTransient, EventMessageListener>(); + configurator.AddTransient, EventMessageListener>(); + configurator.AddTransient, EventMessageListener>(); + configurator.AddTransient, EventMessageListener>(); + + // Register command handlers. + configurator.AddTransient, CommandHandlers.ModelState.Customer.CreateDomainModelCommandHandler>(); + configurator.AddTransient, CommandHandlers.ModelState.Customer.DeleteDomainModelCommandHandler>(); + configurator.AddTransient, CommandHandlers.ModelState.Customer.UpdateDomainModelCommandHandler>(); + configurator.AddTransient, CommandHandlers.ModelState.CustomerOrder.CreateDomainModelCommandHandler>(); + configurator.AddTransient, CommandHandlers.ModelState.CustomerOrder.DeleteDomainModelCommandHandler>(); + configurator.AddTransient, CommandHandlers.ModelState.CustomerOrder.UpdateDomainModelCommandHandler>(); + configurator.AddTransient, CommandHandlers.ModelState.Product.CreateDomainModelCommandHandler>(); + configurator.AddTransient, CommandHandlers.ModelState.Product.DeleteDomainModelCommandHandler>(); + configurator.AddTransient, CommandHandlers.ModelState.Product.UpdateDomainModelCommandHandler>(); + + // Register event handlers. + configurator.AddTransient, EventHandlers.ModelState.Customer.DomainModelCreatedEventHandler>(); + configurator.AddTransient, EventHandlers.ModelState.Customer.DomainModelDeletedEventHandler>(); + configurator.AddTransient, EventHandlers.ModelState.Customer.DomainModelUpdatedEventHandler>(); + configurator.AddTransient, EventHandlers.ModelState.CustomerOrder.DomainModelCreatedEventHandler>(); + configurator.AddTransient, EventHandlers.ModelState.CustomerOrder.DomainModelDeletedEventHandler>(); + configurator.AddTransient, EventHandlers.ModelState.CustomerOrder.DomainModelUpdatedEventHandler>(); + configurator.AddTransient, EventHandlers.ModelState.Product.DomainModelCreatedEventHandler>(); + configurator.AddTransient, EventHandlers.ModelState.Product.DomainModelDeletedEventHandler>(); + configurator.AddTransient, EventHandlers.ModelState.Product.DomainModelUpdatedEventHandler>(); + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/SimulatedDependencyPackage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/SimulatedDependencyPackage.cs new file mode 100644 index 00000000..fb9ea0b7 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/SimulatedDependencyPackage.cs @@ -0,0 +1,41 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using RapidField.SolidInstruments.InversionOfControl; +using RapidField.SolidInstruments.InversionOfControl.DotNetNative; +using System.Collections.Generic; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests +{ + /// + /// Encapsulates container configuration for testing. + /// + public class SimulatedDependencyPackage : DotNetNativeDependencyPackage + { + /// + /// Initializes a new instance of the class. + /// + public SimulatedDependencyPackage() + : base() + { + return; + } + + /// + /// Creates a new collection of dependency modules for the package. + /// + /// + /// Configuration information for the application. + /// + /// + /// The package's dependency modules. + /// + protected override IEnumerable> CreateModules(IConfiguration applicationConfiguration) => new IDependencyModule[] + { + new SimulatedDependencyModule(applicationConfiguration) + }; + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/SimulatedMessagingServiceExecutor.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/SimulatedMessagingServiceExecutor.cs new file mode 100644 index 00000000..6e5691a5 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/SimulatedMessagingServiceExecutor.cs @@ -0,0 +1,173 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.InversionOfControl; +using RapidField.SolidInstruments.InversionOfControl.DotNetNative; +using RapidField.SolidInstruments.Messaging.Service; +using RapidField.SolidInstruments.Service; +using System; +using System.Diagnostics; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests +{ + /// + /// Prepares for and performs execution of the test messaging service. + /// + internal sealed class SimulatedMessagingServiceExecutor : MessagingServiceExecutor + { + /// + /// Initializes a new instance of the class. + /// + /// + /// An action that is performed when the service is starting. + /// + /// + /// An action that is performed when the service is stopping. + /// + /// + /// is -or- is + /// . + /// + public SimulatedMessagingServiceExecutor(Action onStartingAction, Action onStoppingAction) + : base("Simulated Messaging Service", false) + { + OnStartingAction = onStartingAction.RejectIf().IsNull(nameof(onStartingAction)); + OnStoppingAction = onStoppingAction.RejectIf().IsNull(nameof(onStoppingAction)); + } + + /// + /// Adds message subscriptions to the service. + /// + /// + /// An object that is used to add subscriptions. + /// + /// + /// Configuration information for the service application. + /// + protected override void AddSubscriptions(IMessageListeningProfile subscriptionProfile, IConfiguration applicationConfiguration) + { + try + { + // Add queue listeners. + subscriptionProfile.AddQueueListener(); + subscriptionProfile.AddQueueListener(); + subscriptionProfile.AddQueueListener(); + subscriptionProfile.AddQueueListener(); + subscriptionProfile.AddQueueListener(); + subscriptionProfile.AddQueueListener(); + subscriptionProfile.AddQueueListener(); + subscriptionProfile.AddQueueListener(); + subscriptionProfile.AddQueueListener(); + + // Add topic listeners. + subscriptionProfile.AddTopicListener(); + subscriptionProfile.AddTopicListener(); + subscriptionProfile.AddTopicListener(); + subscriptionProfile.AddTopicListener(); + subscriptionProfile.AddTopicListener(); + subscriptionProfile.AddTopicListener(); + subscriptionProfile.AddTopicListener(); + subscriptionProfile.AddTopicListener(); + subscriptionProfile.AddTopicListener(); + + // Add request listeners. + //subscriptionProfile.AddRequestListener(); + } + finally + { + base.AddSubscriptions(subscriptionProfile, applicationConfiguration); + } + } + + /// + /// Builds the application configuration for the service. + /// + /// + /// An object that is used to build the configuration. + /// + protected override void BuildConfiguration(IConfigurationBuilder configurationBuilder) => base.BuildConfiguration(configurationBuilder); + + /// + /// Configures the service to transmit heartbeat messages. + /// + /// + /// An object that defines how the service transmits heartbeat messages. + /// + /// + /// Configuration information for the service application. + /// + protected override void ConfigureHeartbeat(HeartbeatSchedule heartbeatSchedule, IConfiguration applicationConfiguration) => base.ConfigureHeartbeat(heartbeatSchedule, applicationConfiguration); + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Performs startup operations for the service. + /// + /// + /// A scope that is used to resolve service dependencies. + /// + /// + /// Configuration information for the service application. + /// + /// + /// An object that provides control over execution lifetime. + /// + protected override void OnExecutionStarting(IDependencyScope dependencyScope, IConfiguration applicationConfiguration, IServiceExecutionLifetime executionLifetime) + { + try + { + var mediator = dependencyScope.Resolve(); + OnStartingAction(mediator); + } + finally + { + base.OnExecutionStarting(dependencyScope, applicationConfiguration, executionLifetime); + } + } + + /// + /// Performs shutdown operations for the service. + /// + /// + /// A scope that is used to resolve service dependencies. + /// + /// + /// Configuration information for the service application. + /// + protected override void OnExecutionStopping(IDependencyScope dependencyScope, IConfiguration applicationConfiguration) + { + try + { + var mediator = dependencyScope.Resolve(); + OnStoppingAction(mediator); + } + finally + { + base.OnExecutionStopping(dependencyScope, applicationConfiguration); + } + } + + /// + /// Represents an action that is performed when the service is starting. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly Action OnStartingAction; + + /// + /// Represents an action that is performed when the service is stopping. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly Action OnStoppingAction; + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/SimulatedMessagingServiceExecutorTests.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/SimulatedMessagingServiceExecutorTests.cs new file mode 100644 index 00000000..db1c1aa0 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/SimulatedMessagingServiceExecutorTests.cs @@ -0,0 +1,63 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RapidField.SolidInstruments.Command; +using System.Linq; +using System.Threading; +using CreateCustomerCommand = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.Customer.CreateDomainModelCommand; +using CreateCustomerCommandMessage = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Command.ModelState.Customer.CreateDomainModelCommandMessage; +using CustomerModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer.DomainModel; +using UpdateCustomerCommand = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.Customer.UpdateDomainModelCommand; +using UpdateCustomerCommandMessage = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Command.ModelState.Customer.UpdateDomainModelCommandMessage; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests +{ + [TestClass] + public class SimulatedMessagingServiceExecutorTests + { + [TestMethod] + public void ShouldProduceDesiredResults() + { + using (var serviceExecutor = new SimulatedMessagingServiceExecutor(OnStarting, OnStopping)) + { + serviceExecutor.Execute(); + } + } + + private void OnStarting(ICommandMediator mediator) + { + // Assert. + SimulatedServiceState.Customers.Should().BeEmpty(); + SimulatedServiceState.CustomerOrders.Should().BeEmpty(); + SimulatedServiceState.Products.Should().BeEmpty(); + } + + private void OnStopping(ICommandMediator mediator) + { + Thread.Sleep(2584); + + // Arrange. + var acmeCoCustomer = CustomerModel.Named.AcmeCo; + var smithIndustriesCustomer = CustomerModel.Named.SmithIndustries; + + // Act. + mediator.Process(new CreateCustomerCommandMessage(new CreateCustomerCommand(acmeCoCustomer))); + mediator.Process(new CreateCustomerCommandMessage(new CreateCustomerCommand(smithIndustriesCustomer))); + + // Assert. + Thread.Sleep(6765); + SimulatedServiceState.Customers.Should().HaveCount(2); + + // Act. + acmeCoCustomer.Name = "New Acme Corporation"; + mediator.Process(new UpdateCustomerCommandMessage(new UpdateCustomerCommand(acmeCoCustomer))); + + // Assert. + Thread.Sleep(6765); + SimulatedServiceState.Customers.Where(entity => entity.Identifier == acmeCoCustomer.Identifier).Single().Name.Should().Be(acmeCoCustomer.Name); + } + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/SimulatedServiceState.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/SimulatedServiceState.cs new file mode 100644 index 00000000..d33dfb4e --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/SimulatedServiceState.cs @@ -0,0 +1,18 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System.Collections.Generic; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests +{ + /// + /// Represents the state of a service for testing purposes. + /// + internal static class SimulatedServiceState + { + internal static readonly List CustomerOrders = new List(); + internal static readonly List Customers = new List(); + internal static readonly List Products = new List(); + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.UnitTests/SimulatedObject.cs b/test/RapidField.SolidInstruments.Messaging.UnitTests/SimulatedObject.cs index 1a4e50e8..93d1b916 100644 --- a/test/RapidField.SolidInstruments.Messaging.UnitTests/SimulatedObject.cs +++ b/test/RapidField.SolidInstruments.Messaging.UnitTests/SimulatedObject.cs @@ -84,9 +84,9 @@ public override Boolean Equals(Object obj) { return false; } - else if (obj is SimulatedObject) + else if (obj is SimulatedObject simulatedObject) { - return Equals((SimulatedObject)obj); + return Equals(simulatedObject); } return false; diff --git a/test/RapidField.SolidInstruments.Serialization.UnitTests/SimulatedObject.cs b/test/RapidField.SolidInstruments.Serialization.UnitTests/SimulatedObject.cs index 449bdde0..d7f84062 100644 --- a/test/RapidField.SolidInstruments.Serialization.UnitTests/SimulatedObject.cs +++ b/test/RapidField.SolidInstruments.Serialization.UnitTests/SimulatedObject.cs @@ -84,9 +84,9 @@ public override Boolean Equals(Object obj) { return false; } - else if (obj is SimulatedObject) + else if (obj is SimulatedObject simulatedObject) { - return Equals((SimulatedObject)obj); + return Equals(simulatedObject); } return false; From 9b5704a2e0931f537b211990202148384319eb30 Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Tue, 21 Jul 2020 14:04:42 -0500 Subject: [PATCH 46/55] Extending support for messaging abstractions. --- .../AzureServiceBusClientFactory.cs | 74 +++++++++-- .../AzureServiceBusListeningFacade.cs | 95 +++++++++++++- .../InMemoryListeningFacade.cs | 26 ++++ .../MessageListeningFacade.cs | 118 ++++++++++++------ .../MessageListeningRetryDurationScale.cs | 2 +- .../MessageListeningRetryPolicy.cs | 4 +- .../MessageRequestingFacade.cs | 2 +- .../MessageTransportConnection.cs | 4 +- .../Customer/CreateDomainModelCommand.cs | 9 +- .../Customer/DeleteDomainModelCommand.cs | 9 +- .../Customer/UpdateDomainModelCommand.cs | 9 +- .../CustomerOrder/CreateDomainModelCommand.cs | 9 +- .../CustomerOrder/DeleteDomainModelCommand.cs | 9 +- .../CustomerOrder/UpdateDomainModelCommand.cs | 9 +- .../Product/CreateDomainModelCommand.cs | 9 +- .../Product/DeleteDomainModelCommand.cs | 9 +- .../Product/UpdateDomainModelCommand.cs | 9 +- .../Customer/DomainModelCreatedEvent.cs | 9 +- .../Customer/DomainModelDeletedEvent.cs | 9 +- .../Customer/DomainModelUpdatedEvent.cs | 9 +- .../CustomerOrder/DomainModelCreatedEvent.cs | 9 +- .../CustomerOrder/DomainModelDeletedEvent.cs | 9 +- .../CustomerOrder/DomainModelUpdatedEvent.cs | 9 +- .../Product/DomainModelCreatedEvent.cs | 9 +- .../Product/DomainModelDeletedEvent.cs | 9 +- .../Product/DomainModelUpdatedEvent.cs | 9 +- .../RequestResponse/Ping/RequestListener.cs | 59 +++++++++ .../CreateDomainModelCommandMessage.cs | 9 +- .../DeleteDomainModelCommandMessage.cs | 9 +- .../UpdateDomainModelCommandMessage.cs | 9 +- .../CreateDomainModelCommandMessage.cs | 9 +- .../DeleteDomainModelCommandMessage.cs | 9 +- .../UpdateDomainModelCommandMessage.cs | 9 +- .../CreateDomainModelCommandMessage.cs | 9 +- .../DeleteDomainModelCommandMessage.cs | 9 +- .../UpdateDomainModelCommandMessage.cs | 9 +- .../DomainModelCreatedEventMessage.cs | 9 +- .../DomainModelDeletedEventMessage.cs | 9 +- .../DomainModelUpdatedEventMessage.cs | 9 +- .../DomainModelCreatedEventMessage.cs | 9 +- .../DomainModelDeletedEventMessage.cs | 9 +- .../DomainModelUpdatedEventMessage.cs | 9 +- .../Product/DomainModelCreatedEventMessage.cs | 9 +- .../Product/DomainModelDeletedEventMessage.cs | 9 +- .../Product/DomainModelUpdatedEventMessage.cs | 9 +- .../RequestResponse/Ping/RequestMessage.cs | 47 +++++++ .../RequestResponse/Ping/ResponseMessage.cs | 52 ++++++++ .../Models/Customer/DomainModel.cs | 8 +- .../Models/CustomerOrder/DomainModel.cs | 8 +- .../Models/Product/DomainModel.cs | 8 +- .../SimulatedDependencyModule.cs | 6 + .../SimulatedMessagingServiceExecutor.cs | 2 +- .../SimulatedMessagingServiceExecutorTests.cs | 10 ++ .../MessageProcessingInformationTests.cs | 2 +- 54 files changed, 750 insertions(+), 101 deletions(-) create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/MessageListeners/RequestResponse/Ping/RequestListener.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/RequestResponse/Ping/RequestMessage.cs create mode 100644 test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/RequestResponse/Ping/ResponseMessage.cs diff --git a/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusClientFactory.cs b/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusClientFactory.cs index e53aec58..7d45ebc3 100644 --- a/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusClientFactory.cs +++ b/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusClientFactory.cs @@ -185,11 +185,21 @@ private ITopicClient CreateTopicClient(ServiceBusConnection connection /// A task representing the asynchronous operation. /// [DebuggerHidden] - private Task EnsureQueueExistanceAsync(IMessagingEntityPath queuePath) => ManagementClient.QueueExistsAsync(queuePath.ToString()).ContinueWith(async queueExistsTask => + private Task EnsureQueueExistanceAsync(IMessagingEntityPath queuePath) => Task.Factory.StartNew(() => { - if (queueExistsTask.Result == false) + lock (EnsureQueueExistenceSyncRoot) { - await ManagementClient.CreateQueueAsync(queuePath.ToString()).ConfigureAwait(false); + ManagementClient.QueueExistsAsync(queuePath.ToString()).ContinueWith(queueExistsTask => + { + var queueExists = queueExistsTask.Result; + + if (queueExists) + { + return; + } + + ManagementClient.CreateQueueAsync(queuePath.ToString()).Wait(); + }).Wait(); } }); @@ -206,15 +216,24 @@ private Task EnsureQueueExistanceAsync(IMessagingEntityPath queuePath) => Manage /// A task representing the asynchronous operation. /// [DebuggerHidden] - private Task EnsureSubscriptionExistanceAsync(IMessagingEntityPath topicPath, String subscriptionName) => EnsureTopicExistanceAsync(topicPath).ContinueWith(async ensureTopicExistenceTask => + private Task EnsureSubscriptionExistanceAsync(IMessagingEntityPath topicPath, String subscriptionName) => Task.Factory.StartNew(() => { - await ManagementClient.SubscriptionExistsAsync(topicPath.ToString(), subscriptionName).ContinueWith(async subscriptionExistsTask => + EnsureTopicExistanceAsync(topicPath).Wait(); + + lock (EnsureSubscriptionExistenceSyncRoot) { - if (subscriptionExistsTask.Result == false) + ManagementClient.SubscriptionExistsAsync(topicPath.ToString(), subscriptionName).ContinueWith(subscriptionExistsTask => { - await ManagementClient.CreateSubscriptionAsync(topicPath.ToString(), subscriptionName).ConfigureAwait(false); - } - }).ConfigureAwait(false); + var subscriptionExists = subscriptionExistsTask.Result; + + if (subscriptionExists) + { + return; + } + + ManagementClient.CreateSubscriptionAsync(topicPath.ToString(), subscriptionName).Wait(); + }).Wait(); + } }); /// @@ -227,11 +246,21 @@ await ManagementClient.SubscriptionExistsAsync(topicPath.ToString(), subscriptio /// A task representing the asynchronous operation. /// [DebuggerHidden] - private Task EnsureTopicExistanceAsync(IMessagingEntityPath topicPath) => ManagementClient.TopicExistsAsync(topicPath.ToString()).ContinueWith(async topicExistsTask => + private Task EnsureTopicExistanceAsync(IMessagingEntityPath topicPath) => Task.Factory.StartNew(() => { - if (topicExistsTask.Result == false) + lock (EnsureTopicExistenceSyncRoot) { - await ManagementClient.CreateTopicAsync(topicPath.ToString()).ConfigureAwait(false); + ManagementClient.TopicExistsAsync(topicPath.ToString()).ContinueWith(topicExistsTask => + { + var topicExists = topicExistsTask.Result; + + if (topicExists) + { + return; + } + + ManagementClient.CreateTopicAsync(topicPath.ToString()).Wait(); + }).Wait(); } }); @@ -241,6 +270,27 @@ private Task EnsureTopicExistanceAsync(IMessagingEntityPath topicPath) => Manage [DebuggerBrowsable(DebuggerBrowsableState.Never)] private const ReceiveMode ReceiveBehavior = ReceiveMode.PeekLock; + /// + /// Represents an object that is used to synchronize access to + /// . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static readonly Object EnsureQueueExistenceSyncRoot = new Object(); + + /// + /// Represents an object that is used to synchronize access to + /// . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static readonly Object EnsureSubscriptionExistenceSyncRoot = new Object(); + + /// + /// Represents an object that is used to synchronize access to + /// . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static readonly Object EnsureTopicExistenceSyncRoot = new Object(); + /// /// Represents the behavior used by clients when retrying an operation. /// diff --git a/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusListeningFacade.cs b/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusListeningFacade.cs index 55db51f5..5fe7097b 100644 --- a/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusListeningFacade.cs +++ b/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/AzureServiceBusListeningFacade.cs @@ -4,9 +4,11 @@ using Microsoft.Azure.ServiceBus; using Microsoft.Azure.ServiceBus.Core; +using RapidField.SolidInstruments.Core; using RapidField.SolidInstruments.Core.Concurrency; using RapidField.SolidInstruments.Messaging.EventMessages; using System; +using System.Collections.Generic; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; @@ -73,19 +75,100 @@ protected sealed override void RegisterMessageHandler(Action + { + try + { + cancellationToken.ThrowIfCancellationRequested(); + messageHandler(message); + } + catch (Exception exception) + { + receiveClient.AbandonAsync(lockToken).ContinueWith(abandonTask => { TransmitReceiverExceptionAsync(exception, ExtractCorrelationIdentifier(message)).Wait(); }).Wait(); + } + }).ContinueWith(handleMessageTask => + { + try + { + receiveClient.CompleteAsync(lockToken).Wait(); + } + catch (AggregateException exception) + { + TransmitReceiverExceptionAsync(exception, ExtractCorrelationIdentifier(message)).Wait(); + } + }, TaskContinuationOptions.OnlyOnRanToCompletion); } - catch + catch (MessageListeningException exception) { - return receiveClient.AbandonAsync(lockToken); + return TransmitReceiverExceptionAsync(exception, ExtractCorrelationIdentifier(message)); } }); receiveClient.RegisterMessageHandler(messageHandlerFunction, ReceiverOptions); } + /// + /// Asynchronously sends the specified message to a dead letter queue. + /// + /// + /// The type of the message. + /// + /// + /// The adapted message. + /// + /// + /// The message to route to a dead letter queue. + /// + /// + /// An ordered collection of as many as three non-null, non-empty alphanumeric textual labels to append to the path, or + /// to omit path labels. + /// + /// + /// The targeted entity type. + /// + /// + /// A task representing the asynchronous operation. + /// + protected sealed override Task RouteToDeadLetterQueueAsync(AzureServiceBusMessage adaptedMessage, TMessage message, IEnumerable pathLabels, MessagingEntityType entityType) + { + var lockToken = adaptedMessage?.SystemProperties.LockToken; + + if (lockToken is null) + { + return Task.CompletedTask; + } + + var receiverClient = entityType switch + { + MessagingEntityType.Queue => ClientFactory.GetQueueReceiver(pathLabels), + MessagingEntityType.Topic => ClientFactory.GetTopicReceiver(Identifier, pathLabels), + _ => throw new UnsupportedSpecificationException($"The specified messaging entity type, {entityType}, is not supported.") + }; + + var messageType = typeof(TMessage); + return receiverClient.DeadLetterAsync(lockToken, $"The listener(s) exhausted the primary failure remediation steps for this message. Message type: {messageType.FullName}."); + } + + /// + /// Extracts the correlation identifier from the specified message. + /// + /// + /// The message to evaluate. + /// + /// + /// The resulting correlation identifier. + /// + [DebuggerHidden] + private static Guid ExtractCorrelationIdentifier(AzureServiceBusMessage message) + { + if (message?.CorrelationId is null == false && Guid.TryParse(message.CorrelationId, out var correlationIdentifier)) + { + return correlationIdentifier; + } + + return Guid.NewGuid(); + } + /// /// Asynchronously handles an exception that was raised by a message receive client. /// @@ -99,7 +182,7 @@ protected sealed override void RegisterMessageHandler(Action. /// [DebuggerHidden] - private static Task HandleReceiverExceptionAsync(ExceptionReceivedEventArgs exceptionReceivedArguments) => Task.CompletedTask; // TODO Handle receiver exceptions. + private static Task HandleReceiverExceptionAsync(ExceptionReceivedEventArgs exceptionReceivedArguments) => Task.CompletedTask; // The message handling function (defined above) handles exceptions internally. /// /// Gets options that specify how receive clients handle messages. diff --git a/src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryListeningFacade.cs b/src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryListeningFacade.cs index 358b026f..f7a6a1c5 100644 --- a/src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryListeningFacade.cs +++ b/src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryListeningFacade.cs @@ -5,6 +5,8 @@ using RapidField.SolidInstruments.Core.Concurrency; using RapidField.SolidInstruments.Messaging.TransportPrimitives; using System; +using System.Collections.Generic; +using System.Threading.Tasks; namespace RapidField.SolidInstruments.Messaging.InMemory { @@ -78,5 +80,29 @@ protected sealed override void RegisterMessageHandler(Action m receiveClient.RegisterMessageHandler(messageHandlerAction); } + + /// + /// Asynchronously sends the specified message to a dead letter queue. + /// + /// + /// The type of the message. + /// + /// + /// The adapted message. + /// + /// + /// The message to route to a dead letter queue. + /// + /// + /// An ordered collection of as many as three non-null, non-empty alphanumeric textual labels to append to the path, or + /// to omit path labels. + /// + /// + /// The targeted entity type. + /// + /// + /// A task representing the asynchronous operation. + /// + protected sealed override Task RouteToDeadLetterQueueAsync(PrimitiveMessage adaptedMessage, TMessage message, IEnumerable pathLabels, MessagingEntityType entityType) => Task.CompletedTask; // In-memory listeners do not support dead lettering. } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/MessageListeningFacade.cs b/src/RapidField.SolidInstruments.Messaging/MessageListeningFacade.cs index e77d2fb2..0ec3d309 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessageListeningFacade.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessageListeningFacade.cs @@ -121,7 +121,7 @@ public sealed override void RegisterRequestMessageHandler(Action messageHandle /// /// An action that handles a message. /// + /// + /// The adapted message. + /// /// /// A message to handle. /// + /// + /// An ordered collection of as many as three non-null, non-empty alphanumeric textual labels to append to the path, or + /// to omit path labels. + /// + /// + /// The targeted entity type. + /// /// /// A task representing the asynchronous operation. /// [DebuggerHidden] - internal Task HandleMessageAsync(Action messageHandler, TMessage message) - where TMessage : IMessageBase + internal Task HandleMessageAsync(Action messageHandler, TAdaptedMessage adaptedMessage, TMessage message, IEnumerable pathLabels, MessagingEntityType entityType) + where TMessage : class, IMessageBase { var attemptStartTimeStamp = TimeStamp.Current; Exception raisedException; @@ -373,7 +383,7 @@ internal Task HandleMessageAsync(Action messageHandler, TMes raisedException = exception; } - return ExecuteFailurePolicyAsync(messageHandler, message, raisedException); + return ExecuteFailurePolicyAsync(messageHandler, adaptedMessage, message, pathLabels, entityType, raisedException); } /// @@ -447,7 +457,7 @@ internal void RegisterMessageHandler(Action messageHandler, message.ProcessingInformation.FailurePolicy = DefaultFailurePolicy; } - HandleMessageAsync(messageHandler, message).Wait(); + HandleMessageAsync(messageHandler, adaptedMessage, message, pathLabels, entityType).Wait(); } catch (Exception exception) { @@ -524,19 +534,63 @@ internal Boolean TryAddListenedMessageType(IConcurrencyControlToken co /// protected abstract void RegisterMessageHandler(Action adaptedMessageHandler, TReceiver receiveClient, IConcurrencyControlToken controlToken); + /// + /// Asynchronously sends the specified message to a dead letter queue. + /// + /// + /// The type of the message. + /// + /// + /// The adapted message. + /// + /// + /// The message to route to a dead letter queue. + /// + /// + /// An ordered collection of as many as three non-null, non-empty alphanumeric textual labels to append to the path, or + /// to omit path labels. + /// + /// + /// The targeted entity type. + /// + /// + /// A task representing the asynchronous operation. + /// + protected abstract Task RouteToDeadLetterQueueAsync(TAdaptedMessage adaptedMessage, TMessage message, IEnumerable pathLabels, MessagingEntityType entityType) + where TMessage : class, IMessageBase; + + /// + /// Asynchronously transmits a new instance for the specified exception. + /// + /// + /// An exception for which to transmit a new message. + /// + /// + /// A correlation identifier for the message that could not be processed. + /// + /// + /// A task representing the asynchronous operation. + /// + protected Task TransmitReceiverExceptionAsync(Exception raisedException, Guid correlationIdentifier) + { + var exceptionRaisedEvent = new ExceptionRaisedEvent(ApplicationIdentity, raisedException, ExceptionRaisedMessageEventVerbosity, correlationIdentifier); + var exceptionRaisedEventMessage = new ExceptionRaisedEventMessage(exceptionRaisedEvent); + return TransmitReceiverExceptionAsync(exceptionRaisedEventMessage, ExceptionRaisedMessageEntityType); + } + /// /// Asynchronously transmits the specified instance. /// /// /// The message to transmit. /// - /// + /// /// The messaging type to use when transmitting the message. /// /// /// A task representing the asynchronous operation. /// - protected abstract Task TransmitReceiverExceptionAsync(ExceptionRaisedEventMessage exceptionRaisedMessage, MessagingEntityType messagingEntityType); + protected abstract Task TransmitReceiverExceptionAsync(ExceptionRaisedEventMessage exceptionRaisedMessage, MessagingEntityType entityType); /// /// Asynchronously executes the failure policy prescribed by the specified message. @@ -547,9 +601,19 @@ internal Boolean TryAddListenedMessageType(IConcurrencyControlToken co /// /// An action that handles a message. /// + /// + /// The adapted message. + /// /// /// A message that could not be processed. /// + /// + /// An ordered collection of as many as three non-null, non-empty alphanumeric textual labels to append to the path, or + /// to omit path labels. + /// + /// + /// The targeted entity type. + /// /// /// An exception that was raised while attempting to process the message. /// @@ -557,8 +621,8 @@ internal Boolean TryAddListenedMessageType(IConcurrencyControlToken co /// A task representing the asynchronous operation. /// [DebuggerHidden] - private Task ExecuteFailurePolicyAsync(Action messageHandler, TMessage message, Exception raisedException) - where TMessage : IMessageBase + private Task ExecuteFailurePolicyAsync(Action messageHandler, TAdaptedMessage adaptedMessage, TMessage message, IEnumerable pathLabels, MessagingEntityType entityType, Exception raisedException) + where TMessage : class, IMessageBase { var processingInformation = message?.ProcessingInformation; @@ -582,21 +646,23 @@ private Task ExecuteFailurePolicyAsync(Action messageHandler var baseDelayDurationInSeconds = retryPolicy.BaseDelayDurationInSeconds.AbsoluteValue(); var calculatedDelayDurationInSeconds = retryPolicy.DurationScale switch { - MessageListeningRetryDurationScale.Fibonacci => (Int32)new FibonacciSequence(0, baseDelayDurationInSeconds).ToArray(attemptCount, 1).First(), + MessageListeningRetryDurationScale.Decelerating => (Int32)new FibonacciSequence(0, baseDelayDurationInSeconds).ToArray(attemptCount, 1).First(), MessageListeningRetryDurationScale.Linear => baseDelayDurationInSeconds * processingInformation.AttemptCount, _ => throw new UnsupportedSpecificationException($"The specified duration scale, {retryPolicy.DurationScale}, is not supported.") }; - failurePolicyTasks.Add(Task.Delay(TimeSpan.FromSeconds(calculatedDelayDurationInSeconds))); - failurePolicyTasks.Add(HandleMessageAsync(messageHandler, message)); + failurePolicyTasks.Add(Task.Delay(TimeSpan.FromSeconds(calculatedDelayDurationInSeconds)).ContinueWith(delayTask => + { + HandleMessageAsync(messageHandler, adaptedMessage, message, pathLabels, entityType).Wait(); + })); } else { - throw failurePolicy.SecondaryFailureBehavior switch + var secondaryFailureTask = failurePolicy.SecondaryFailureBehavior switch { - MessageListeningSecondaryFailureBehavior.Discard => new UnsupportedSpecificationException($"The specified secondary failure behavior, {failurePolicy.SecondaryFailureBehavior}, is not supported."), // TODO Implement discard failure behavior. - MessageListeningSecondaryFailureBehavior.RouteToDeadLetterQueue => new UnsupportedSpecificationException($"The specified secondary failure behavior, {failurePolicy.SecondaryFailureBehavior}, is not supported."), // TODO Implement DLQ failure behavior. - _ => new UnsupportedSpecificationException($"The specified secondary failure behavior, {failurePolicy.SecondaryFailureBehavior}, is not supported.") + MessageListeningSecondaryFailureBehavior.Discard => Task.CompletedTask, + MessageListeningSecondaryFailureBehavior.RouteToDeadLetterQueue => RouteToDeadLetterQueueAsync(adaptedMessage, message, pathLabels, entityType), + _ => throw new UnsupportedSpecificationException($"The specified secondary failure behavior, {failurePolicy.SecondaryFailureBehavior}, is not supported.") }; } @@ -608,26 +674,6 @@ private Task ExecuteFailurePolicyAsync(Action messageHandler return Task.CompletedTask; } - /// - /// Asynchronously transmits a new instance for the specified exception. - /// - /// - /// An exception for which to transmit a new message. - /// - /// - /// A correlation identifier for the message that could not be processed. - /// - /// - /// A task representing the asynchronous operation. - /// - [DebuggerHidden] - private Task TransmitReceiverExceptionAsync(Exception raisedException, Guid correlationIdentifier) - { - var exceptionRaisedEvent = new ExceptionRaisedEvent(ApplicationIdentity, raisedException, ExceptionRaisedMessageEventVerbosity, correlationIdentifier); - var exceptionRaisedEventMessage = new ExceptionRaisedEventMessage(exceptionRaisedEvent); - return TransmitReceiverExceptionAsync(exceptionRaisedEventMessage, ExceptionRaisedMessageEntityType); - } - /// /// Gets the collection of message types for which the current /// has one or more registered handlers. diff --git a/src/RapidField.SolidInstruments.Messaging/MessageListeningRetryDurationScale.cs b/src/RapidField.SolidInstruments.Messaging/MessageListeningRetryDurationScale.cs index ee50ea11..933e49ce 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessageListeningRetryDurationScale.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessageListeningRetryDurationScale.cs @@ -31,6 +31,6 @@ public enum MessageListeningRetryDurationScale : Int32 /// sum of the previous two delay durations. /// [EnumMember] - Fibonacci = 2 + Decelerating = 2 } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/MessageListeningRetryPolicy.cs b/src/RapidField.SolidInstruments.Messaging/MessageListeningRetryPolicy.cs index 9e6f260d..fe8c843c 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessageListeningRetryPolicy.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessageListeningRetryPolicy.cs @@ -73,7 +73,7 @@ public MessageListeningRetryPolicy(Int32 retryCount, Int32 baseDelayDurationInSe /// /// /// The retry duration scaling behavior employed by listener in response to message processing failure. The default value is - /// . + /// . /// /// /// is less than zero -or- is equal to @@ -147,7 +147,7 @@ public Int32 RetryCount /// Represents the default retry duration scaling behavior employed by listener in response to message processing failure. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const MessageListeningRetryDurationScale DefaultDurationScale = MessageListeningRetryDurationScale.Fibonacci; + private const MessageListeningRetryDurationScale DefaultDurationScale = MessageListeningRetryDurationScale.Decelerating; /// /// Represents the default number of times that listener should try to process a failed message before employing secondary diff --git a/src/RapidField.SolidInstruments.Messaging/MessageRequestingFacade.cs b/src/RapidField.SolidInstruments.Messaging/MessageRequestingFacade.cs index b4d1f3bc..802ecaaf 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessageRequestingFacade.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessageRequestingFacade.cs @@ -195,7 +195,7 @@ public Task RequestAsync(TR } } - return TransmitRequestMessageAsync(requestMessage).ContinueWith((transmitTask) => + return TransmitRequestMessageAsync(requestMessage).ContinueWith(transmitTask => { RejectIfDisposed(); diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransportConnection.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransportConnection.cs index 6a413e3f..fb5dce7a 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransportConnection.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransportConnection.cs @@ -254,7 +254,7 @@ private void Poll(IEnumerable handlers) /// The operation timed out. /// [DebuggerHidden] - private Task PollQueueAsync(IMessagingEntityPath queuePath, IEnumerable> handleMessageActions) => Transport.ReceiveFromQueueAsync(queuePath, MessageReceiptBatchSize).ContinueWith((receiveFromQueueTask) => + private Task PollQueueAsync(IMessagingEntityPath queuePath, IEnumerable> handleMessageActions) => Transport.ReceiveFromQueueAsync(queuePath, MessageReceiptBatchSize).ContinueWith(receiveFromQueueTask => { var messageBatch = receiveFromQueueTask.Result; @@ -351,7 +351,7 @@ private Task PollQueuesAsync(IEnumerable queueHandlers) /// The operation timed out. /// [DebuggerHidden] - private Task PollSubscriptionAsync(IMessagingEntityPath topicPath, String subscriptionName, IEnumerable> handleMessageActions) => Transport.ReceiveFromTopicAsync(topicPath, subscriptionName, MessageReceiptBatchSize).ContinueWith((receiveFromTopicTask) => + private Task PollSubscriptionAsync(IMessagingEntityPath topicPath, String subscriptionName, IEnumerable> handleMessageActions) => Transport.ReceiveFromTopicAsync(topicPath, subscriptionName, MessageReceiptBatchSize).ContinueWith(receiveFromTopicTask => { var messageBatch = receiveFromTopicTask.Result; diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Customer/CreateDomainModelCommand.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Customer/CreateDomainModelCommand.cs index b3182fd0..b6b01ee0 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Customer/CreateDomainModelCommand.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Customer/CreateDomainModelCommand.cs @@ -5,6 +5,7 @@ using RapidField.SolidInstruments.Command; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer.DomainModel; @@ -13,7 +14,7 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.Mode /// /// Represents a command to create a . /// - [DataContract(Name = "CreateCustomerEvent")] + [DataContract(Name = DataContractName)] internal sealed class CreateDomainModelCommand : CreateDomainModelCommand { /// @@ -117,5 +118,11 @@ public CreateDomainModelCommand(DomainModel model, IEnumerable labels, G { return; } + + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "CreateCustomerEvent"; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Customer/DeleteDomainModelCommand.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Customer/DeleteDomainModelCommand.cs index 1fe510ff..e1d26a41 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Customer/DeleteDomainModelCommand.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Customer/DeleteDomainModelCommand.cs @@ -5,6 +5,7 @@ using RapidField.SolidInstruments.Command; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer.DomainModel; @@ -13,7 +14,7 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.Mode /// /// Represents a command to delete a . /// - [DataContract(Name = "DeleteCustomerEvent")] + [DataContract(Name = DataContractName)] internal sealed class DeleteDomainModelCommand : DeleteDomainModelCommand { /// @@ -117,5 +118,11 @@ public DeleteDomainModelCommand(DomainModel model, IEnumerable labels, G { return; } + + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "DeleteCustomerEvent"; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Customer/UpdateDomainModelCommand.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Customer/UpdateDomainModelCommand.cs index 18500b5d..ffb77217 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Customer/UpdateDomainModelCommand.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Customer/UpdateDomainModelCommand.cs @@ -5,6 +5,7 @@ using RapidField.SolidInstruments.Command; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer.DomainModel; @@ -13,7 +14,7 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.Mode /// /// Represents a command to update a . /// - [DataContract(Name = "UpdateCustomerEvent")] + [DataContract(Name = DataContractName)] internal sealed class UpdateDomainModelCommand : UpdateDomainModelCommand { /// @@ -117,5 +118,11 @@ public UpdateDomainModelCommand(DomainModel model, IEnumerable labels, G { return; } + + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "UpdateCustomerEvent"; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/CustomerOrder/CreateDomainModelCommand.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/CustomerOrder/CreateDomainModelCommand.cs index 21685855..4a1c27ff 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/CustomerOrder/CreateDomainModelCommand.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/CustomerOrder/CreateDomainModelCommand.cs @@ -5,6 +5,7 @@ using RapidField.SolidInstruments.Command; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.CustomerOrder.DomainModel; @@ -13,7 +14,7 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.Mode /// /// Represents a command to create a . /// - [DataContract(Name = "CreateCustomerOrderEvent")] + [DataContract(Name = DataContractName)] internal sealed class CreateDomainModelCommand : CreateDomainModelCommand { /// @@ -117,5 +118,11 @@ public CreateDomainModelCommand(DomainModel model, IEnumerable labels, G { return; } + + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "CreateCustomerOrderEvent"; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/CustomerOrder/DeleteDomainModelCommand.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/CustomerOrder/DeleteDomainModelCommand.cs index d9fcf560..8087693c 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/CustomerOrder/DeleteDomainModelCommand.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/CustomerOrder/DeleteDomainModelCommand.cs @@ -5,6 +5,7 @@ using RapidField.SolidInstruments.Command; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.CustomerOrder.DomainModel; @@ -13,7 +14,7 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.Mode /// /// Represents a command to delete a . /// - [DataContract(Name = "DeleteCustomerOrderEvent")] + [DataContract(Name = DataContractName)] internal sealed class DeleteDomainModelCommand : DeleteDomainModelCommand { /// @@ -117,5 +118,11 @@ public DeleteDomainModelCommand(DomainModel model, IEnumerable labels, G { return; } + + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "DeleteCustomerOrderEvent"; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/CustomerOrder/UpdateDomainModelCommand.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/CustomerOrder/UpdateDomainModelCommand.cs index 8810d7b3..f595d502 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/CustomerOrder/UpdateDomainModelCommand.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/CustomerOrder/UpdateDomainModelCommand.cs @@ -5,6 +5,7 @@ using RapidField.SolidInstruments.Command; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.CustomerOrder.DomainModel; @@ -13,7 +14,7 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.Mode /// /// Represents a command to update a . /// - [DataContract(Name = "UpdateCustomerOrderEvent")] + [DataContract(Name = DataContractName)] internal sealed class UpdateDomainModelCommand : UpdateDomainModelCommand { /// @@ -117,5 +118,11 @@ public UpdateDomainModelCommand(DomainModel model, IEnumerable labels, G { return; } + + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "UpdateCustomerOrderEvent"; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Product/CreateDomainModelCommand.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Product/CreateDomainModelCommand.cs index aa10ce55..d584e05f 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Product/CreateDomainModelCommand.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Product/CreateDomainModelCommand.cs @@ -5,6 +5,7 @@ using RapidField.SolidInstruments.Command; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Product.DomainModel; @@ -13,7 +14,7 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.Mode /// /// Represents a command to create a . /// - [DataContract(Name = "CreateProductEvent")] + [DataContract(Name = DataContractName)] internal sealed class CreateDomainModelCommand : CreateDomainModelCommand { /// @@ -117,5 +118,11 @@ public CreateDomainModelCommand(DomainModel model, IEnumerable labels, G { return; } + + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "CreateProductEvent"; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Product/DeleteDomainModelCommand.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Product/DeleteDomainModelCommand.cs index 07b3dcf1..268547bb 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Product/DeleteDomainModelCommand.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Product/DeleteDomainModelCommand.cs @@ -5,6 +5,7 @@ using RapidField.SolidInstruments.Command; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Product.DomainModel; @@ -13,7 +14,7 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.Mode /// /// Represents a command to delete a . /// - [DataContract(Name = "DeleteProductEvent")] + [DataContract(Name = DataContractName)] internal sealed class DeleteDomainModelCommand : DeleteDomainModelCommand { /// @@ -117,5 +118,11 @@ public DeleteDomainModelCommand(DomainModel model, IEnumerable labels, G { return; } + + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "DeleteProductEvent"; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Product/UpdateDomainModelCommand.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Product/UpdateDomainModelCommand.cs index 2664d945..5f599f29 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Product/UpdateDomainModelCommand.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Commands/ModelState/Product/UpdateDomainModelCommand.cs @@ -5,6 +5,7 @@ using RapidField.SolidInstruments.Command; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Product.DomainModel; @@ -13,7 +14,7 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.Mode /// /// Represents a command to update a . /// - [DataContract(Name = "UpdateProductEvent")] + [DataContract(Name = DataContractName)] internal sealed class UpdateDomainModelCommand : UpdateDomainModelCommand { /// @@ -117,5 +118,11 @@ public UpdateDomainModelCommand(DomainModel model, IEnumerable labels, G { return; } + + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "UpdateProductEvent"; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Customer/DomainModelCreatedEvent.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Customer/DomainModelCreatedEvent.cs index 03b3186b..08978209 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Customer/DomainModelCreatedEvent.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Customer/DomainModelCreatedEvent.cs @@ -5,6 +5,7 @@ using RapidField.SolidInstruments.EventAuthoring; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer.DomainModel; @@ -13,7 +14,7 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelS /// /// Represents information about the creation of a . /// - [DataContract(Name = "CustomerCreatedEvent")] + [DataContract(Name = DataContractName)] internal sealed class DomainModelCreatedEvent : DomainModelCreatedEvent { /// @@ -169,5 +170,11 @@ public DomainModelCreatedEvent(DomainModel model, IEnumerable labels, Ev { return; } + + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "CustomerCreatedEvent"; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Customer/DomainModelDeletedEvent.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Customer/DomainModelDeletedEvent.cs index d17a2e1c..598ebcd0 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Customer/DomainModelDeletedEvent.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Customer/DomainModelDeletedEvent.cs @@ -5,6 +5,7 @@ using RapidField.SolidInstruments.EventAuthoring; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer.DomainModel; @@ -13,7 +14,7 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelS /// /// Represents information about the deletion of a . /// - [DataContract(Name = "CustomerDeletedEvent")] + [DataContract(Name = DataContractName)] internal sealed class DomainModelDeletedEvent : DomainModelDeletedEvent { /// @@ -169,5 +170,11 @@ public DomainModelDeletedEvent(DomainModel model, IEnumerable labels, Ev { return; } + + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "CustomerDeletedEvent"; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Customer/DomainModelUpdatedEvent.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Customer/DomainModelUpdatedEvent.cs index 47fc6e3a..226401f8 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Customer/DomainModelUpdatedEvent.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Customer/DomainModelUpdatedEvent.cs @@ -5,6 +5,7 @@ using RapidField.SolidInstruments.EventAuthoring; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer.DomainModel; @@ -13,7 +14,7 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelS /// /// Represents information about an update to a . /// - [DataContract(Name = "CustomerUpdatedEvent")] + [DataContract(Name = DataContractName)] internal sealed class DomainModelUpdatedEvent : DomainModelUpdatedEvent { /// @@ -169,5 +170,11 @@ public DomainModelUpdatedEvent(DomainModel model, IEnumerable labels, Ev { return; } + + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "CustomerUpdatedEvent"; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/CustomerOrder/DomainModelCreatedEvent.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/CustomerOrder/DomainModelCreatedEvent.cs index bca4767b..2e10a64e 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/CustomerOrder/DomainModelCreatedEvent.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/CustomerOrder/DomainModelCreatedEvent.cs @@ -5,6 +5,7 @@ using RapidField.SolidInstruments.EventAuthoring; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.CustomerOrder.DomainModel; @@ -13,7 +14,7 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelS /// /// Represents information about the creation of a . /// - [DataContract(Name = "CustomerOrderCreatedEvent")] + [DataContract(Name = DataContractName)] internal sealed class DomainModelCreatedEvent : DomainModelCreatedEvent { /// @@ -169,5 +170,11 @@ public DomainModelCreatedEvent(DomainModel model, IEnumerable labels, Ev { return; } + + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "CustomerOrderCreatedEvent"; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/CustomerOrder/DomainModelDeletedEvent.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/CustomerOrder/DomainModelDeletedEvent.cs index 61b8e6d6..0f7d365b 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/CustomerOrder/DomainModelDeletedEvent.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/CustomerOrder/DomainModelDeletedEvent.cs @@ -5,6 +5,7 @@ using RapidField.SolidInstruments.EventAuthoring; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.CustomerOrder.DomainModel; @@ -13,7 +14,7 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelS /// /// Represents information about the deletion of a . /// - [DataContract(Name = "CustomerOrderDeletedEvent")] + [DataContract(Name = DataContractName)] internal sealed class DomainModelDeletedEvent : DomainModelDeletedEvent { /// @@ -169,5 +170,11 @@ public DomainModelDeletedEvent(DomainModel model, IEnumerable labels, Ev { return; } + + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "CustomerOrderDeletedEvent"; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/CustomerOrder/DomainModelUpdatedEvent.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/CustomerOrder/DomainModelUpdatedEvent.cs index 389ba971..6282552e 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/CustomerOrder/DomainModelUpdatedEvent.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/CustomerOrder/DomainModelUpdatedEvent.cs @@ -5,6 +5,7 @@ using RapidField.SolidInstruments.EventAuthoring; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.CustomerOrder.DomainModel; @@ -13,7 +14,7 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelS /// /// Represents information about an update to a . /// - [DataContract(Name = "CustomerOrderUpdatedEvent")] + [DataContract(Name = DataContractName)] internal sealed class DomainModelUpdatedEvent : DomainModelUpdatedEvent { /// @@ -169,5 +170,11 @@ public DomainModelUpdatedEvent(DomainModel model, IEnumerable labels, Ev { return; } + + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "CustomerOrderUpdatedEvent"; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Product/DomainModelCreatedEvent.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Product/DomainModelCreatedEvent.cs index b4a85a1d..ad9284c5 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Product/DomainModelCreatedEvent.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Product/DomainModelCreatedEvent.cs @@ -5,6 +5,7 @@ using RapidField.SolidInstruments.EventAuthoring; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Product.DomainModel; @@ -13,7 +14,7 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelS /// /// Represents information about the creation of a . /// - [DataContract(Name = "ProductCreatedEvent")] + [DataContract(Name = DataContractName)] internal sealed class DomainModelCreatedEvent : DomainModelCreatedEvent { /// @@ -169,5 +170,11 @@ public DomainModelCreatedEvent(DomainModel model, IEnumerable labels, Ev { return; } + + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "ProductCreatedEvent"; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Product/DomainModelDeletedEvent.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Product/DomainModelDeletedEvent.cs index bf1450f0..a1511ac8 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Product/DomainModelDeletedEvent.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Product/DomainModelDeletedEvent.cs @@ -5,6 +5,7 @@ using RapidField.SolidInstruments.EventAuthoring; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Product.DomainModel; @@ -13,7 +14,7 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelS /// /// Represents information about the deletion of a . /// - [DataContract(Name = "ProductDeletedEvent")] + [DataContract(Name = DataContractName)] internal sealed class DomainModelDeletedEvent : DomainModelDeletedEvent { /// @@ -169,5 +170,11 @@ public DomainModelDeletedEvent(DomainModel model, IEnumerable labels, Ev { return; } + + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "ProductDeletedEvent"; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Product/DomainModelUpdatedEvent.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Product/DomainModelUpdatedEvent.cs index c52daa51..781c177b 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Product/DomainModelUpdatedEvent.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Events/ModelState/Product/DomainModelUpdatedEvent.cs @@ -5,6 +5,7 @@ using RapidField.SolidInstruments.EventAuthoring; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Product.DomainModel; @@ -13,7 +14,7 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelS /// /// Represents information about an update to a . /// - [DataContract(Name = "ProductUpdatedEvent")] + [DataContract(Name = DataContractName)] internal sealed class DomainModelUpdatedEvent : DomainModelUpdatedEvent { /// @@ -169,5 +170,11 @@ public DomainModelUpdatedEvent(DomainModel model, IEnumerable labels, Ev { return; } + + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "ProductUpdatedEvent"; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/MessageListeners/RequestResponse/Ping/RequestListener.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/MessageListeners/RequestResponse/Ping/RequestListener.cs new file mode 100644 index 00000000..c5dfc275 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/MessageListeners/RequestResponse/Ping/RequestListener.cs @@ -0,0 +1,59 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Concurrency; +using System; +using AssociatedRequestMessage = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.RequestResponse.Ping.RequestMessage; +using AssociatedResponseMessage = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.RequestResponse.Ping.ResponseMessage; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.MessageListeners.RequestResponse.Ping +{ + /// + /// Listens for and processes instances. + /// + internal sealed class RequestListener : RequestListener + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A processing intermediary that is used to process sub-commands. + /// + /// + /// is . + /// + public RequestListener(ICommandMediator mediator) + : base(mediator) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Processes the specified command. + /// + /// + /// The command to process. + /// + /// + /// A processing intermediary that is used to process sub-commands. Do not process using + /// , as doing so will generally result in infinite-looping. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + /// + /// The result that is emitted when processing the command. + /// + protected override AssociatedResponseMessage Process(AssociatedRequestMessage command, ICommandMediator mediator, IConcurrencyControlToken controlToken) => new AssociatedResponseMessage(command.Identifier, command.CorrelationIdentifier); + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Customer/CreateDomainModelCommandMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Customer/CreateDomainModelCommandMessage.cs index 65844b86..76059b61 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Customer/CreateDomainModelCommandMessage.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Customer/CreateDomainModelCommandMessage.cs @@ -3,6 +3,7 @@ // ================================================================================================================================= using System; +using System.Diagnostics; using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer.DomainModel; using DomainModelCommand = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.Customer.CreateDomainModelCommand; @@ -14,7 +15,7 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Comm /// /// Represents a message that contains a command to create a . /// - [DataContract(Name = "CreateCustomerCommandMessage")] + [DataContract(Name = DataContractName)] internal sealed class CreateDomainModelCommandMessage : DomainModelCommandMessage { /// @@ -40,5 +41,11 @@ public CreateDomainModelCommandMessage(DomainModelCommand commandObject) { return; } + + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "CreateCustomerCommandMessage"; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Customer/DeleteDomainModelCommandMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Customer/DeleteDomainModelCommandMessage.cs index 2d154e1b..ae5a040d 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Customer/DeleteDomainModelCommandMessage.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Customer/DeleteDomainModelCommandMessage.cs @@ -3,6 +3,7 @@ // ================================================================================================================================= using System; +using System.Diagnostics; using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer.DomainModel; using DomainModelCommand = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.Customer.DeleteDomainModelCommand; @@ -14,7 +15,7 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Comm /// /// Represents a message that contains a command to delete a . /// - [DataContract(Name = "DeleteCustomerCommandMessage")] + [DataContract(Name = DataContractName)] internal sealed class DeleteDomainModelCommandMessage : DomainModelCommandMessage { /// @@ -40,5 +41,11 @@ public DeleteDomainModelCommandMessage(DomainModelCommand commandObject) { return; } + + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "DeleteCustomerCommandMessage"; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Customer/UpdateDomainModelCommandMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Customer/UpdateDomainModelCommandMessage.cs index af24ecc4..593aa4b7 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Customer/UpdateDomainModelCommandMessage.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Customer/UpdateDomainModelCommandMessage.cs @@ -3,6 +3,7 @@ // ================================================================================================================================= using System; +using System.Diagnostics; using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer.DomainModel; using DomainModelCommand = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.Customer.UpdateDomainModelCommand; @@ -14,7 +15,7 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Comm /// /// Represents a message that contains a command to update a . /// - [DataContract(Name = "UpdateCustomerCommandMessage")] + [DataContract(Name = DataContractName)] internal sealed class UpdateDomainModelCommandMessage : DomainModelCommandMessage { /// @@ -40,5 +41,11 @@ public UpdateDomainModelCommandMessage(DomainModelCommand commandObject) { return; } + + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "UpdateCustomerCommandMessage"; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/CustomerOrder/CreateDomainModelCommandMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/CustomerOrder/CreateDomainModelCommandMessage.cs index ae66b0df..4e168e43 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/CustomerOrder/CreateDomainModelCommandMessage.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/CustomerOrder/CreateDomainModelCommandMessage.cs @@ -3,6 +3,7 @@ // ================================================================================================================================= using System; +using System.Diagnostics; using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.CustomerOrder.DomainModel; using DomainModelCommand = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.CustomerOrder.CreateDomainModelCommand; @@ -14,7 +15,7 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Comm /// /// Represents a message that contains a command to create a . /// - [DataContract(Name = "CreateCustomerOrderCommandMessage")] + [DataContract(Name = DataContractName)] internal sealed class CreateDomainModelCommandMessage : DomainModelCommandMessage { /// @@ -40,5 +41,11 @@ public CreateDomainModelCommandMessage(DomainModelCommand commandObject) { return; } + + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "CreateCustomerOrderCommandMessage"; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/CustomerOrder/DeleteDomainModelCommandMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/CustomerOrder/DeleteDomainModelCommandMessage.cs index f148b15a..8401cdcc 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/CustomerOrder/DeleteDomainModelCommandMessage.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/CustomerOrder/DeleteDomainModelCommandMessage.cs @@ -3,6 +3,7 @@ // ================================================================================================================================= using System; +using System.Diagnostics; using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.CustomerOrder.DomainModel; using DomainModelCommand = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.CustomerOrder.DeleteDomainModelCommand; @@ -14,7 +15,7 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Comm /// /// Represents a message that contains a command to delete a . /// - [DataContract(Name = "DeleteCustomerOrderCommandMessage")] + [DataContract(Name = DataContractName)] internal sealed class DeleteDomainModelCommandMessage : DomainModelCommandMessage { /// @@ -40,5 +41,11 @@ public DeleteDomainModelCommandMessage(DomainModelCommand commandObject) { return; } + + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "DeleteCustomerOrderCommandMessage"; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/CustomerOrder/UpdateDomainModelCommandMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/CustomerOrder/UpdateDomainModelCommandMessage.cs index 06ed823f..8e9ec953 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/CustomerOrder/UpdateDomainModelCommandMessage.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/CustomerOrder/UpdateDomainModelCommandMessage.cs @@ -3,6 +3,7 @@ // ================================================================================================================================= using System; +using System.Diagnostics; using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.CustomerOrder.DomainModel; using DomainModelCommand = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.CustomerOrder.UpdateDomainModelCommand; @@ -14,7 +15,7 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Comm /// /// Represents a message that contains a command to update a . /// - [DataContract(Name = "UpdateCustomerOrderCommandMessage")] + [DataContract(Name = DataContractName)] internal sealed class UpdateDomainModelCommandMessage : DomainModelCommandMessage { /// @@ -40,5 +41,11 @@ public UpdateDomainModelCommandMessage(DomainModelCommand commandObject) { return; } + + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "UpdateCustomerOrderCommandMessage"; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Product/CreateDomainModelCommandMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Product/CreateDomainModelCommandMessage.cs index f8852532..0f975904 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Product/CreateDomainModelCommandMessage.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Product/CreateDomainModelCommandMessage.cs @@ -3,6 +3,7 @@ // ================================================================================================================================= using System; +using System.Diagnostics; using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Product.DomainModel; using DomainModelCommand = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.Product.CreateDomainModelCommand; @@ -14,7 +15,7 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Comm /// /// Represents a message that contains a command to create a . /// - [DataContract(Name = "CreateProductCommandMessage")] + [DataContract(Name = DataContractName)] internal sealed class CreateDomainModelCommandMessage : DomainModelCommandMessage { /// @@ -40,5 +41,11 @@ public CreateDomainModelCommandMessage(DomainModelCommand commandObject) { return; } + + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "CreateProductCommandMessage"; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Product/DeleteDomainModelCommandMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Product/DeleteDomainModelCommandMessage.cs index ba4cab48..faf1c9da 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Product/DeleteDomainModelCommandMessage.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Product/DeleteDomainModelCommandMessage.cs @@ -3,6 +3,7 @@ // ================================================================================================================================= using System; +using System.Diagnostics; using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Product.DomainModel; using DomainModelCommand = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.Product.DeleteDomainModelCommand; @@ -14,7 +15,7 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Comm /// /// Represents a message that contains a command to delete a . /// - [DataContract(Name = "DeleteProductCommandMessage")] + [DataContract(Name = DataContractName)] internal sealed class DeleteDomainModelCommandMessage : DomainModelCommandMessage { /// @@ -40,5 +41,11 @@ public DeleteDomainModelCommandMessage(DomainModelCommand commandObject) { return; } + + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "DeleteProductCommandMessage"; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Product/UpdateDomainModelCommandMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Product/UpdateDomainModelCommandMessage.cs index c2eb71cc..13986f40 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Product/UpdateDomainModelCommandMessage.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Command/ModelState/Product/UpdateDomainModelCommandMessage.cs @@ -3,6 +3,7 @@ // ================================================================================================================================= using System; +using System.Diagnostics; using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Product.DomainModel; using DomainModelCommand = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.Product.UpdateDomainModelCommand; @@ -14,7 +15,7 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Comm /// /// Represents a message that contains a command to update a . /// - [DataContract(Name = "UpdateProductCommandMessage")] + [DataContract(Name = DataContractName)] internal sealed class UpdateDomainModelCommandMessage : DomainModelCommandMessage { /// @@ -40,5 +41,11 @@ public UpdateDomainModelCommandMessage(DomainModelCommand commandObject) { return; } + + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "UpdateProductCommandMessage"; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Customer/DomainModelCreatedEventMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Customer/DomainModelCreatedEventMessage.cs index 5a899e98..59aca6f9 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Customer/DomainModelCreatedEventMessage.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Customer/DomainModelCreatedEventMessage.cs @@ -3,6 +3,7 @@ // ================================================================================================================================= using System; +using System.Diagnostics; using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer.DomainModel; using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.Customer.DomainModelCreatedEvent; @@ -14,7 +15,7 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Even /// /// Represents a message that provides notification about the creation of a . /// - [DataContract(Name = "CustomerCreatedEventMessage")] + [DataContract(Name = DataContractName)] internal sealed class DomainModelCreatedEventMessage : DomainModelEventMessage { /// @@ -40,5 +41,11 @@ public DomainModelCreatedEventMessage(DomainModelEvent eventObject) { return; } + + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "CustomerCreatedEventMessage"; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Customer/DomainModelDeletedEventMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Customer/DomainModelDeletedEventMessage.cs index 657d98f8..83ecd552 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Customer/DomainModelDeletedEventMessage.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Customer/DomainModelDeletedEventMessage.cs @@ -3,6 +3,7 @@ // ================================================================================================================================= using System; +using System.Diagnostics; using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer.DomainModel; using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.Customer.DomainModelDeletedEvent; @@ -14,7 +15,7 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Even /// /// Represents a message that provides notification about the deletion of a . /// - [DataContract(Name = "CustomerDeletedEventMessage")] + [DataContract(Name = DataContractName)] internal sealed class DomainModelDeletedEventMessage : DomainModelEventMessage { /// @@ -40,5 +41,11 @@ public DomainModelDeletedEventMessage(DomainModelEvent eventObject) { return; } + + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "CustomerDeletedEventMessage"; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Customer/DomainModelUpdatedEventMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Customer/DomainModelUpdatedEventMessage.cs index 7ab8c5a9..345701d3 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Customer/DomainModelUpdatedEventMessage.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Customer/DomainModelUpdatedEventMessage.cs @@ -3,6 +3,7 @@ // ================================================================================================================================= using System; +using System.Diagnostics; using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer.DomainModel; using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.Customer.DomainModelUpdatedEvent; @@ -14,7 +15,7 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Even /// /// Represents a message that provides notification about an update to a . /// - [DataContract(Name = "CustomerUpdatedEventMessage")] + [DataContract(Name = DataContractName)] internal sealed class DomainModelUpdatedEventMessage : DomainModelEventMessage { /// @@ -40,5 +41,11 @@ public DomainModelUpdatedEventMessage(DomainModelEvent eventObject) { return; } + + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "CustomerUpdatedEventMessage"; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/CustomerOrder/DomainModelCreatedEventMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/CustomerOrder/DomainModelCreatedEventMessage.cs index 5842fadd..35cff2d0 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/CustomerOrder/DomainModelCreatedEventMessage.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/CustomerOrder/DomainModelCreatedEventMessage.cs @@ -3,6 +3,7 @@ // ================================================================================================================================= using System; +using System.Diagnostics; using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.CustomerOrder.DomainModel; using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.CustomerOrder.DomainModelCreatedEvent; @@ -14,7 +15,7 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Even /// /// Represents a message that provides notification about the creation of a . /// - [DataContract(Name = "CustomerOrderCreatedEventMessage")] + [DataContract(Name = DataContractName)] internal sealed class DomainModelCreatedEventMessage : DomainModelEventMessage { /// @@ -40,5 +41,11 @@ public DomainModelCreatedEventMessage(DomainModelEvent eventObject) { return; } + + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "CustomerOrderCreatedEventMessage"; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/CustomerOrder/DomainModelDeletedEventMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/CustomerOrder/DomainModelDeletedEventMessage.cs index b401b7dd..9c15a585 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/CustomerOrder/DomainModelDeletedEventMessage.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/CustomerOrder/DomainModelDeletedEventMessage.cs @@ -3,6 +3,7 @@ // ================================================================================================================================= using System; +using System.Diagnostics; using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.CustomerOrder.DomainModel; using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.CustomerOrder.DomainModelDeletedEvent; @@ -14,7 +15,7 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Even /// /// Represents a message that provides notification about the deletion of a . /// - [DataContract(Name = "CustomerOrderDeletedEventMessage")] + [DataContract(Name = DataContractName)] internal sealed class DomainModelDeletedEventMessage : DomainModelEventMessage { /// @@ -40,5 +41,11 @@ public DomainModelDeletedEventMessage(DomainModelEvent eventObject) { return; } + + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "CustomerOrderDeletedEventMessage"; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/CustomerOrder/DomainModelUpdatedEventMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/CustomerOrder/DomainModelUpdatedEventMessage.cs index c8da1d1e..06be2ba5 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/CustomerOrder/DomainModelUpdatedEventMessage.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/CustomerOrder/DomainModelUpdatedEventMessage.cs @@ -3,6 +3,7 @@ // ================================================================================================================================= using System; +using System.Diagnostics; using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.CustomerOrder.DomainModel; using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.CustomerOrder.DomainModelUpdatedEvent; @@ -14,7 +15,7 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Even /// /// Represents a message that provides notification about an update to a . /// - [DataContract(Name = "CustomerOrderUpdatedEventMessage")] + [DataContract(Name = DataContractName)] internal sealed class DomainModelUpdatedEventMessage : DomainModelEventMessage { /// @@ -40,5 +41,11 @@ public DomainModelUpdatedEventMessage(DomainModelEvent eventObject) { return; } + + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "CustomerOrderUpdatedEventMessage"; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Product/DomainModelCreatedEventMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Product/DomainModelCreatedEventMessage.cs index 69c53952..db830094 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Product/DomainModelCreatedEventMessage.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Product/DomainModelCreatedEventMessage.cs @@ -3,6 +3,7 @@ // ================================================================================================================================= using System; +using System.Diagnostics; using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Product.DomainModel; using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.Product.DomainModelCreatedEvent; @@ -14,7 +15,7 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Even /// /// Represents a message that provides notification about the creation of a . /// - [DataContract(Name = "ProductCreatedEventMessage")] + [DataContract(Name = DataContractName)] internal sealed class DomainModelCreatedEventMessage : DomainModelEventMessage { /// @@ -40,5 +41,11 @@ public DomainModelCreatedEventMessage(DomainModelEvent eventObject) { return; } + + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "ProductCreatedEventMessage"; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Product/DomainModelDeletedEventMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Product/DomainModelDeletedEventMessage.cs index 4837a715..9838d7cc 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Product/DomainModelDeletedEventMessage.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Product/DomainModelDeletedEventMessage.cs @@ -3,6 +3,7 @@ // ================================================================================================================================= using System; +using System.Diagnostics; using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Product.DomainModel; using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.Product.DomainModelDeletedEvent; @@ -14,7 +15,7 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Even /// /// Represents a message that provides notification about the deletion of a . /// - [DataContract(Name = "ProductDeletedEventMessage")] + [DataContract(Name = DataContractName)] internal sealed class DomainModelDeletedEventMessage : DomainModelEventMessage { /// @@ -40,5 +41,11 @@ public DomainModelDeletedEventMessage(DomainModelEvent eventObject) { return; } + + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "ProductDeletedEventMessage"; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Product/DomainModelUpdatedEventMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Product/DomainModelUpdatedEventMessage.cs index eae5c1e9..e5bcf74f 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Product/DomainModelUpdatedEventMessage.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/Event/ModelState/Product/DomainModelUpdatedEventMessage.cs @@ -3,6 +3,7 @@ // ================================================================================================================================= using System; +using System.Diagnostics; using System.Runtime.Serialization; using DomainModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Product.DomainModel; using DomainModelEvent = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Events.ModelState.Product.DomainModelUpdatedEvent; @@ -14,7 +15,7 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Even /// /// Represents a message that provides notification about an update to a . /// - [DataContract(Name = "ProductUpdatedEventMessage")] + [DataContract(Name = DataContractName)] internal sealed class DomainModelUpdatedEventMessage : DomainModelEventMessage { /// @@ -40,5 +41,11 @@ public DomainModelUpdatedEventMessage(DomainModelEvent eventObject) { return; } + + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "ProductUpdatedEventMessage"; } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/RequestResponse/Ping/RequestMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/RequestResponse/Ping/RequestMessage.cs new file mode 100644 index 00000000..333cd01e --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/RequestResponse/Ping/RequestMessage.cs @@ -0,0 +1,47 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Diagnostics; +using System.Runtime.Serialization; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.RequestResponse.Ping +{ + /// + /// Represents a message that requests a response from a service. + /// + [DataContract(Name = DataContractName)] + internal sealed class RequestMessage : RequestMessage + { + /// + /// Initializes a new instance of the class. + /// + public RequestMessage() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A unique identifier that is assigned to related messages. + /// + /// + /// is equal to . + /// + public RequestMessage(Guid correlationIdentifier) + : base(correlationIdentifier) + { + return; + } + + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "PingRequestMessage"; + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/RequestResponse/Ping/ResponseMessage.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/RequestResponse/Ping/ResponseMessage.cs new file mode 100644 index 00000000..1bfe5081 --- /dev/null +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Messages/RequestResponse/Ping/ResponseMessage.cs @@ -0,0 +1,52 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System; +using System.Diagnostics; +using System.Runtime.Serialization; +using BaseResponseMessage = RapidField.SolidInstruments.Messaging.ResponseMessage; + +namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.RequestResponse.Ping +{ + /// + /// Represents a response message. + /// + [DataContract(Name = DataContractName)] + internal sealed class ResponseMessage : BaseResponseMessage + { + /// + /// Initializes a new instance of the class. + /// + public ResponseMessage() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The identifier for the associated request message. + /// + /// + /// A unique identifier that is assigned to related messages. + /// + /// + /// is equal to -or- + /// is equal to . + /// + public ResponseMessage(Guid requestMessageIdentifier, Guid correlationIdentifier) + : base(requestMessageIdentifier, correlationIdentifier) + { + return; + } + + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "PingResponseMessage"; + } +} \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Customer/DomainModel.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Customer/DomainModel.cs index 94f995b2..2ec00b71 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Customer/DomainModel.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Customer/DomainModel.cs @@ -15,7 +15,7 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Custom /// /// Represents a customer. /// - [DataContract] + [DataContract(Name = DataContractName)] internal sealed class DomainModel : BaseDomainModel, IAggregate { /// @@ -57,6 +57,12 @@ public String Name set => NameValue = value.RejectIf().IsNullOrEmpty(nameof(Name)); } + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "Customer"; + /// /// Represents the name of the current . /// diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/CustomerOrder/DomainModel.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/CustomerOrder/DomainModel.cs index cd791724..80f4b1ae 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/CustomerOrder/DomainModel.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/CustomerOrder/DomainModel.cs @@ -17,7 +17,7 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Custom /// /// Represents an order placed by a customer. /// - [DataContract] + [DataContract(Name = DataContractName)] internal sealed class DomainModel : BaseDomainModel, IAggregate { /// @@ -98,6 +98,12 @@ public ICollection Products [IgnoreDataMember] public Decimal TotalCost => Products.Select(product => product.Price ?? 0m).Sum(); + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "CustomerOrder"; + /// /// Contains a collection of known instances. /// diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Product/DomainModel.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Product/DomainModel.cs index 80ce83af..499f89e7 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Product/DomainModel.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/Models/Product/DomainModel.cs @@ -15,7 +15,7 @@ namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Produc /// /// Represents an item of merchandise. /// - [DataContract] + [DataContract(Name = DataContractName)] internal sealed class DomainModel : BaseDomainModel, IAggregate { /// @@ -73,6 +73,12 @@ public Decimal? Price set => PriceValue = value?.RejectIf().IsLessThan(0, nameof(Price)); } + /// + /// Represents the name that is used when representing this current type in serialization and transport contexts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DataContractName = "Product"; + /// /// Represents the name of the current . /// diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/SimulatedDependencyModule.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/SimulatedDependencyModule.cs index 62b37b33..01143974 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/SimulatedDependencyModule.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/SimulatedDependencyModule.cs @@ -86,6 +86,9 @@ protected override void Configure(ServiceCollection configurator, IConfiguration configurator.AddTransient, EventMessageTransmitter>(); configurator.AddTransient, EventMessageTransmitter>(); + // Register request transmitters. + configurator.AddTransient, RequestTransmitter>(); + // Register queue listeners. configurator.AddTransient, CommandMessageListener>(); configurator.AddTransient, CommandMessageListener>(); @@ -108,6 +111,9 @@ protected override void Configure(ServiceCollection configurator, IConfiguration configurator.AddTransient, EventMessageListener>(); configurator.AddTransient, EventMessageListener>(); + // Register request listeners. + configurator.AddTransient, MessageListeners.RequestResponse.Ping.RequestListener>(); + // Register command handlers. configurator.AddTransient, CommandHandlers.ModelState.Customer.CreateDomainModelCommandHandler>(); configurator.AddTransient, CommandHandlers.ModelState.Customer.DeleteDomainModelCommandHandler>(); diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/SimulatedMessagingServiceExecutor.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/SimulatedMessagingServiceExecutor.cs index 6e5691a5..d057da19 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/SimulatedMessagingServiceExecutor.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/SimulatedMessagingServiceExecutor.cs @@ -76,7 +76,7 @@ protected override void AddSubscriptions(IMessageListeningProfile subscriptionPr subscriptionProfile.AddTopicListener(); // Add request listeners. - //subscriptionProfile.AddRequestListener(); + subscriptionProfile.AddRequestListener(); } finally { diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/SimulatedMessagingServiceExecutorTests.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/SimulatedMessagingServiceExecutorTests.cs index db1c1aa0..9f6ffb4f 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/SimulatedMessagingServiceExecutorTests.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/SimulatedMessagingServiceExecutorTests.cs @@ -5,11 +5,14 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using RapidField.SolidInstruments.Command; +using System; using System.Linq; using System.Threading; using CreateCustomerCommand = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.Customer.CreateDomainModelCommand; using CreateCustomerCommandMessage = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Command.ModelState.Customer.CreateDomainModelCommandMessage; using CustomerModel = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Models.Customer.DomainModel; +using PingRequestMessage = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.RequestResponse.Ping.RequestMessage; +using PingResponseMessage = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.RequestResponse.Ping.ResponseMessage; using UpdateCustomerCommand = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Commands.ModelState.Customer.UpdateDomainModelCommand; using UpdateCustomerCommandMessage = RapidField.SolidInstruments.Messaging.InMemory.UnitTests.Messages.Command.ModelState.Customer.UpdateDomainModelCommandMessage; @@ -58,6 +61,13 @@ private void OnStopping(ICommandMediator mediator) // Assert. Thread.Sleep(6765); SimulatedServiceState.Customers.Where(entity => entity.Identifier == acmeCoCustomer.Identifier).Single().Name.Should().Be(acmeCoCustomer.Name); + + // Act. + var pingCorrelationIdentifier = Guid.NewGuid(); + var response = mediator.Process(new PingRequestMessage(pingCorrelationIdentifier)); + + // Assert. + response.CorrelationIdentifier.Should().Be(pingCorrelationIdentifier); } } } \ No newline at end of file diff --git a/test/RapidField.SolidInstruments.Messaging.UnitTests/MessageProcessingInformationTests.cs b/test/RapidField.SolidInstruments.Messaging.UnitTests/MessageProcessingInformationTests.cs index e15b5e79..1981dc4a 100644 --- a/test/RapidField.SolidInstruments.Messaging.UnitTests/MessageProcessingInformationTests.cs +++ b/test/RapidField.SolidInstruments.Messaging.UnitTests/MessageProcessingInformationTests.cs @@ -68,7 +68,7 @@ private static void ShouldBeSerializable(SerializationFormat format) // Arrange. var retryCount = 3; var baseDelayDurationInSeconds = 1; - var durationScale = MessageListeningRetryDurationScale.Fibonacci; + var durationScale = MessageListeningRetryDurationScale.Decelerating; var retryPolicy = new MessageListeningRetryPolicy(retryCount, baseDelayDurationInSeconds, durationScale); var secondaryFailureBehavior = MessageListeningSecondaryFailureBehavior.Discard; var transmitExceptionRaisedMessage = true; From 3f6818d28158167a84a98e9dfb78b0c69b44fdb7 Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Thu, 23 Jul 2020 02:27:06 -0500 Subject: [PATCH 47/55] Add formal RabbitMQ abstractions. --- cicd/modules/AutomationTools.psm1 | 67 + en-US_User.dic | 4 + .../ExampleMessagingServiceExecutor.cs | 67 +- .../InMemoryListeningFacade.cs | 6 +- .../RabbitMqClientFactory.cs | 255 +++ .../RabbitMqListeningFacade.cs | 112 ++ .../RabbitMqMessageAdapter.cs | 75 + .../RabbitMqRequestingFacade.cs | 38 + .../RabbitMqTransmittingFacade.cs | 61 + .../RabbitMqMessageTransport.cs | 1547 +++++++++++++++++ .../RabbitMqMessageTransportConnection.cs | 300 ++++ .../MessageListeningFacade.cs | 34 + .../MessagingEntityPath.cs | 8 +- .../Service/MessagingServiceExecutor.cs | 149 +- .../TransportPrimitives/IMessageTransport.cs | 32 - .../TransportPrimitives/MessageLockToken.cs | 36 + .../TransportPrimitives/MessageTransport.cs | 2 +- .../ServiceExecutor.cs | 61 +- 18 files changed, 2737 insertions(+), 117 deletions(-) create mode 100644 src/RapidField.SolidInstruments.Messaging.RabbitMq/RabbitMqClientFactory.cs create mode 100644 src/RapidField.SolidInstruments.Messaging.RabbitMq/RabbitMqListeningFacade.cs create mode 100644 src/RapidField.SolidInstruments.Messaging.RabbitMq/RabbitMqMessageAdapter.cs create mode 100644 src/RapidField.SolidInstruments.Messaging.RabbitMq/RabbitMqRequestingFacade.cs create mode 100644 src/RapidField.SolidInstruments.Messaging.RabbitMq/RabbitMqTransmittingFacade.cs create mode 100644 src/RapidField.SolidInstruments.Messaging.RabbitMq/TransportPrimitives/RabbitMqMessageTransport.cs create mode 100644 src/RapidField.SolidInstruments.Messaging.RabbitMq/TransportPrimitives/RabbitMqMessageTransportConnection.cs diff --git a/cicd/modules/AutomationTools.psm1 b/cicd/modules/AutomationTools.psm1 index de41dceb..5a0c7683 100644 --- a/cicd/modules/AutomationTools.psm1 +++ b/cicd/modules/AutomationTools.psm1 @@ -43,6 +43,7 @@ $ChoclateyPackageNameForNodeJs = "nodejs"; $ChoclateyPackageNameForOpenCover = "opencover.portable"; $ChoclateyPackageNameForOpenSsl = "openssl.light"; $ChoclateyPackageNameForPsake = "psake"; +$ChoclateyPackageNameForRabbitMq = "rabbitmq"; # NPM package names $NpmPackageNameForHtmlMinifier = "html-minifier"; @@ -77,6 +78,7 @@ $SuppressPackageManagers = $false; $SuppressPoshGit = $true; $SuppressPowershellYaml = $false; $SuppressPsake = $false; +$SuppressRabbitMq = $true; # Modules Import-Module $FilePathForCoreModule -Force; @@ -207,6 +209,15 @@ Function GetPsakeInstallationStatus Return (GetChocolateyInstallationStatus) -and (choco list -lo | Where-Object { $_.ToLower().StartsWith("$ChoclateyPackageNameForPsake") }); } +<# +.Synopsis +Returns a boolean value indicating whether or not RabbitMQ is installed in the current environment. +#> +Function GetRabbitMqInstallationStatus +{ + Return (GetChocolateyInstallationStatus) -and (choco list -lo | Where-Object { $_.ToLower().StartsWith("$ChoclateyPackageNameForRabbitMq") }); +} + <# .Synopsis Installs all of the available automation tools in the current environment. @@ -226,6 +237,7 @@ Function InstallAllAutomationTools InstallPoshGit; InstallPowershellYaml; InstallPsake; + InstallRabbitMq; ComposeFinish "Finished installing all automation tools."; } @@ -250,6 +262,7 @@ Function InstallChocolatey Stop-Process -Name "$FileNameForChocoExe" -Force -ErrorAction Ignore; Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString($InstallScriptUriForChocolatey)); + choco feature enable -n allowGlobalConfirmation; ComposeFinish "Finished installing Chocolatey."; } @@ -569,6 +582,28 @@ Function InstallPsake ComposeFinish "Finished installing psake."; } +<# +.Synopsis +Installs RabbitMQ in the current environment. +#> +Function InstallRabbitMq +{ + If ($SuppressRabbitMq -eq $true) + { + ComposeNormal "Suppressing installation of RabbitMQ."; + Return; + } + ElseIf (GetRabbitMqInstallationStatus) + { + ComposeNormal "RabbitMQ is already installed."; + Return; + } + + ComposeStart "Installing RabbitMQ."; + UseChocolateyToInstall -PackageName "$ChoclateyPackageNameForRabbitMq"; + ComposeFinish "Finished installing RabbitMQ."; +} + <# .Synopsis Exposes the path for the specified command using the specified environment target. @@ -843,6 +878,18 @@ Function RestorePsake ComposeFinish "Finished restoring psake."; } +<# +.Synopsis +Uninstalls, if necessary, and installs RabbitMQ in the current environment. +#> +Function RestoreRabbitMq +{ + ComposeStart "Restoring RabbitMQ."; + UninstallRabbitMq; + InstallRabbitMq; + ComposeFinish "Finished restoring RabbitMQ."; +} + <# .Synopsis Uninstalls all available automation tools in the current environment. @@ -861,6 +908,7 @@ Function UninstallAllAutomationTools UninstallPoshGit; UninstallPowershellYaml; UninstallPsake; + UninstallRabbitMq; ComposeFinish "Finished uninstalling all automation tools."; } @@ -1111,6 +1159,25 @@ Function UninstallPsake } } +<# +.Synopsis +Uninstalls RabbitMQ in the current environment. +#> +Function UninstallRabbitMq +{ + If ($SuppressRabbitMq -eq $true) + { + ComposeNormal "Suppressing uninstallation of RabbitMQ."; + Return; + } + ElseIf (GetRabbitMqInstallationStatus) + { + ComposeStart "Uninstalling RabbitMQ."; + UseChocolateyToUninstall -PackageName "$ChoclateyPackageNameForRabbitMq"; + ComposeFinish "Finished uninstalling RabbitMQ."; + } +} + <# .Synopsis Uses Chocolatey to install the specified package in the current environment. diff --git a/en-US_User.dic b/en-US_User.dic index d7e6399b..25f23432 100644 --- a/en-US_User.dic +++ b/en-US_User.dic @@ -2,6 +2,7 @@ aa aaaaaaaa accomodate adamjstone +amqp api apidoc appveyor @@ -97,8 +98,10 @@ Enum Enums env eventing +Exc Exe extensibility +fanout fibonacci finalizer fo @@ -138,6 +141,7 @@ labeltokentwo labeltwo leanify li +localhost loglevel Lorem ly diff --git a/example/RapidField.SolidInstruments.Example.ServiceApplication/ExampleMessagingServiceExecutor.cs b/example/RapidField.SolidInstruments.Example.ServiceApplication/ExampleMessagingServiceExecutor.cs index c5a64d0f..c697a8f5 100644 --- a/example/RapidField.SolidInstruments.Example.ServiceApplication/ExampleMessagingServiceExecutor.cs +++ b/example/RapidField.SolidInstruments.Example.ServiceApplication/ExampleMessagingServiceExecutor.cs @@ -4,18 +4,13 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using RapidField.SolidInstruments.Command; -using RapidField.SolidInstruments.EventAuthoring; using RapidField.SolidInstruments.Example.Contracts.Messages; -using RapidField.SolidInstruments.InversionOfControl; using RapidField.SolidInstruments.InversionOfControl.DotNetNative; using RapidField.SolidInstruments.Messaging; using RapidField.SolidInstruments.Messaging.EventMessages; using RapidField.SolidInstruments.Messaging.Service; -using RapidField.SolidInstruments.Service; using System; using System.IO; -using System.Threading; namespace RapidField.SolidInstruments.Example.ServiceApplication { @@ -111,65 +106,15 @@ protected override void ConfigureHeartbeat(HeartbeatSchedule heartbeatSchedule, protected override void Dispose(Boolean disposing) => base.Dispose(disposing); /// - /// Performs startup operations for the service. + /// When overridden by a derived class, gets a copyright notice which is written to the console at the start of service + /// execution. /// - /// - /// A scope that is used to resolve service dependencies. - /// - /// - /// Configuration information for the service application. - /// - /// - /// An object that provides control over execution lifetime. - /// - protected override void OnExecutionStarting(IDependencyScope dependencyScope, IConfiguration applicationConfiguration, IServiceExecutionLifetime executionLifetime) - { - Console.WriteLine($"Solid Instruments | {ServiceName}"); - Console.WriteLine($"Copyright (c) RapidField LLC. All rights reserved.{Environment.NewLine}"); - - try - { - Console.CancelKeyPress += (sender, eventArguments) => - { - executionLifetime.End(); - eventArguments.Cancel = true; - }; - - var mediator = dependencyScope.Resolve(); - var applicationStartedEvent = new ApplicationStartedEvent(ServiceName); - var applicationStartedEventMessage = new ApplicationStartedEventMessage(applicationStartedEvent); - mediator.Process(applicationStartedEventMessage); - Thread.Sleep(1600); - } - finally - { - base.OnExecutionStarting(dependencyScope, applicationConfiguration, executionLifetime); - } - } + protected override sealed String CopyrightNotice => "Copyright (c) RapidField LLC. All rights reserved."; /// - /// Performs shutdown operations for the service. + /// When overridden by a derived class, gets a product name associated with the service which is written to the console at + /// the start of service execution. /// - /// - /// A scope that is used to resolve service dependencies. - /// - /// - /// Configuration information for the service application. - /// - protected override void OnExecutionStopping(IDependencyScope dependencyScope, IConfiguration applicationConfiguration) - { - try - { - var mediator = dependencyScope.Resolve(); - var applicationStoppedEvent = new ApplicationStoppedEvent(ServiceName); - var applicationStoppedEventMessage = new ApplicationStoppedEventMessage(applicationStoppedEvent); - mediator.Process(applicationStoppedEventMessage); - Thread.Sleep(3200); - } - finally - { - base.OnExecutionStopping(dependencyScope, applicationConfiguration); - } - } + protected override sealed String ProductName => "Solid Instruments"; } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryListeningFacade.cs b/src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryListeningFacade.cs index f7a6a1c5..c5e61615 100644 --- a/src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryListeningFacade.cs +++ b/src/RapidField.SolidInstruments.Messaging.InMemory/InMemoryListeningFacade.cs @@ -103,6 +103,10 @@ protected sealed override void RegisterMessageHandler(Action m /// /// A task representing the asynchronous operation. /// - protected sealed override Task RouteToDeadLetterQueueAsync(PrimitiveMessage adaptedMessage, TMessage message, IEnumerable pathLabels, MessagingEntityType entityType) => Task.CompletedTask; // In-memory listeners do not support dead lettering. + protected sealed override Task RouteToDeadLetterQueueAsync(PrimitiveMessage adaptedMessage, TMessage message, IEnumerable pathLabels, MessagingEntityType entityType) + { + var deadLetterQueueSender = ClientFactory.GetQueueSender(AppendDeadLetterQueueLabel(pathLabels)); + return deadLetterQueueSender.SendAsync(adaptedMessage); + } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.RabbitMq/RabbitMqClientFactory.cs b/src/RapidField.SolidInstruments.Messaging.RabbitMq/RabbitMqClientFactory.cs new file mode 100644 index 00000000..df0857cf --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.RabbitMq/RabbitMqClientFactory.cs @@ -0,0 +1,255 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Messaging.TransportPrimitives; +using System; +using System.Diagnostics; +using System.Threading.Tasks; + +namespace RapidField.SolidInstruments.Messaging.RabbitMq +{ + /// + /// Represents an appliance that manages RabbitMQ messaging clients. + /// + public sealed class RabbitMqClientFactory : MessagingClientFactory + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A connection that governs interaction with messaging entities. + /// + /// + /// is . + /// + public RabbitMqClientFactory(IMessageTransportConnection connection) + : base(connection) + { + Transport = connection.Transport; + } + + /// + /// Creates a new implementation-specific client that facilitates receive operations. + /// + /// + /// The type of the message that the client handles. + /// + /// + /// A connection that governs interaction with messaging entities. + /// + /// + /// The type of the entity. + /// + /// + /// The unique path for the entity. + /// + /// + /// The unique name of the subscription. + /// + /// + /// A new implementation-specific client that facilitates receive operations. + /// + protected sealed override IMessagingEntityReceiveClient CreateMessageReceiver(IMessageTransportConnection connection, MessagingEntityType entityType, IMessagingEntityPath entityPath, String subscriptionName) => entityType switch + { + MessagingEntityType.Queue => CreateQueueClient(connection, entityPath), + MessagingEntityType.Topic => CreateSubscriptionClient(connection, entityPath, subscriptionName), + _ => throw new UnsupportedSpecificationException($"The specified entity type, {entityType}, is not supported.") + }; + + /// + /// Creates a new implementation-specific client that facilitates send operations. + /// + /// + /// The type of the message that the client handles. + /// + /// + /// A connection that governs interaction with messaging entities. + /// + /// + /// The type of the entity. + /// + /// + /// The unique path for the entity. + /// + /// + /// A new implementation-specific client that facilitates send operations. + /// + protected sealed override IMessagingEntitySendClient CreateMessageSender(IMessageTransportConnection connection, MessagingEntityType entityType, IMessagingEntityPath entityPath) => entityType switch + { + MessagingEntityType.Queue => CreateQueueClient(connection, entityPath), + MessagingEntityType.Topic => CreateTopicClient(connection, entityPath), + _ => throw new UnsupportedSpecificationException($"The specified entity type, {entityType}, is not supported.") + }; + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected sealed override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Creates a new for the specified message type. + /// + /// + /// The type of the message that the client handles. + /// + /// + /// A connection that governs interaction with messaging entities. + /// + /// + /// The unique path for the queue. + /// + /// + /// A new . + /// + /// + /// An exception was raised while creating the client. + /// + [DebuggerHidden] + private IMessageQueueClient CreateQueueClient(IMessageTransportConnection connection, IMessagingEntityPath queuePath) + where TMessage : class + { + EnsureQueueExistanceAsync(queuePath).Wait(); + return new MessageQueueClient(connection, queuePath); + } + + /// + /// Creates a new for the specified message type. + /// + /// + /// The type of the message that the client handles. + /// + /// + /// A connection that governs interaction with messaging entities. + /// + /// + /// A unique path for the topic. + /// + /// + /// A name for the subscription. + /// + /// + /// A new . + /// + /// + /// An exception was raised while creating the client. + /// + [DebuggerHidden] + private IMessageSubscriptionClient CreateSubscriptionClient(IMessageTransportConnection connection, IMessagingEntityPath topicPath, String subscriptionName) + where TMessage : class + { + EnsureSubscriptionExistanceAsync(topicPath, subscriptionName).Wait(); + return new MessageSubscriptionClient(connection, topicPath, subscriptionName); + } + + /// + /// Creates a new for the specified message type. + /// + /// + /// The type of the message that the client handles. + /// + /// + /// A connection that governs interaction with messaging entities. + /// + /// + /// A unique path for the topic. + /// + /// + /// A new . + /// + /// + /// An exception was raised while creating the client. + /// + [DebuggerHidden] + private IMessageTopicClient CreateTopicClient(IMessageTransportConnection connection, IMessagingEntityPath topicPath) + where TMessage : class + { + EnsureTopicExistanceAsync(topicPath).Wait(); + return new MessageTopicClient(connection, topicPath); + } + + /// + /// Asynchronously creates the specified RabbitMQ queue if it does not exist. + /// + /// + /// The queue entity path. + /// + /// + /// A task representing the asynchronous operation. + /// + [DebuggerHidden] + private Task EnsureQueueExistanceAsync(IMessagingEntityPath queuePath) => Task.Factory.StartNew(() => + { + lock (MessagingEntityClient.EnsureQueueExistenceSyncRoot) + { + if (Transport.QueueExists(queuePath)) + { + return; + } + + Transport.CreateQueueAsync(queuePath).Wait(); + } + }); + + /// + /// Asynchronously creates the specified RabbitMQ subscription if it does not exist. + /// + /// + /// The topic entity path for the subscription. + /// + /// + /// The name of the subscription. + /// + /// + /// A task representing the asynchronous operation. + /// + [DebuggerHidden] + private Task EnsureSubscriptionExistanceAsync(IMessagingEntityPath topicPath, String subscriptionName) => Task.Factory.StartNew(() => + { + EnsureTopicExistanceAsync(topicPath).Wait(); + + lock (MessagingEntityClient.EnsureSubscriptionExistenceSyncRoot) + { + if (Transport.SubscriptionExists(topicPath, subscriptionName)) + { + return; + } + + Transport.CreateSubscriptionAsync(topicPath, subscriptionName).Wait(); + } + }); + + /// + /// Asynchronously creates the specified RabbitMQ topic if it does not exist. + /// + /// + /// The topic entity path. + /// + /// + /// A task representing the asynchronous operation. + /// + [DebuggerHidden] + private Task EnsureTopicExistanceAsync(IMessagingEntityPath topicPath) => Task.Factory.StartNew(() => + { + lock (MessagingEntityClient.EnsureTopicExistenceSyncRoot) + { + if (Transport.TopicExists(topicPath)) + { + return; + } + + Transport.CreateTopicAsync(topicPath).Wait(); + } + }); + + /// + /// Represents the transport for which the current creates clients. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly IMessageTransport Transport; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.RabbitMq/RabbitMqListeningFacade.cs b/src/RapidField.SolidInstruments.Messaging.RabbitMq/RabbitMqListeningFacade.cs new file mode 100644 index 00000000..a4aae2e3 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.RabbitMq/RabbitMqListeningFacade.cs @@ -0,0 +1,112 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Concurrency; +using RapidField.SolidInstruments.Messaging.TransportPrimitives; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace RapidField.SolidInstruments.Messaging.RabbitMq +{ + /// + /// Facilitates listening operations for RabbitMQ queues. + /// + public sealed class RabbitMqListeningFacade : MessageListeningFacade + { + /// + /// Initializes a new instance of the class. + /// + /// + /// An implementation-specific messaging facade that is used to transmit response messages. + /// + /// + /// is . + /// + public RabbitMqListeningFacade(RabbitMqTransmittingFacade transmittingFacade) + : base(transmittingFacade) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Registers the specified message handler with the bus. + /// + /// + /// An action that handles a message. + /// + /// + /// An implementation-specific receive client. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + protected sealed override void RegisterMessageHandler(Action messageHandler, IMessagingEntityReceiveClient receiveClient, IConcurrencyControlToken controlToken) + { + var messageHandlerAction = new Action((message) => + { + var lockToken = (MessageLockToken)null; + + try + { + lockToken = message.LockToken; + + if (lockToken is null) + { + throw new MessageListeningException("The message cannot be processed because the lock token is invalid."); + } + else if (receiveClient.Connection.State == MessageTransportConnectionState.Closed) + { + throw new MessageListeningException("The message cannot be processed because the receive client is unavailable."); + } + + messageHandler(message); + receiveClient.ConveySuccessAsync(lockToken).Wait(); + } + catch + { + receiveClient.ConveyFailureAsync(lockToken).Wait(); + } + }); + + receiveClient.RegisterMessageHandler(messageHandlerAction); + } + + /// + /// Asynchronously sends the specified message to a dead letter queue. + /// + /// + /// The type of the message. + /// + /// + /// The adapted message. + /// + /// + /// The message to route to a dead letter queue. + /// + /// + /// An ordered collection of as many as three non-null, non-empty alphanumeric textual labels to append to the path, or + /// to omit path labels. + /// + /// + /// The targeted entity type. + /// + /// + /// A task representing the asynchronous operation. + /// + protected sealed override Task RouteToDeadLetterQueueAsync(PrimitiveMessage adaptedMessage, TMessage message, IEnumerable pathLabels, MessagingEntityType entityType) + { + var deadLetterQueueSender = ClientFactory.GetQueueSender(AppendDeadLetterQueueLabel(pathLabels)); + return deadLetterQueueSender.SendAsync(adaptedMessage); + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.RabbitMq/RabbitMqMessageAdapter.cs b/src/RapidField.SolidInstruments.Messaging.RabbitMq/RabbitMqMessageAdapter.cs new file mode 100644 index 00000000..24750c68 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.RabbitMq/RabbitMqMessageAdapter.cs @@ -0,0 +1,75 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Messaging.TransportPrimitives; +using RapidField.SolidInstruments.Serialization; +using System; + +namespace RapidField.SolidInstruments.Messaging.RabbitMq +{ + /// + /// Facilitates conversion of general-format messages to and from RabbitMQ messages. + /// + public sealed class RabbitMqMessageAdapter : MessageAdapter + { + /// + /// Initializes a new instance of the class. + /// + public RabbitMqMessageAdapter() + : base() + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// Gets the format that is used to serialize and deserialize messages. The default value is + /// . + /// + /// + /// is equal to . + /// + public RabbitMqMessageAdapter(SerializationFormat messageSerializationFormat) + : base(messageSerializationFormat) + { + return; + } + + /// + /// Converts the specified general-format message to an implementation-specific message. + /// + /// + /// The type of the message. + /// + /// + /// A general-format message to convert. + /// + /// + /// A serializer that is used to serialize the message. + /// + /// + /// The implementation-specific message. + /// + protected sealed override PrimitiveMessage ConvertForward(TMessage message, ISerializer serializer) => new PrimitiveMessage(message, new MessageLockToken(Guid.NewGuid(), message.Identifier), MessageSerializationFormat); + + /// + /// Converts the specified implementation-specific message to a general-format message. + /// + /// + /// The type of the message. + /// + /// + /// An implementation-specific message to convert. + /// + /// + /// A serializer that is used to serialize the message. + /// + /// + /// The general-format message. + /// + protected sealed override TMessage ConvertReverse(PrimitiveMessage message, ISerializer serializer) => message.GetBody(); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.RabbitMq/RabbitMqRequestingFacade.cs b/src/RapidField.SolidInstruments.Messaging.RabbitMq/RabbitMqRequestingFacade.cs new file mode 100644 index 00000000..98401a47 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.RabbitMq/RabbitMqRequestingFacade.cs @@ -0,0 +1,38 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Messaging.TransportPrimitives; +using System; + +namespace RapidField.SolidInstruments.Messaging.RabbitMq +{ + /// + /// Facilitates requesting operations for RabbitMQ. + /// + public sealed class RabbitMqRequestingFacade : MessageRequestingFacade + { + /// + /// Initializes a new instance of the class. + /// + /// + /// An implementation-specific messaging facade that listens for request messages. + /// + /// + /// is . + /// + public RabbitMqRequestingFacade(RabbitMqListeningFacade listeningFacade) + : base(listeningFacade) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.RabbitMq/RabbitMqTransmittingFacade.cs b/src/RapidField.SolidInstruments.Messaging.RabbitMq/RabbitMqTransmittingFacade.cs new file mode 100644 index 00000000..34049528 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.RabbitMq/RabbitMqTransmittingFacade.cs @@ -0,0 +1,61 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Core.Concurrency; +using RapidField.SolidInstruments.Messaging.TransportPrimitives; +using System; +using System.Threading.Tasks; + +namespace RapidField.SolidInstruments.Messaging.RabbitMq +{ + /// + /// Facilitates transmission operations for RabbitMQ queues. + /// + public sealed class RabbitMqTransmittingFacade : MessageTransmittingFacade + { + /// + /// Initializes a new instance of the class. + /// + /// + /// An appliance that creates manages implementation-specific messaging clients. + /// + /// + /// An appliance that facilitates implementation-specific message conversion. + /// + /// + /// is -or- is + /// . + /// + public RabbitMqTransmittingFacade(IMessagingClientFactory clientFactory, IMessageAdapter messageAdapter) + : base(clientFactory, messageAdapter) + { + return; + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) => base.Dispose(disposing); + + /// + /// Asynchronously transmits the specified message to a bus. + /// + /// + /// The message to transmit. + /// + /// + /// An implementation-specific receive client. + /// + /// + /// A token that represents and manages contextual thread safety. + /// + /// + /// A task representing the asynchronous operation. + /// + protected sealed override Task TransmitAsync(PrimitiveMessage message, IMessagingEntitySendClient sendClient, IConcurrencyControlToken controlToken) => sendClient.SendAsync(message); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.RabbitMq/TransportPrimitives/RabbitMqMessageTransport.cs b/src/RapidField.SolidInstruments.Messaging.RabbitMq/TransportPrimitives/RabbitMqMessageTransport.cs new file mode 100644 index 00000000..e46d21fb --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.RabbitMq/TransportPrimitives/RabbitMqMessageTransport.cs @@ -0,0 +1,1547 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RabbitMQ.Client; +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Core.Extensions; +using RapidField.SolidInstruments.Messaging.TransportPrimitives; +using RapidField.SolidInstruments.Serialization; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using IRabbitMqChannel = RabbitMQ.Client.IModel; +using IRabbitMqConnection = RabbitMQ.Client.IConnection; +using IRabbitMqConnectionFactory = RabbitMQ.Client.IConnectionFactory; +using RabbitMqConnectionFactory = RabbitMQ.Client.ConnectionFactory; + +namespace RapidField.SolidInstruments.Messaging.RabbitMq.TransportPrimitives +{ + /// + /// Supports message exchange for RabbitMQ queues and topics. + /// + internal sealed class RabbitMqMessageTransport : Instrument, IMessageTransport + { + /// + /// Initializes a new instance of the class. + /// + [DebuggerHidden] + internal RabbitMqMessageTransport() + : this(DefaultConnectionHostName, null, null, null, null) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The connection URI for the target RabbitMQ instance. + /// + /// + /// is not valid for AMQP connections. + /// + /// + /// is . + /// + [DebuggerHidden] + internal RabbitMqMessageTransport(Uri connectionUri) + : this(connectionUri, PrimitiveMessage.DefaultBodySerializationFormat) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The connection URI for the target RabbitMQ instance. + /// + /// + /// The format that is used to serialize enqueued message bodies. The default value is + /// . + /// + /// + /// is not valid for AMQP connections. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + [DebuggerHidden] + internal RabbitMqMessageTransport(Uri connectionUri, SerializationFormat messageBodySerializationFormat) + : this(CreateConnectionFactory(connectionUri), messageBodySerializationFormat) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The name of the host to connect to. The default value is "localhost". + /// + /// + /// The port number to connect to, or to use the default port number (5672). + /// + /// + /// The name of the virtual host to connect to, or to omit a virtual host. + /// + /// + /// The user name for the connection, or to use the default user name ("guest"). + /// + /// + /// The password for the connection, or to use the default password ("guest"). + /// + /// + /// is empty. + /// + /// + /// is . + /// + [DebuggerHidden] + internal RabbitMqMessageTransport(String hostName, Int32? portNumber, String virtualHost, String userName, String password) + : this(hostName, portNumber, virtualHost, userName, password, PrimitiveMessage.DefaultBodySerializationFormat) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The name of the host to connect to. The default value is "localhost". + /// + /// + /// The port number to connect to, or to use the default port number (5672). + /// + /// + /// The name of the virtual host to connect to, or to omit a virtual host. + /// + /// + /// The user name for the connection, or to use the default user name ("guest"). + /// + /// + /// The password for the connection, or to use the default password ("guest"). + /// + /// + /// The format that is used to serialize enqueued message bodies. The default value is + /// . + /// + /// + /// is empty. + /// + /// + /// is . + /// + /// + /// is equal to . + /// + [DebuggerHidden] + internal RabbitMqMessageTransport(String hostName, Int32? portNumber, String virtualHost, String userName, String password, SerializationFormat messageBodySerializationFormat) + : this(CreateConnectionFactory(hostName, portNumber, virtualHost, userName, password), messageBodySerializationFormat) + { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A factory that contains connection information for the target RabbitMQ instance. + /// + /// + /// The format that is used to serialize enqueued message bodies. The default value is + /// . + /// + /// + /// is . + /// + /// + /// is equal to . + /// + [DebuggerHidden] + private RabbitMqMessageTransport(IRabbitMqConnectionFactory connectionFactory, SerializationFormat messageBodySerializationFormat) + : base() + { + SharedConnection = connectionFactory.RejectIf().IsNull(nameof(connectionFactory)).TargetArgument.CreateConnection(); + Channel = SharedConnection.CreateModel(); + MessageBodySerializationFormat = messageBodySerializationFormat.RejectIf().IsEqualToValue(SerializationFormat.Unspecified, nameof(messageBodySerializationFormat)); + } + + /// + /// Closes the specified connection as an idempotent operation. + /// + /// + /// The connection to close. + /// + /// + /// is . + /// + public void CloseConnection(IMessageTransportConnection connection) + { + if (ConnectionDictionary.ContainsKey(connection.RejectIf().IsNull(nameof(connection)).TargetArgument.Identifier)) + { + if (ConnectionDictionary.TryRemove(connection.Identifier, out _)) + { + connection.Dispose(); + } + } + } + + /// + /// Asynchronously notifies the specified queue that a locked message was not processed and can be made available for + /// processing by other consumers. + /// + /// + /// A lock token corresponding to a message that was not processed. + /// + /// + /// A unique textual path that identifies the queue. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is -or- is . + /// + /// + /// does not reference an existing locked message -or- does not + /// reference an existing queue. + /// + /// + /// The object is disposed. + /// + /// + /// The operation timed out. + /// + public Task ConveyFailureToQueueAsync(MessageLockToken lockToken, IMessagingEntityPath path) + { + RejectIfDisposed(); + + return Task.Factory.StartNew(() => + { + try + { + Channel.BasicReject(lockToken.DeliveryTag, true); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convey failure. The specified queue, \"{path}\", does not exist or the RabbitMQ connection is unavailable.", exception); + } + }); + } + + /// + /// Asynchronously notifies the specified subscription that a locked message was not processed and can be made available for + /// processing by other consumers. + /// + /// + /// A lock token corresponding to a message that was not processed. + /// + /// + /// A unique textual path that identifies the subscription topic. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is -or- is . + /// + /// + /// does not reference an existing locked message -or- does not + /// reference an existing topic. + /// + /// + /// The object is disposed. + /// + /// + /// The operation timed out. + /// + public Task ConveyFailureToSubscriptionAsync(MessageLockToken lockToken, IMessagingEntityPath path) + { + RejectIfDisposed(); + + return Task.Factory.StartNew(() => + { + try + { + Channel.BasicReject(lockToken.DeliveryTag, true); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convey failure. The specified topic, \"{path}\", does not exist or the RabbitMQ connection is unavailable.", exception); + } + }); + } + + /// + /// Asynchronously notifies the specified queue that a locked message was processed successfully and can be destroyed + /// permanently. + /// + /// + /// A lock token corresponding to a message that was processed successfully. + /// + /// + /// A unique textual path that identifies the queue. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is -or- is . + /// + /// + /// does not reference an existing locked message -or- does not + /// reference an existing queue. + /// + /// + /// The object is disposed. + /// + public Task ConveySuccessToQueueAsync(MessageLockToken lockToken, IMessagingEntityPath path) + { + RejectIfDisposed(); + + return Task.Factory.StartNew(() => + { + try + { + Channel.BasicAck(lockToken.DeliveryTag, false); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convey success. The specified queue, \"{path}\", does not exist or the RabbitMQ connection is unavailable.", exception); + } + }); + } + + /// + /// Asynchronously notifies the specified subscription that a locked message was processed successfully and can be destroyed + /// permanently. + /// + /// + /// A lock token corresponding to a message that was processed successfully. + /// + /// + /// A unique textual path that identifies the subscription topic. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is -or- is . + /// + /// + /// does not reference an existing locked message -or- does not + /// reference an existing topic. + /// + /// + /// The object is disposed. + /// + public Task ConveySuccessToSubscriptionAsync(MessageLockToken lockToken, IMessagingEntityPath path) + { + RejectIfDisposed(); + + return Task.Factory.StartNew(() => + { + try + { + Channel.BasicAck(lockToken.DeliveryTag, false); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convey success. The specified topic, \"{path}\", does not exist or the RabbitMQ connection is unavailable.", exception); + } + }); + } + + /// + /// Opens and returns a new to the current + /// . + /// + /// + /// A new to the current . + /// + /// + /// The object is disposed. + /// + public IMessageTransportConnection CreateConnection() + { + RejectIfDisposed(); + var connection = new RabbitMqMessageTransportConnection(this); + + if (ConnectionDictionary.TryAdd(connection.Identifier, connection)) + { + return connection; + } + + return connection; + } + + /// + /// Asynchronously creates a new queue. + /// + /// + /// A unique textual path that identifies the new queue. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// An entity with the specified path already exists. + /// + public Task CreateQueueAsync(IMessagingEntityPath path) => CreateQueueAsync(path, MessagingEntity.DefaultMessageLockExpirationThreshold); + + /// + /// Asynchronously creates a new queue. + /// + /// + /// A unique textual path that identifies the new queue. + /// + /// + /// The length of time that a locked message is held before abandoning the associated token and making the message available + /// for processing. The default value is three minutes. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// is less than eight seconds. + /// + /// + /// The object is disposed. + /// + /// + /// An entity with the specified path already exists. + /// + public Task CreateQueueAsync(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold) => CreateQueueAsync(path, messageLockExpirationThreshold, MessagingEntity.DefaultEnqueueTimeoutThreshold); + + /// + /// Asynchronously creates a new queue. + /// + /// + /// A unique textual path that identifies the new queue. + /// + /// + /// The length of time that a locked message is held before abandoning the associated token and making the message available + /// for processing. The default value is three minutes. + /// + /// + /// The maximum length of time to wait for a message to be enqueued before raising an exception. The default value is eight + /// seconds. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// is less than eight seconds -or- + /// is less than two seconds. + /// + /// + /// The object is disposed. + /// + /// + /// An entity with the specified path already exists. + /// + public Task CreateQueueAsync(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold, TimeSpan enqueueTimeoutThreshold) => Task.Factory.StartNew(() => + { + RejectIfDisposed(); + + if (TryCreateQueue(path.RejectIf().IsNull(nameof(path)).TargetArgument, messageLockExpirationThreshold.RejectIf().IsLessThan(MessagingEntity.MessageLockExpirationThresholdFloor, nameof(messageLockExpirationThreshold)), enqueueTimeoutThreshold.RejectIf().IsLessThan(MessagingEntity.EnqueueTimeoutThresholdFloor, nameof(enqueueTimeoutThreshold)))) + { + return; + } + + throw new InvalidOperationException($"The specified queue, \"{path}\", already exists"); + }); + + /// + /// Asynchronously creates a new subscription. + /// + /// + /// A unique textual path that identifies the subscription topic. + /// + /// + /// The unique name of the subscription. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is empty. + /// + /// + /// is -or- is + /// . + /// + /// + /// The object is disposed. + /// + /// + /// The specified subscription already exists. + /// + public Task CreateSubscriptionAsync(IMessagingEntityPath path, String subscriptionName) => Task.Factory.StartNew(() => + { + RejectIfDisposed(); + + if (TryCreateSubscription(path.RejectIf().IsNull(nameof(path)).TargetArgument, subscriptionName.RejectIf().IsNullOrEmpty(nameof(subscriptionName)))) + { + return; + } + + throw new InvalidOperationException($"The specified subscription, \"{subscriptionName}\", already exists"); + }); + + /// + /// Asynchronously creates a new topic. + /// + /// + /// A unique textual path that identifies the new topic. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// An entity with the specified path already exists. + /// + public Task CreateTopicAsync(IMessagingEntityPath path) => CreateTopicAsync(path, MessagingEntity.DefaultMessageLockExpirationThreshold); + + /// + /// Asynchronously creates a new topic. + /// + /// + /// A unique textual path that identifies the new topic. + /// + /// + /// The length of time that a locked message is held before abandoning the associated token and making the message available + /// for processing. The default value is three minutes. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// is less than eight seconds. + /// + /// + /// The object is disposed. + /// + /// + /// An entity with the specified path already exists. + /// + public Task CreateTopicAsync(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold) => CreateTopicAsync(path, messageLockExpirationThreshold, MessagingEntity.DefaultEnqueueTimeoutThreshold); + + /// + /// Asynchronously creates a new topic. + /// + /// + /// A unique textual path that identifies the new topic. + /// + /// + /// The length of time that a locked message is held before abandoning the associated token and making the message available + /// for processing. The default value is three minutes. + /// + /// + /// The maximum length of time to wait for a message to be enqueued before raising an exception. The default value is eight + /// seconds. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// is less than eight seconds -or- + /// is less than two seconds. + /// + /// + /// The object is disposed. + /// + /// + /// An entity with the specified path already exists. + /// + public Task CreateTopicAsync(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold, TimeSpan enqueueTimeoutThreshold) => Task.Factory.StartNew(() => + { + RejectIfDisposed(); + + if (TryCreateTopic(path.RejectIf().IsNull(nameof(path)).TargetArgument, messageLockExpirationThreshold.RejectIf().IsLessThan(MessagingEntity.MessageLockExpirationThresholdFloor, nameof(messageLockExpirationThreshold)), enqueueTimeoutThreshold.RejectIf().IsLessThan(MessagingEntity.EnqueueTimeoutThresholdFloor, nameof(enqueueTimeoutThreshold)))) + { + return; + } + + throw new InvalidOperationException($"Failed to create topic. The specified topic, \"{path}\", already exists"); + }); + + /// + /// Asynchronously destroys the specified queue. + /// + /// + /// A unique textual path that identifies the queue. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// The specified queue does not exist. + /// + public Task DestroyQueueAsync(IMessagingEntityPath path) => Task.Factory.StartNew(() => + { + RejectIfDisposed(); + + if (TryDestroyQueue(path.RejectIf().IsNull(nameof(path)).TargetArgument)) + { + return; + } + + throw new InvalidOperationException($"Failed to destroy queue. The specified queue, \"{path}\", does not exist or the RabbitMQ connection is unavailable."); + }); + + /// + /// Asynchronously destroys the specified subscription. + /// + /// + /// A unique textual path that identifies the subscription topic. + /// + /// + /// The unique name of the subscription. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is empty. + /// + /// + /// is -or- is + /// . + /// + /// + /// The object is disposed. + /// + /// + /// The specified subscription does not exist. + /// + public Task DestroySubscriptionAsync(IMessagingEntityPath path, String subscriptionName) => Task.Factory.StartNew(() => + { + RejectIfDisposed(); + + if (TryDestroySubscription(path.RejectIf().IsNull(nameof(path)).TargetArgument, subscriptionName.RejectIf().IsNullOrEmpty(nameof(subscriptionName)))) + { + return; + } + + throw new InvalidOperationException($"Failed to destroy subscription. The specified subscription, \"{subscriptionName}\", does not exist or the RabbitMQ connection is unavailable."); + }); + + /// + /// Asynchronously destroys the specified topic. + /// + /// + /// A unique textual path that identifies the topic. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + /// + /// The specified topic does not exist. + /// + public Task DestroyTopicAsync(IMessagingEntityPath path) => Task.Factory.StartNew(() => + { + RejectIfDisposed(); + + if (TryDestroyTopic(path.RejectIf().IsNull(nameof(path)).TargetArgument)) + { + return; + } + + throw new InvalidOperationException($"Failed to destroy topic. The specified topic, \"{path}\", does not exist."); + }); + + /// + /// Returns a value indicating whether or not the specified queue exists. + /// + /// + /// A unique textual path that identifies the queue. + /// + /// + /// if the queue exists, otherwise . + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public Boolean QueueExists(IMessagingEntityPath path) + { + RejectIfDisposed(); + + try + { + DeclareAndBindQueue(path); + return true; + } + catch (ArgumentNullException) + { + throw; + } + catch + { + return false; + } + } + + /// + /// Asynchronously requests the specified number of messages from the specified queue. + /// + /// + /// A unique textual path that identifies the queue. + /// + /// + /// The maximum number of messages to read from the queue. + /// + /// + /// A task representing the asynchronous operation and containing the dequeued messages, if any. + /// + /// + /// is . + /// + /// + /// is less than zero. + /// + /// + /// The specified queue does not exist. + /// + /// + /// The object is disposed. + /// + /// + /// The operation timed out. + /// + public Task> ReceiveFromQueueAsync(IMessagingEntityPath path, Int32 count) => throw new NotImplementedException($"The RabbitMQ implementation does not use this abstraction."); + + /// + /// Asynchronously requests the specified number of messages from the specified topic. + /// + /// + /// A unique textual path that identifies the topic. + /// + /// + /// The unique name of the subscription. + /// + /// + /// The maximum number of messages to read from the topic. + /// + /// + /// A task representing the asynchronous operation and containing the dequeued messages, if any. + /// + /// + /// is empty. + /// + /// + /// is -or- is + /// . + /// + /// + /// is less than zero. + /// + /// + /// The specified topic does not exist. + /// + /// + /// The object is disposed. + /// + /// + /// The operation timed out. + /// + public Task> ReceiveFromTopicAsync(IMessagingEntityPath path, String subscriptionName, Int32 count) => throw new NotImplementedException($"The RabbitMQ implementation does not use this abstraction."); + + /// + /// Asynchronously sends the specified message to the specified queue. + /// + /// + /// A unique textual path that identifies the queue. + /// + /// + /// The message to send. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is -or- is . + /// + /// + /// The specified queue does not exist. + /// + /// + /// The object is disposed. + /// + /// + /// The operation timed out. + /// + public Task SendToQueueAsync(IMessagingEntityPath path, PrimitiveMessage message) + { + var queueName = path.RejectIf().IsNull(nameof(path)).ToString(); + var exchangeName = $"{EntityPathExchangePrefix}{DelimitingCharacterForExchangePrefix}{queueName}"; + _ = message.RejectIf().IsNull(nameof(message)); + + return Task.Factory.StartNew(() => + { + try + { + var serializer = new DynamicSerializer(MessageBodySerializationFormat); + Channel.BasicPublish(exchangeName, queueName, false, null, new ReadOnlyMemory(serializer.Serialize(message))); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to send message. The specified queue, \"{path}\", does not exist or the RabbitMQ connection is unavailable.", exception); + } + }); + } + + /// + /// Asynchronously sends the specified message to the specified topic. + /// + /// + /// A unique textual path that identifies the topic. + /// + /// + /// The message to send. + /// + /// + /// A task representing the asynchronous operation. + /// + /// + /// is -or- is . + /// + /// + /// The specified topic does not exist. + /// + /// + /// The object is disposed. + /// + /// + /// The operation timed out. + /// + public Task SendToTopicAsync(IMessagingEntityPath path, PrimitiveMessage message) + { + var topicName = path.RejectIf().IsNull(nameof(path)).ToString(); + var exchangeName = $"{EntityPathExchangePrefix}{DelimitingCharacterForExchangePrefix}{topicName}"; + _ = message.RejectIf().IsNull(nameof(message)); + + return Task.Factory.StartNew(() => + { + try + { + var serializer = new DynamicSerializer(MessageBodySerializationFormat); + Channel.BasicPublish(exchangeName, null, false, null, new ReadOnlyMemory(serializer.Serialize(message))); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to send message. The specified topic, \"{path}\", does not exist or the RabbitMQ connection is unavailable.", exception); + } + }); + } + + /// + /// Returns a value indicating whether or not the specified subscription exists. + /// + /// + /// A unique textual path that identifies the subscription topic. + /// + /// + /// The unique name of the subscription. + /// + /// + /// if the subscription exists, otherwise . + /// + /// + /// is empty. + /// + /// + /// is -or- is + /// . + /// + /// + /// The object is disposed. + /// + public Boolean SubscriptionExists(IMessagingEntityPath path, String subscriptionName) + { + RejectIfDisposed(); + + if (TopicExists(path)) + { + try + { + DeclareAndBindSubscription(path, subscriptionName); + return true; + } + catch (ArgumentEmptyException) + { + throw; + } + catch (ArgumentNullException) + { + throw; + } + catch + { + return false; + } + } + + return false; + } + + /// + /// Returns a value indicating whether or not the specified topic exists. + /// + /// + /// A unique textual path that identifies the topic. + /// + /// + /// if the topic exists, otherwise . + /// + /// + /// is . + /// + /// + /// The object is disposed. + /// + public Boolean TopicExists(IMessagingEntityPath path) + { + RejectIfDisposed(); + + try + { + DeclareTopic(path); + return true; + } + catch (ArgumentNullException) + { + throw; + } + catch + { + return false; + } + } + + /// + /// Attempts to create a new queue. + /// + /// + /// A unique textual path that identifies the new queue. + /// + /// + /// if the queue was successfully created, otherwise . + /// + public Boolean TryCreateQueue(IMessagingEntityPath path) => TryCreateQueue(path, MessagingEntity.DefaultMessageLockExpirationThreshold); + + /// + /// Attempts to create a new queue. + /// + /// + /// A unique textual path that identifies the new queue. + /// + /// + /// The length of time that a locked message is held before abandoning the associated token and making the message available + /// for processing. The default value is three minutes. + /// + /// + /// if the queue was successfully created, otherwise . + /// + public Boolean TryCreateQueue(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold) => TryCreateQueue(path, messageLockExpirationThreshold, MessagingEntity.DefaultEnqueueTimeoutThreshold); + + /// + /// Attempts to create a new queue. + /// + /// + /// A unique textual path that identifies the new queue. + /// + /// + /// The length of time that a locked message is held before abandoning the associated token and making the message available + /// for processing. The default value is three minutes. + /// + /// + /// The maximum length of time to wait for a message to be enqueued before raising an exception. The default value is eight + /// seconds. + /// + /// + /// if the queue was successfully created, otherwise . + /// + public Boolean TryCreateQueue(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold, TimeSpan enqueueTimeoutThreshold) + { + if (IsDisposedOrDisposing || path is null || messageLockExpirationThreshold < MessagingEntity.MessageLockExpirationThresholdFloor || enqueueTimeoutThreshold < MessagingEntity.EnqueueTimeoutThresholdFloor) + { + return false; + } + + using (var controlToken = StateControl.Enter()) + { + if (IsDisposedOrDisposing) + { + return false; + } + + return QueueExists(path); + } + } + + /// + /// Attempts to create a new subscription. + /// + /// + /// A unique textual path that identifies the subscription topic. + /// + /// + /// The unique name of the subscription. + /// + /// + /// if the subscription was successfully created, otherwise . + /// + public Boolean TryCreateSubscription(IMessagingEntityPath path, String subscriptionName) + { + if (IsDisposedOrDisposing || path is null || subscriptionName is null) + { + return false; + } + + using (var controlToken = StateControl.Enter()) + { + if (IsDisposedOrDisposing) + { + return false; + } + + return SubscriptionExists(path, subscriptionName); + } + } + + /// + /// Attempts to create a new topic. + /// + /// + /// A unique textual path that identifies the new topic. + /// + /// + /// if the topic was successfully created, otherwise . + /// + public Boolean TryCreateTopic(IMessagingEntityPath path) => TryCreateTopic(path, MessagingEntity.DefaultMessageLockExpirationThreshold); + + /// + /// Attempts to create a new topic. + /// + /// + /// A unique textual path that identifies the new topic. + /// + /// + /// The length of time that a locked message is held before abandoning the associated token and making the message available + /// for processing. The default value is three minutes. + /// + /// + /// if the topic was successfully created, otherwise . + /// + public Boolean TryCreateTopic(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold) => TryCreateTopic(path, messageLockExpirationThreshold, MessagingEntity.DefaultEnqueueTimeoutThreshold); + + /// + /// Attempts to create a new topic. + /// + /// + /// A unique textual path that identifies the new topic. + /// + /// + /// The length of time that a locked message is held before abandoning the associated token and making the message available + /// for processing. The default value is three minutes. + /// + /// + /// The maximum length of time to wait for a message to be enqueued before raising an exception. The default value is eight + /// seconds. + /// + /// + /// if the topic was successfully created, otherwise . + /// + public Boolean TryCreateTopic(IMessagingEntityPath path, TimeSpan messageLockExpirationThreshold, TimeSpan enqueueTimeoutThreshold) + { + if (IsDisposedOrDisposing || path is null || messageLockExpirationThreshold < MessagingEntity.MessageLockExpirationThresholdFloor || enqueueTimeoutThreshold < MessagingEntity.EnqueueTimeoutThresholdFloor) + { + return false; + } + + using (var controlToken = StateControl.Enter()) + { + if (IsDisposedOrDisposing) + { + return false; + } + + return TopicExists(path); + } + } + + /// + /// Attempts to destroy the specified queue. + /// + /// + /// A unique textual path that identifies the queue. + /// + /// + /// if the queue was successfully destroyed, otherwise . + /// + public Boolean TryDestroyQueue(IMessagingEntityPath path) + { + if (IsDisposedOrDisposing || path is null) + { + return false; + } + + using (var controlToken = StateControl.Enter()) + { + if (IsDisposedOrDisposing) + { + return false; + } + + try + { + var queueName = path.ToString(); + var exchangeName = $"{EntityPathExchangePrefix}{DelimitingCharacterForExchangePrefix}{queueName}"; + Channel.QueueUnbind(queueName, exchangeName, queueName); + Channel.QueueDelete(queueName); + Channel.ExchangeDelete(exchangeName); + return true; + } + catch + { + return false; + } + } + } + + /// + /// Attempts to destroy the specified subscription. + /// + /// + /// A unique textual path that identifies the subscription topic. + /// + /// + /// The unique name of the subscription. + /// + /// ** + /// + /// if the subscription was successfully destroyed, otherwise . + /// + public Boolean TryDestroySubscription(IMessagingEntityPath path, String subscriptionName) + { + if (IsDisposedOrDisposing || path is null || subscriptionName.IsNullOrEmpty()) + { + return false; + } + + using (var controlToken = StateControl.Enter()) + { + if (IsDisposedOrDisposing) + { + return false; + } + + try + { + var topicName = path.ToString(); + var queueName = subscriptionName; + var exchangeName = $"{EntityPathExchangePrefix}{DelimitingCharacterForExchangePrefix}{topicName}"; + Channel.QueueUnbind(queueName, exchangeName, queueName); + Channel.QueueDelete(queueName); + return true; + } + catch + { + return false; + } + } + } + + /// + /// Attempts to destroy the specified topic. + /// + /// + /// A unique textual path that identifies the topic. + /// + /// + /// if the topic was successfully destroyed, otherwise . + /// + public Boolean TryDestroyTopic(IMessagingEntityPath path) + { + if (IsDisposedOrDisposing || path is null) + { + return false; + } + + using (var controlToken = StateControl.Enter()) + { + if (IsDisposedOrDisposing) + { + return false; + } + + try + { + var topicName = path.ToString(); + var exchangeName = $"{EntityPathExchangePrefix}{DelimitingCharacterForExchangePrefix}{topicName}"; + Channel.ExchangeDelete(exchangeName); + return true; + } + catch + { + return false; + } + } + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + protected override void Dispose(Boolean disposing) + { + try + { + if (disposing) + { + while (ConnectionCount > 0) + { + DestroyAllConnections(); + } + + Channel?.Dispose(); + SharedConnection?.Dispose(); + } + } + finally + { + base.Dispose(disposing); + } + } + + /// + /// Creates and hydrates a connection factory using the specified connection information. + /// + /// + /// The connection URI for the target RabbitMQ instance. + /// + /// + /// The resulting connection factory. + /// + /// + /// is not valid for AMQP connections. + /// + /// + /// is . + /// + [DebuggerHidden] + private static IRabbitMqConnectionFactory CreateConnectionFactory(Uri connectionUri) => new RabbitMqConnectionFactory() + { + Uri = connectionUri.RejectIf().IsNull(nameof(connectionUri)).OrIf(argument => argument.Scheme != AmqpUriScheme, nameof(connectionUri), "The specified connection URI is not valid for AMQP connections.") + }; + + /// + /// Creates and hydrates a connection factory using the specified connection information. + /// + /// + /// The name of the host to connect to. + /// + /// + /// The port number to connect to, or to use the default port number (5672). + /// + /// + /// The name of the virtual host to connect to, or to omit a virtual host. + /// + /// + /// The user name for the connection, or to use the default user name ("guest"). + /// + /// + /// The password for the connection, or to use the default password ("guest"). + /// + /// + /// The resulting connection factory. + /// + /// + /// is empty. + /// + /// + /// is . + /// + [DebuggerHidden] + private static IRabbitMqConnectionFactory CreateConnectionFactory(String hostName, Int32? portNumber, String virtualHost, String userName, String password) => new RabbitMqConnectionFactory() + { + HostName = hostName.RejectIf().IsNullOrEmpty(nameof(hostName)), + Password = password.IsNullOrEmpty() ? DefaultConnectionPassword : password, + Port = portNumber ?? DefaultConnectionPortNumber, + UserName = userName.IsNullOrEmpty() ? DefaultConnectionUserName : userName, + VirtualHost = virtualHost.IsNullOrEmpty() ? null : virtualHost + }; + + /// + /// Declares a direct exchange-queue pair and binds them using specified queue path as an idempotent operation. + /// + /// + /// A unique textual path that identifies the queue. + /// + /// + /// is . + /// + /// + /// An exception was raised while attempting to declare or bind the queue. + /// + [DebuggerHidden] + private void DeclareAndBindQueue(IMessagingEntityPath path) + { + var queueName = path.RejectIf().IsNull(nameof(path)).ToString(); + var exchangeName = $"{EntityPathExchangePrefix}{DelimitingCharacterForExchangePrefix}{queueName}"; + + try + { + Channel.ExchangeDeclare(exchangeName, ExchangeType.Direct, true, false); + Channel.QueueDeclare(queueName, true, false, false); + Channel.QueueBind(queueName, exchangeName, queueName); + } + catch (Exception exception) + { + throw new MessagingException("Failed to declare and bind the specified queue. See inner exception.", exception); + } + } + + /// + /// Declares a subscription queue and binds it to the appropriate exchange using specified path and subscription name as an + /// idempotent operation. + /// + /// + /// A unique textual path that identifies the topic. + /// + /// + /// The unique name of the subscription. + /// + /// + /// is empty. + /// + /// + /// is -or- is + /// . + /// + /// + /// An exception was raised while attempting to declare or bind the subscription. + /// + [DebuggerHidden] + private void DeclareAndBindSubscription(IMessagingEntityPath path, String subscriptionName) + { + var topicName = path.RejectIf().IsNull(nameof(path)).ToString(); + var queueName = subscriptionName.RejectIf().IsNullOrEmpty(nameof(path)); + var exchangeName = $"{EntityPathExchangePrefix}{DelimitingCharacterForExchangePrefix}{topicName}"; + + try + { + Channel.QueueDeclare(queueName, true, true, true); + Channel.QueueBind(queueName, exchangeName, queueName); + } + catch (Exception exception) + { + throw new MessagingException("Failed to declare and bind the specified subscription. See inner exception.", exception); + } + } + + /// + /// Declares a fanout exchange for the specified topic path as an idempotent operation. + /// + /// + /// A unique textual path that identifies the topic. + /// + /// + /// is . + /// + /// + /// An exception was raised while attempting to declare the exchange. + /// + [DebuggerHidden] + private void DeclareTopic(IMessagingEntityPath path) + { + var topicName = path.RejectIf().IsNull(nameof(path)).ToString(); + var exchangeName = $"{EntityPathExchangePrefix}{DelimitingCharacterForExchangePrefix}{topicName}"; + + try + { + Channel.ExchangeDeclare(exchangeName, ExchangeType.Fanout, true, false); + } + catch (Exception exception) + { + throw new MessagingException("Failed to declare the specified topic. See inner exception.", exception); + } + } + + /// + /// Closes and disposes of all connections to the current . + /// + [DebuggerHidden] + private void DestroyAllConnections() + { + var destroyConnectionTasks = new List(); + + while (ConnectionDictionary.Any()) + { + var connectionIdentifier = ConnectionDictionary.Keys.FirstOrDefault(); + + if (connectionIdentifier == default) + { + continue; + } + else if (ConnectionDictionary.TryRemove(connectionIdentifier, out var connection)) + { + destroyConnectionTasks.Add(Task.Factory.StartNew(() => + { + connection.Dispose(); + })); + } + } + + Task.WaitAll(destroyConnectionTasks.ToArray()); + } + + /// + /// Gets the number of active connections to the current . + /// + public Int32 ConnectionCount => Connections.Count(); + + /// + /// Gets a collection of active connections to the current . + /// + public IEnumerable Connections => ConnectionDictionary.Values; + + /// + /// Gets the format that is used to serialize enqueued message bodies. + /// + public SerializationFormat MessageBodySerializationFormat + { + get; + } + + /// + /// Represents the delimiting character that follows the exchange prefix token. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal const Char DelimitingCharacterForExchangePrefix = '-'; + + /// + /// Represents the entity path prefix for exchanges. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal const String EntityPathExchangePrefix = "Exc"; + + /// + /// Represents the entity path prefix for queues. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal const String EntityPathQueuePrefix = "Que"; + + /// + /// Represents the entity path prefix for topics. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal const String EntityPathTopicPrefix = "Top"; + + /// + /// Represents the name prefix for subscriptions. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal const String SubscriptionNamePrefix = "Sub"; + + /// + /// Represents the AMQP model that is used to manage the transport. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal readonly IRabbitMqChannel Channel; + + /// + /// Represents the valid URI scheme for AMQP connections. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String AmqpUriScheme = "amqp"; + + /// + /// Represents the default host name for new connections. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DefaultConnectionHostName = "localhost"; + + /// + /// Represents the default password for new connections. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DefaultConnectionPassword = "guest"; + + /// + /// Represents the default port number for new connections. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Int32 DefaultConnectionPortNumber = 5672; + + /// + /// Represents the default user name for new connections. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DefaultConnectionUserName = "guest"; + + /// + /// Represents a collection of active connections to the current , which are keyed by + /// identifier. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly ConcurrentDictionary ConnectionDictionary = new ConcurrentDictionary(); + + /// + /// Represents the shared connection to the target RabbitMQ instance through which all other connections communicate. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly IRabbitMqConnection SharedConnection; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.RabbitMq/TransportPrimitives/RabbitMqMessageTransportConnection.cs b/src/RapidField.SolidInstruments.Messaging.RabbitMq/TransportPrimitives/RabbitMqMessageTransportConnection.cs new file mode 100644 index 00000000..ba1e4fa8 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.RabbitMq/TransportPrimitives/RabbitMqMessageTransportConnection.cs @@ -0,0 +1,300 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RabbitMQ.Client; +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Core.Extensions; +using RapidField.SolidInstruments.Messaging.TransportPrimitives; +using RapidField.SolidInstruments.Serialization; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.Serialization; +using IRabbitMqChannel = RabbitMQ.Client.IModel; +using IRabbitMqConsumer = RabbitMQ.Client.IBasicConsumer; +using RabbitMqConsumer = RabbitMQ.Client.Events.EventingBasicConsumer; + +namespace RapidField.SolidInstruments.Messaging.RabbitMq.TransportPrimitives +{ + /// + /// Represents a client connection to a . + /// + internal sealed class RabbitMqMessageTransportConnection : Instrument, IMessageTransportConnection + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The associated transport. + /// + /// + /// is . + /// + [DebuggerHidden] + internal RabbitMqMessageTransportConnection(RabbitMqMessageTransport transport) + : base() + { + Consumers = new List(); + Identifier = Guid.NewGuid(); + State = MessageTransportConnectionState.Open; + TransportReference = transport.RejectIf().IsNull(nameof(transport)).TargetArgument; + } + + /// + /// Closes the current as an idempotent operation. + /// + /// + /// An exception was raised while closing the transport connection. + /// + public void Close() + { + if (State == MessageTransportConnectionState.Open) + { + State = MessageTransportConnectionState.Closed; + + try + { + if (TransportReference.Connections.Any(connection => connection.Identifier == Identifier)) + { + TransportReference.CloseConnection(this); + } + } + catch (Exception exception) + { + throw new MessagingException("An exception was raised while closing a transport connection. See inner exception.", exception); + } + } + } + + /// + /// Registers the specified message handler for the specified queue. + /// + /// + /// A unique textual path that identifies the queue. + /// + /// + /// An action to perform upon message receipt. + /// + /// + /// is -or- is + /// . + /// + /// + /// The specified queue does not exist. + /// + /// + /// The object is disposed. + /// + public void RegisterQueueHandler(IMessagingEntityPath queuePath, Action handleMessageAction) + { + RejectIfDisposed(); + + if (Transport.QueueExists(queuePath)) + { + _ = handleMessageAction.RejectIf().IsNull(nameof(handleMessageAction)); + var queueName = queuePath.ToString(); + var consumer = new RabbitMqConsumer(Channel); + consumer.Received += (model, eventArguments) => + { + try + { + var messageBytes = eventArguments.Body.ToArray(); + var serializer = new DynamicSerializer(Transport.MessageBodySerializationFormat); + var message = serializer.Deserialize(messageBytes); + + try + { + handleMessageAction(message); + } + catch (Exception exception) + { + throw new MessageListeningException($"An exception was raised while processing a message from queue \"{queueName}\". Message identifier: {message?.Identifier.ToSerializedString() ?? "unknown"}.", exception); + } + } + catch (MessageListeningException) + { + throw; + } + catch (SerializationException exception) + { + throw new MessageListeningException($"Failed to deserialize a message from queue \"{queueName}\". Expected format: {Transport.MessageBodySerializationFormat}.", exception); + } + }; + + try + { + Channel.BasicConsume(queueName, false, consumer); + Consumers.Add(consumer); + } + catch (Exception exception) + { + throw new MessageListeningException($"An exception was raised while attempting to register a consumer for queue \"{queueName}\".", exception); + } + + return; + } + + throw new InvalidOperationException($"Failed to register queue handler. The specified queue, \"{queuePath}\", does not exist."); + } + + /// + /// Registers the specified message handler for the specified topic subscription. + /// + /// + /// A unique textual path that identifies the topic. + /// + /// + /// The unique name of the subscription. + /// + /// + /// An action to perform upon message receipt. + /// + /// + /// is empty. + /// + /// + /// is -or- is + /// -or- is . + /// + /// + /// The specified subscription does not exist. + /// + /// + /// The object is disposed. + /// + public void RegisterSubscriptionHandler(IMessagingEntityPath topicPath, String subscriptionName, Action handleMessageAction) + { + RejectIfDisposed(); + + if (Transport.SubscriptionExists(topicPath, subscriptionName)) + { + _ = handleMessageAction.RejectIf().IsNull(nameof(handleMessageAction)); + var topicName = topicPath.ToString(); + var consumer = new RabbitMqConsumer(Channel); + consumer.Received += (model, eventArguments) => + { + try + { + var messageBytes = eventArguments.Body.ToArray(); + var serializer = new DynamicSerializer(Transport.MessageBodySerializationFormat); + var message = serializer.Deserialize(messageBytes); + + try + { + handleMessageAction(message); + } + catch (Exception exception) + { + throw new MessageListeningException($"An exception was raised while processing a message from topic \"{topicName}\" for subscription \"{subscriptionName}\". Message identifier: {message?.Identifier.ToSerializedString() ?? "unknown"}.", exception); + } + } + catch (MessageListeningException) + { + throw; + } + catch (SerializationException exception) + { + throw new MessageListeningException($"Failed to deserialize a message from topic \"{topicName}\" for subscription \"{subscriptionName}\". Expected format: {Transport.MessageBodySerializationFormat}.", exception); + } + }; + + try + { + Channel.BasicConsume(subscriptionName, false, consumer); + Consumers.Add(consumer); + } + catch (Exception exception) + { + throw new MessageListeningException($"An exception was raised while attempting to register a consumer for topic \"{topicName}\" with subscription name \"{subscriptionName}\".", exception); + } + + return; + } + + throw new InvalidOperationException($"Failed to register subscription handler. The specified subscription, \"{subscriptionName}\", does not exist."); + } + + /// + /// Releases all resources consumed by the current . + /// + /// + /// A value indicating whether or not managed resources should be released. + /// + /// + /// An exception was raised while closing the transport connection. + /// + protected override void Dispose(Boolean disposing) + { + try + { + if (disposing) + { + Close(); + } + } + finally + { + base.Dispose(disposing); + } + } + + /// + /// Gets a value that uniquely identifies the current . + /// + public Guid Identifier + { + get; + } + + /// + /// Gets the state of the current . + /// + public MessageTransportConnectionState State + { + get; + private set; + } + + /// + /// Gets the associated . + /// + /// + /// The connection is closed. + /// + public IMessageTransport Transport => State == MessageTransportConnectionState.Open ? TransportReference : throw new MessageTransportConnectionClosedException($"Connection {Identifier.ToSerializedString()} is closed."); + + /// + /// Gets the AMQP model that is used to manage the transport. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private IRabbitMqChannel Channel => TransportReference?.Channel; + + /// + /// Represents the maximum number of messages to dequeue from each entity during a single polling permutation. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Int32 MessageReceiptBatchSize = 34; + + /// + /// Represents the interval, in milliseconds, at which is polled for receive operations. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Int32 PollingIntervalInMilliseconds = 987; + + /// + /// Represents a collection of consumers that are registered with the current + /// . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly IList Consumers; + + /// + /// Represents the associated . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly RabbitMqMessageTransport TransportReference; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/MessageListeningFacade.cs b/src/RapidField.SolidInstruments.Messaging/MessageListeningFacade.cs index 0ec3d309..07069db1 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessageListeningFacade.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessageListeningFacade.cs @@ -5,6 +5,7 @@ using RapidField.SolidInstruments.Core; using RapidField.SolidInstruments.Core.ArgumentValidation; using RapidField.SolidInstruments.Core.Concurrency; +using RapidField.SolidInstruments.Core.Extensions; using RapidField.SolidInstruments.EventAuthoring; using RapidField.SolidInstruments.Mathematics.Extensions; using RapidField.SolidInstruments.Mathematics.Sequences; @@ -339,6 +340,33 @@ public void RegisterTopicMessageHandler(Action messageHandle public void RegisterTopicMessageHandler(Action messageHandler, IEnumerable pathLabels) where TMessage : class, IMessage => RegisterMessageHandler(messageHandler, pathLabels, MessagingEntityType.Topic); + /// + /// Appends the dead letter queue label to the specified path labels. + /// + /// + /// An ordered collection of as many as three non-null, non-empty alphanumeric textual labels to append to the path, or + /// if path labels are omitted. + /// + /// + /// The specified path labels with the dead letter label appended. + /// + [DebuggerHidden] + internal static IEnumerable AppendDeadLetterQueueLabel(IEnumerable pathLabels) + { + if (pathLabels.IsNullOrEmpty()) + { + return new String[] { DeadLetterQueueLabel }; + } + + return (pathLabels.Count()) switch + { + 1 => new String[] { pathLabels.ElementAt(0), DeadLetterQueueLabel }, + 2 => new String[] { pathLabels.ElementAt(0), pathLabels.ElementAt(1), DeadLetterQueueLabel }, + 3 => new String[] { pathLabels.ElementAt(0), pathLabels.ElementAt(1), $"{pathLabels.ElementAt(1)}{MessagingEntityPath.DelimitingCharacterForLabelTokenSubParts}{DeadLetterQueueLabel}" }, + _ => throw new ArgumentException("The specified path label collection contains more than three elements.", nameof(pathLabels)), + }; + } + /// /// Asynchronously performs the specified action for the specified message and, upon failure, applies the execution policy. /// @@ -706,6 +734,12 @@ public IEnumerable ListenedMessageTypes [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal static readonly MessageListeningFailurePolicy DefaultFailurePolicy = MessageListeningFailurePolicy.Default; + /// + /// Represents a label that is appended to dead letter queues. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const String DeadLetterQueueLabel = "DeadLetter"; + /// /// Represents the type. /// diff --git a/src/RapidField.SolidInstruments.Messaging/MessagingEntityPath.cs b/src/RapidField.SolidInstruments.Messaging/MessagingEntityPath.cs index b4da416c..c9a61b5a 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessagingEntityPath.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessagingEntityPath.cs @@ -789,7 +789,7 @@ public String Prefix /// Gets a regular expression that is used to validate label tokens. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private static String RegularExpressionPatternForLabelToken => $"[a-zA-Z0-9]{{1,{MaximumCharacterLengthForLabelToken}}}"; + private static String RegularExpressionPatternForLabelToken => $"[a-zA-Z0-9{DelimitingCharacterForLabelTokenSubParts}]{{1,{MaximumCharacterLengthForLabelToken}}}"; /// /// Gets a regular expression that is used to validate the second label. @@ -845,6 +845,12 @@ public String Prefix [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal const Char DelimitingCharacterForLabelToken = '_'; + /// + /// Represents the delimiting character that is permitted for label token sub-parts. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal const Char DelimitingCharacterForLabelTokenSubParts = '+'; + /// /// Represents the delimiting character that follows the prefix token. /// diff --git a/src/RapidField.SolidInstruments.Messaging/Service/MessagingServiceExecutor.cs b/src/RapidField.SolidInstruments.Messaging/Service/MessagingServiceExecutor.cs index 83b292f7..0cfd7712 100644 --- a/src/RapidField.SolidInstruments.Messaging/Service/MessagingServiceExecutor.cs +++ b/src/RapidField.SolidInstruments.Messaging/Service/MessagingServiceExecutor.cs @@ -3,8 +3,11 @@ // ================================================================================================================================= using Microsoft.Extensions.Configuration; +using RapidField.SolidInstruments.Command; using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.EventAuthoring; using RapidField.SolidInstruments.InversionOfControl; +using RapidField.SolidInstruments.Messaging.EventMessages; using RapidField.SolidInstruments.Service; using System; using System.Collections.Generic; @@ -68,11 +71,39 @@ protected MessagingServiceExecutor(String serviceName) /// is . /// protected MessagingServiceExecutor(String serviceName, Boolean runsContinuously) + : this(serviceName, runsContinuously, DefaultPublishesStartAndStopEventsValue) + { + return; + } + + /// + /// Initializes a new instance of the + /// class. + /// + /// + /// The name of the service. + /// + /// + /// A value indicating whether or not the service should schedule heartbeat messages and stay running indefinitely. The + /// default value is . + /// + /// + /// A value indicating whether or not the service should publish event messages when the application starts and stops. The + /// default value is . + /// + /// + /// is empty. + /// + /// + /// is . + /// + protected MessagingServiceExecutor(String serviceName, Boolean runsContinuously, Boolean publishesStartAndStopEvents) : base(serviceName) { LazyHeartbeatSchedule = new Lazy(CreateHeartbeatSchedule, LazyThreadSafetyMode.ExecutionAndPublication); LazyHeartbeatTimers = new Lazy>(() => new List(), LazyThreadSafetyMode.ExecutionAndPublication); LazyListeningProfile = new Lazy(CreateListeningProfile, LazyThreadSafetyMode.ExecutionAndPublication); + PublishesStartAndStopEvents = publishesStartAndStopEvents; RunsContinuously = runsContinuously; } @@ -148,28 +179,55 @@ protected sealed override void Execute(IDependencyScope dependencyScope, IConfig { try { - using (var childScope = dependencyScope.CreateChildScope()) - { - OnExecutionStarting(childScope, applicationConfiguration, executionLifetime); - } + AddSubscriptions(ListeningProfile, ApplicationConfiguration); + Thread.Sleep(ExecutionStartDelay); try { - AddSubscriptions(ListeningProfile, ApplicationConfiguration); + using (var childScope = dependencyScope.CreateChildScope()) + { + OnExecutionStarting(childScope, applicationConfiguration, executionLifetime); + + if (PublishesStartAndStopEvents) + { + PublishApplicationStartedEventMessage(childScope); + } + } - if (RunsContinuously) + try { - StartHeartbeats(); - executionLifetime.KeepAlive(); + if (RunsContinuously) + { + Console.CancelKeyPress += (sender, eventArguments) => + { + executionLifetime.End(); + eventArguments.Cancel = true; + }; + + try + { + StartHeartbeats(); + executionLifetime.KeepAlive(); + } + finally + { + StopHeartbeats(); + } + } + } + finally + { + if (PublishesStartAndStopEvents) + { + using (var childScope = dependencyScope.CreateChildScope()) + { + PublishApplicationStoppedEventMessage(childScope); + } + } } } finally { - if (RunsContinuously) - { - StopHeartbeats(); - } - using (var childScope = dependencyScope.CreateChildScope()) { OnExecutionStopping(childScope, applicationConfiguration); @@ -179,6 +237,7 @@ protected sealed override void Execute(IDependencyScope dependencyScope, IConfig finally { base.Execute(dependencyScope, applicationConfiguration, executionLifetime); + Thread.Sleep(ExecutionStopDelay); } } @@ -241,6 +300,42 @@ private IMessageListeningProfile CreateListeningProfile() return new MessageListeningProfile(dependencyScope); } + /// + /// Publishes an event to notify the system that the application has started. + /// + /// + /// A scope that is used to resolve service dependencies. + /// + /// + /// An exception was raised while publishing the event message. + /// + [DebuggerHidden] + private void PublishApplicationStartedEventMessage(IDependencyScope dependencyScope) + { + var mediator = dependencyScope.Resolve(); + var applicationStartedEvent = new ApplicationStartedEvent(ServiceName); + var applicationStartedEventMessage = new ApplicationStartedEventMessage(applicationStartedEvent); + mediator.Process(applicationStartedEventMessage); + } + + /// + /// Publishes an event to notify the system that the application has stopped. + /// + /// + /// A scope that is used to resolve service dependencies. + /// + /// + /// An exception was raised while publishing the event message. + /// + [DebuggerHidden] + private void PublishApplicationStoppedEventMessage(IDependencyScope dependencyScope) + { + var mediator = dependencyScope.Resolve(); + var applicationStoppedEvent = new ApplicationStoppedEvent(ServiceName); + var applicationStoppedEventMessage = new ApplicationStoppedEventMessage(applicationStoppedEvent); + mediator.Process(applicationStoppedEventMessage); + } + /// /// Creates a new timer that transmits messages according to the supplied specifications. /// @@ -343,6 +438,13 @@ private Task TransmitHeartbeatMessageAsync(IHeartbeatScheduleItem scheduleItem) [DebuggerBrowsable(DebuggerBrowsableState.Never)] private IMessageListeningProfile ListeningProfile => LazyListeningProfile.Value; + /// + /// Represents a default value indicating whether or not the service should publish event messages when the application + /// starts and stops. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private const Boolean DefaultPublishesStartAndStopEventsValue = false; + /// /// Represents a default value indicating whether or not the service should schedule heartbeat messages and stay running /// indefinitely. The @@ -350,6 +452,18 @@ private Task TransmitHeartbeatMessageAsync(IHeartbeatScheduleItem scheduleItem) [DebuggerBrowsable(DebuggerBrowsableState.Never)] private const Boolean DefaultRunsContinuouslyValue = true; + /// + /// Represents the length of time to delay service execution after adding subscriptions. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static readonly TimeSpan ExecutionStartDelay = TimeSpan.FromSeconds(1); + + /// + /// Represents the length of time to wait after execution has finished. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static readonly TimeSpan ExecutionStopDelay = TimeSpan.FromSeconds(3); + /// /// Represents the lazily-initialized heartbeat message schedule for the service. /// @@ -368,9 +482,16 @@ private Task TransmitHeartbeatMessageAsync(IHeartbeatScheduleItem scheduleItem) [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Lazy LazyListeningProfile; + /// + /// Represents value indicating whether or not the service should publish event messages when the application starts and + /// stops. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly Boolean PublishesStartAndStopEvents; + /// /// Represents a value indicating whether or not the service should schedule heartbeat messages and stay running - /// indefinitely. The + /// indefinitely. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Boolean RunsContinuously; diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransport.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransport.cs index 2dddfa9e..cad2866f 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransport.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/IMessageTransport.cs @@ -733,37 +733,5 @@ public SerializationFormat MessageBodySerializationFormat { get; } - - /// - /// Gets the number of queues within the current . - /// - public Int32 QueueCount - { - get; - } - - /// - /// Gets a collection of available queue paths for the current . - /// - public IEnumerable QueuePaths - { - get; - } - - /// - /// Gets the number of topics within the current . - /// - public Int32 TopicCount - { - get; - } - - /// - /// Gets a collection of available topic paths for the current . - /// - public IEnumerable TopicPaths - { - get; - } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageLockToken.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageLockToken.cs index 5721fbfb..c5e744cd 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageLockToken.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageLockToken.cs @@ -56,7 +56,31 @@ internal MessageLockToken(Guid identifier, Guid messageIdentifier) /// [DebuggerHidden] internal MessageLockToken(Guid identifier, Guid messageIdentifier, DateTime expirationDateTime) + : this(default, identifier, messageIdentifier, expirationDateTime) { + return; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A transport-issued tracking identifier, or if the implementation does not utilize delivery + /// tags. + /// + /// + /// A unique identifier for the lock token. + /// + /// + /// The unique identifier for the associated, locked message. + /// + /// + /// The date and time of expiration for the lock, after which the message will become available for processing. + /// + [DebuggerHidden] + internal MessageLockToken(UInt64 deliveryTag, Guid identifier, Guid messageIdentifier, DateTime expirationDateTime) + { + DeliveryTag = deliveryTag; ExpirationDateTime = expirationDateTime; Identifier = identifier; MessageIdentifier = messageIdentifier; @@ -235,6 +259,10 @@ public Boolean Equals(MessageLockToken other) { return false; } + else if (DeliveryTag != other.DeliveryTag) + { + return false; + } else if (MessageIdentifier != other.MessageIdentifier) { return false; @@ -262,6 +290,7 @@ public Byte[] ToByteArray() var bytes = new List(); bytes.AddRange(ExpirationDateTime.ToByteArray()); bytes.AddRange(Identifier.ToByteArray()); + bytes.AddRange(DeliveryTag.ToByteArray()); bytes.AddRange(MessageIdentifier.ToByteArray()); return bytes.ToArray(); } @@ -274,6 +303,13 @@ public Byte[] ToByteArray() /// public sealed override String ToString() => $"{{ {nameof(Identifier)}: {Identifier.ToSerializedString()}, {nameof(ExpirationDateTime)}: {ExpirationDateTime.ToSerializedString()} }}"; + /// + /// Represents a transport-issued tracking identifier, or if the implementation does not utilize + /// delivery tags. + /// + [DataMember] + internal readonly UInt64 DeliveryTag; + /// /// Represents the date and time of expiration for the lock, after which the message will become available for processing. /// diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransport.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransport.cs index 888d66b0..d08bab0d 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransport.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransport.cs @@ -213,7 +213,7 @@ public Task ConveySuccessToSubscriptionAsync(MessageLockToken lockToken, IMessag return topic.ConveySuccessAsync(lockToken); } - throw new InvalidOperationException($"Failed to convey failure. The specified topic, \"{path}\", does not exist."); + throw new InvalidOperationException($"Failed to convey success. The specified topic, \"{path}\", does not exist."); } /// diff --git a/src/RapidField.SolidInstruments.Service/ServiceExecutor.cs b/src/RapidField.SolidInstruments.Service/ServiceExecutor.cs index e24a1527..0e65983b 100644 --- a/src/RapidField.SolidInstruments.Service/ServiceExecutor.cs +++ b/src/RapidField.SolidInstruments.Service/ServiceExecutor.cs @@ -68,23 +68,58 @@ public void Execute() { try { - using (var executionLifetime = new ServiceExecutionLifetime()) + var productName = ProductName?.Trim(); + var serviceName = ServiceName?.Trim(); + var copyrightNotice = CopyrightNotice?.Trim(); + + if (productName.IsNullOrEmpty() == false) + { + Console.WriteLine(productName); + } + + if (serviceName.IsNullOrEmpty() == false) { - ExecutionLifetime = executionLifetime; + Console.WriteLine(serviceName); + } - using (var dependencyScope = CreateDependencyScope()) + if (copyrightNotice.IsNullOrEmpty() == false) + { + Console.WriteLine(copyrightNotice); + } + + Console.WriteLine($"{Environment.NewLine}Service execution starting."); + + try + { + using (var executionLifetime = new ServiceExecutionLifetime()) { - Execute(dependencyScope, ApplicationConfiguration, executionLifetime); + ExecutionLifetime = executionLifetime; + + using (var dependencyScope = CreateDependencyScope()) + { + Execute(dependencyScope, ApplicationConfiguration, executionLifetime); + } } } + catch (ServiceExectuionException) + { + throw; + } + catch (Exception exception) + { + throw new ServiceExectuionException($"An exception was raised during execution of the service: \"{ServiceName}\". See inner exception.", exception); + } } - catch (ServiceExectuionException) + catch (ServiceExectuionException exception) { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"{exception.Message} : {exception.StackTrace}{Environment.NewLine}"); + Console.ResetColor(); throw; } - catch (Exception exception) + finally { - throw new ServiceExectuionException($"An exception was raised during execution of the service: \"{ServiceName}\". See inner exception.", exception); + Console.WriteLine("Service execution finished."); } } @@ -217,6 +252,18 @@ internal IServiceExecutionLifetime ExecutionLifetime /// protected IConfiguration ApplicationConfiguration => LazyApplicationConfiguration.Value; + /// + /// When overridden by a derived class, gets a copyright notice which is written to the console at the start of service + /// execution. + /// + protected virtual String CopyrightNotice => null; + + /// + /// When overridden by a derived class, gets a product name associated with the service which is written to the console at + /// the start of service execution. + /// + protected virtual String ProductName => null; + /// /// Gets a utility that disposes of the object references that are managed by the current /// . From 3d6badc7aa9f5efc517b176d9ae7b28dbd3f6b86 Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Fri, 24 Jul 2020 03:14:51 -0500 Subject: [PATCH 48/55] Introduce concrete IoC integrations across the framework. --- LICENSE.txt | 2 +- RapidField.SolidInstruments.sln | 86 +++++- doc/docfx.json | 5 +- ....SolidInstruments.Example.Contracts.csproj | 4 +- .../ExampleDatabaseModelDependencyModule.cs | 15 +- ...idInstruments.Example.DatabaseModel.csproj | 5 +- ...eld.SolidInstruments.Example.Domain.csproj | 4 +- .../ApplicationDependencyModule.cs | 3 +- .../ApplicationDependencyModule.cs | 3 +- ...dField.SolidInstruments.Collections.csproj | 4 +- .../AssemblyAttributes.cs | 7 + .../Extensions/ContainerBuilderExtensions.cs | 31 ++ .../README.md | 57 ++++ ...ld.SolidInstruments.Command.Autofac.csproj | 50 ++++ .../AssemblyAttributes.cs | 7 + .../IServiceCollectionExtensions.cs | 34 +++ .../README.md | 57 ++++ ...lidInstruments.Command.DotNetNative.csproj | 50 ++++ ...RapidField.SolidInstruments.Command.csproj | 4 +- .../RapidField.SolidInstruments.Core.csproj | 4 +- ...Field.SolidInstruments.Cryptography.csproj | 6 +- .../AssemblyAttributes.cs | 7 + .../Extensions/ContainerBuilderExtensions.cs | 51 ++++ .../README.md | 57 ++++ ...SolidInstruments.DataAccess.Autofac.csproj | 51 ++++ .../AssemblyAttributes.cs | 7 + .../IServiceCollectionExtensions.cs | 58 ++++ .../README.md | 57 ++++ ...Instruments.DataAccess.DotNetNative.csproj | 51 ++++ ...truments.DataAccess.EntityFramework.csproj | 6 +- ...idField.SolidInstruments.DataAccess.csproj | 4 +- .../AssemblyAttributes.cs | 7 + .../Extensions/ContainerBuilderExtensions.cs | 31 ++ .../README.md | 57 ++++ ...dInstruments.EventAuthoring.Autofac.csproj | 51 ++++ .../AssemblyAttributes.cs | 7 + .../IServiceCollectionExtensions.cs | 35 +++ .../README.md | 57 ++++ ...ruments.EventAuthoring.DotNetNative.csproj | 51 ++++ .../EventHandler.cs | 5 +- .../Extensions/EventCategoryExtensions.cs | 2 +- .../Extensions/EventVerbosityExtensions.cs | 2 +- .../TransactionEventOutcomeExtensions.cs | 2 +- .../UserActionEventOutcomeExtensions.cs | 2 +- .../IEventHandler.cs | 19 ++ ...eld.SolidInstruments.EventAuthoring.csproj | 6 +- .../Extensions/ContainerBuilderExtensions.cs | 26 ++ ...truments.InversionOfControl.Autofac.csproj | 4 +- .../IServiceCollectionExtensions.cs | 18 +- ...nts.InversionOfControl.DotNetNative.csproj | 4 +- ...SolidInstruments.InversionOfControl.csproj | 4 +- ...dField.SolidInstruments.Mathematics.csproj | 4 +- .../AssemblyAttributes.cs | 7 + .../README.md | 57 ++++ ...idInstruments.Messaging.Autofac.Asb.csproj | 52 ++++ .../AssemblyAttributes.cs | 7 + .../README.md | 57 ++++ ...idInstruments.Messaging.Autofac.Rmq.csproj | 52 ++++ .../AssemblyAttributes.cs | 7 + .../Extensions/ContainerBuilderExtensions.cs | 239 +++++++++++++++ .../README.md | 57 ++++ ....SolidInstruments.Messaging.Autofac.csproj | 53 ++++ ...struments.Messaging.AzureServiceBus.csproj | 4 +- .../AssemblyAttributes.cs | 7 + .../README.md | 57 ++++ ...truments.Messaging.DotNetNative.Asb.csproj | 52 ++++ .../AssemblyAttributes.cs | 7 + .../README.md | 57 ++++ ...truments.Messaging.DotNetNative.Rmq.csproj | 52 ++++ .../AssemblyAttributes.cs | 7 + .../IServiceCollectionExtensions.cs | 271 ++++++++++++++++++ .../README.md | 57 ++++ ...dInstruments.Messaging.DotNetNative.csproj | 53 ++++ ...SolidInstruments.Messaging.InMemory.csproj | 4 +- ...SolidInstruments.Messaging.RabbitMq.csproj | 4 +- .../AssemblyAttributes.cs | 2 + ...pidField.SolidInstruments.Messaging.csproj | 4 +- ....SolidInstruments.ObjectComposition.csproj | 4 +- ...ield.SolidInstruments.Serialization.csproj | 4 +- ...RapidField.SolidInstruments.Service.csproj | 4 +- ...d.SolidInstruments.SignalProcessing.csproj | 4 +- ...Field.SolidInstruments.TextEncoding.csproj | 4 +- .../SingleThreadSpinLockControlTests.cs | 2 +- ...uments.Messaging.InMemory.UnitTests.csproj | 1 + .../SimulatedDependencyModule.cs | 136 ++++----- 85 files changed, 2407 insertions(+), 159 deletions(-) create mode 100644 src/RapidField.SolidInstruments.Command.Autofac/AssemblyAttributes.cs create mode 100644 src/RapidField.SolidInstruments.Command.Autofac/Extensions/ContainerBuilderExtensions.cs create mode 100644 src/RapidField.SolidInstruments.Command.Autofac/README.md create mode 100644 src/RapidField.SolidInstruments.Command.Autofac/RapidField.SolidInstruments.Command.Autofac.csproj create mode 100644 src/RapidField.SolidInstruments.Command.DotNetNative/AssemblyAttributes.cs create mode 100644 src/RapidField.SolidInstruments.Command.DotNetNative/Extensions/IServiceCollectionExtensions.cs create mode 100644 src/RapidField.SolidInstruments.Command.DotNetNative/README.md create mode 100644 src/RapidField.SolidInstruments.Command.DotNetNative/RapidField.SolidInstruments.Command.DotNetNative.csproj create mode 100644 src/RapidField.SolidInstruments.DataAccess.Autofac/AssemblyAttributes.cs create mode 100644 src/RapidField.SolidInstruments.DataAccess.Autofac/Extensions/ContainerBuilderExtensions.cs create mode 100644 src/RapidField.SolidInstruments.DataAccess.Autofac/README.md create mode 100644 src/RapidField.SolidInstruments.DataAccess.Autofac/RapidField.SolidInstruments.DataAccess.Autofac.csproj create mode 100644 src/RapidField.SolidInstruments.DataAccess.DotNetNative/AssemblyAttributes.cs create mode 100644 src/RapidField.SolidInstruments.DataAccess.DotNetNative/Extensions/IServiceCollectionExtensions.cs create mode 100644 src/RapidField.SolidInstruments.DataAccess.DotNetNative/README.md create mode 100644 src/RapidField.SolidInstruments.DataAccess.DotNetNative/RapidField.SolidInstruments.DataAccess.DotNetNative.csproj create mode 100644 src/RapidField.SolidInstruments.EventAuthoring.Autofac/AssemblyAttributes.cs create mode 100644 src/RapidField.SolidInstruments.EventAuthoring.Autofac/Extensions/ContainerBuilderExtensions.cs create mode 100644 src/RapidField.SolidInstruments.EventAuthoring.Autofac/README.md create mode 100644 src/RapidField.SolidInstruments.EventAuthoring.Autofac/RapidField.SolidInstruments.EventAuthoring.Autofac.csproj create mode 100644 src/RapidField.SolidInstruments.EventAuthoring.DotNetNative/AssemblyAttributes.cs create mode 100644 src/RapidField.SolidInstruments.EventAuthoring.DotNetNative/Extensions/IServiceCollectionExtensions.cs create mode 100644 src/RapidField.SolidInstruments.EventAuthoring.DotNetNative/README.md create mode 100644 src/RapidField.SolidInstruments.EventAuthoring.DotNetNative/RapidField.SolidInstruments.EventAuthoring.DotNetNative.csproj create mode 100644 src/RapidField.SolidInstruments.EventAuthoring/IEventHandler.cs create mode 100644 src/RapidField.SolidInstruments.InversionOfControl.Autofac/Extensions/ContainerBuilderExtensions.cs create mode 100644 src/RapidField.SolidInstruments.Messaging.Autofac.Asb/AssemblyAttributes.cs create mode 100644 src/RapidField.SolidInstruments.Messaging.Autofac.Asb/README.md create mode 100644 src/RapidField.SolidInstruments.Messaging.Autofac.Asb/RapidField.SolidInstruments.Messaging.Autofac.Asb.csproj create mode 100644 src/RapidField.SolidInstruments.Messaging.Autofac.Rmq/AssemblyAttributes.cs create mode 100644 src/RapidField.SolidInstruments.Messaging.Autofac.Rmq/README.md create mode 100644 src/RapidField.SolidInstruments.Messaging.Autofac.Rmq/RapidField.SolidInstruments.Messaging.Autofac.Rmq.csproj create mode 100644 src/RapidField.SolidInstruments.Messaging.Autofac/AssemblyAttributes.cs create mode 100644 src/RapidField.SolidInstruments.Messaging.Autofac/Extensions/ContainerBuilderExtensions.cs create mode 100644 src/RapidField.SolidInstruments.Messaging.Autofac/README.md create mode 100644 src/RapidField.SolidInstruments.Messaging.Autofac/RapidField.SolidInstruments.Messaging.Autofac.csproj create mode 100644 src/RapidField.SolidInstruments.Messaging.DotNetNative.Asb/AssemblyAttributes.cs create mode 100644 src/RapidField.SolidInstruments.Messaging.DotNetNative.Asb/README.md create mode 100644 src/RapidField.SolidInstruments.Messaging.DotNetNative.Asb/RapidField.SolidInstruments.Messaging.DotNetNative.Asb.csproj create mode 100644 src/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq/AssemblyAttributes.cs create mode 100644 src/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq/README.md create mode 100644 src/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq.csproj create mode 100644 src/RapidField.SolidInstruments.Messaging.DotNetNative/AssemblyAttributes.cs create mode 100644 src/RapidField.SolidInstruments.Messaging.DotNetNative/Extensions/IServiceCollectionExtensions.cs create mode 100644 src/RapidField.SolidInstruments.Messaging.DotNetNative/README.md create mode 100644 src/RapidField.SolidInstruments.Messaging.DotNetNative/RapidField.SolidInstruments.Messaging.DotNetNative.csproj diff --git a/LICENSE.txt b/LICENSE.txt index 8c591691..a4c3cdce 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 RapidField LLC +Copyright (c) 2020 RapidField LLC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/RapidField.SolidInstruments.sln b/RapidField.SolidInstruments.sln index e7e75953..8b2202be 100644 --- a/RapidField.SolidInstruments.sln +++ b/RapidField.SolidInstruments.sln @@ -341,7 +341,31 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ISSUE_TEMPLATE", "ISSUE_TEM .github\ISSUE_TEMPLATE\QUESTION.md = .github\ISSUE_TEMPLATE\QUESTION.md EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RapidField.SolidInstruments.Messaging.InMemory.UnitTests", "test\RapidField.SolidInstruments.Messaging.InMemory.UnitTests\RapidField.SolidInstruments.Messaging.InMemory.UnitTests.csproj", "{0E10878E-46FB-4512-BE1B-6A97FEDC0C58}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RapidField.SolidInstruments.Messaging.InMemory.UnitTests", "test\RapidField.SolidInstruments.Messaging.InMemory.UnitTests\RapidField.SolidInstruments.Messaging.InMemory.UnitTests.csproj", "{0E10878E-46FB-4512-BE1B-6A97FEDC0C58}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RapidField.SolidInstruments.Command.Autofac", "src\RapidField.SolidInstruments.Command.Autofac\RapidField.SolidInstruments.Command.Autofac.csproj", "{282FA7A1-42CD-42C4-8B5B-A0012EE23B4E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RapidField.SolidInstruments.Command.DotNetNative", "src\RapidField.SolidInstruments.Command.DotNetNative\RapidField.SolidInstruments.Command.DotNetNative.csproj", "{0502C599-6B97-44EA-83F5-3FC7FBF91D5E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RapidField.SolidInstruments.EventAuthoring.Autofac", "src\RapidField.SolidInstruments.EventAuthoring.Autofac\RapidField.SolidInstruments.EventAuthoring.Autofac.csproj", "{796F661A-049A-4B01-B85B-D065761D8866}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RapidField.SolidInstruments.EventAuthoring.DotNetNative", "src\RapidField.SolidInstruments.EventAuthoring.DotNetNative\RapidField.SolidInstruments.EventAuthoring.DotNetNative.csproj", "{42DC26D2-05A6-40AA-8CC2-28EEB38D694C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RapidField.SolidInstruments.DataAccess.Autofac", "src\RapidField.SolidInstruments.DataAccess.Autofac\RapidField.SolidInstruments.DataAccess.Autofac.csproj", "{749DE514-CD14-4974-B9D8-0D32FF0BF7D3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RapidField.SolidInstruments.DataAccess.DotNetNative", "src\RapidField.SolidInstruments.DataAccess.DotNetNative\RapidField.SolidInstruments.DataAccess.DotNetNative.csproj", "{7D6B0CBD-DC6E-49EB-8280-1C5F6035A819}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RapidField.SolidInstruments.Messaging.Autofac", "src\RapidField.SolidInstruments.Messaging.Autofac\RapidField.SolidInstruments.Messaging.Autofac.csproj", "{BBC35EAE-AA5B-4DF1-82F1-5DB131D59235}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RapidField.SolidInstruments.Messaging.DotNetNative", "src\RapidField.SolidInstruments.Messaging.DotNetNative\RapidField.SolidInstruments.Messaging.DotNetNative.csproj", "{3CE88795-3F31-4017-B476-F002F98E7DB7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RapidField.SolidInstruments.Messaging.Autofac.Asb", "src\RapidField.SolidInstruments.Messaging.Autofac.Asb\RapidField.SolidInstruments.Messaging.Autofac.Asb.csproj", "{6ABD2807-9569-42E2-A8BF-BE8079821BE7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RapidField.SolidInstruments.Messaging.Autofac.Rmq", "src\RapidField.SolidInstruments.Messaging.Autofac.Rmq\RapidField.SolidInstruments.Messaging.Autofac.Rmq.csproj", "{C1B05DFA-35B4-4BF7-BE63-B8854AE55B67}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RapidField.SolidInstruments.Messaging.DotNetNative.Asb", "src\RapidField.SolidInstruments.Messaging.DotNetNative.Asb\RapidField.SolidInstruments.Messaging.DotNetNative.Asb.csproj", "{0A130B10-C3DA-48BB-9D2D-8BB1130D299D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RapidField.SolidInstruments.Messaging.DotNetNative.Rmq", "src\RapidField.SolidInstruments.Messaging.DotNetNative.Rmq\RapidField.SolidInstruments.Messaging.DotNetNative.Rmq.csproj", "{8FFB8E4C-5858-4D18-B8DA-D669EED643AB}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -517,6 +541,54 @@ Global {0E10878E-46FB-4512-BE1B-6A97FEDC0C58}.Debug|Any CPU.Build.0 = Debug|Any CPU {0E10878E-46FB-4512-BE1B-6A97FEDC0C58}.Release|Any CPU.ActiveCfg = Release|Any CPU {0E10878E-46FB-4512-BE1B-6A97FEDC0C58}.Release|Any CPU.Build.0 = Release|Any CPU + {282FA7A1-42CD-42C4-8B5B-A0012EE23B4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {282FA7A1-42CD-42C4-8B5B-A0012EE23B4E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {282FA7A1-42CD-42C4-8B5B-A0012EE23B4E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {282FA7A1-42CD-42C4-8B5B-A0012EE23B4E}.Release|Any CPU.Build.0 = Release|Any CPU + {0502C599-6B97-44EA-83F5-3FC7FBF91D5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0502C599-6B97-44EA-83F5-3FC7FBF91D5E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0502C599-6B97-44EA-83F5-3FC7FBF91D5E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0502C599-6B97-44EA-83F5-3FC7FBF91D5E}.Release|Any CPU.Build.0 = Release|Any CPU + {796F661A-049A-4B01-B85B-D065761D8866}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {796F661A-049A-4B01-B85B-D065761D8866}.Debug|Any CPU.Build.0 = Debug|Any CPU + {796F661A-049A-4B01-B85B-D065761D8866}.Release|Any CPU.ActiveCfg = Release|Any CPU + {796F661A-049A-4B01-B85B-D065761D8866}.Release|Any CPU.Build.0 = Release|Any CPU + {42DC26D2-05A6-40AA-8CC2-28EEB38D694C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {42DC26D2-05A6-40AA-8CC2-28EEB38D694C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {42DC26D2-05A6-40AA-8CC2-28EEB38D694C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {42DC26D2-05A6-40AA-8CC2-28EEB38D694C}.Release|Any CPU.Build.0 = Release|Any CPU + {749DE514-CD14-4974-B9D8-0D32FF0BF7D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {749DE514-CD14-4974-B9D8-0D32FF0BF7D3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {749DE514-CD14-4974-B9D8-0D32FF0BF7D3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {749DE514-CD14-4974-B9D8-0D32FF0BF7D3}.Release|Any CPU.Build.0 = Release|Any CPU + {7D6B0CBD-DC6E-49EB-8280-1C5F6035A819}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7D6B0CBD-DC6E-49EB-8280-1C5F6035A819}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7D6B0CBD-DC6E-49EB-8280-1C5F6035A819}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7D6B0CBD-DC6E-49EB-8280-1C5F6035A819}.Release|Any CPU.Build.0 = Release|Any CPU + {BBC35EAE-AA5B-4DF1-82F1-5DB131D59235}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BBC35EAE-AA5B-4DF1-82F1-5DB131D59235}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BBC35EAE-AA5B-4DF1-82F1-5DB131D59235}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BBC35EAE-AA5B-4DF1-82F1-5DB131D59235}.Release|Any CPU.Build.0 = Release|Any CPU + {3CE88795-3F31-4017-B476-F002F98E7DB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3CE88795-3F31-4017-B476-F002F98E7DB7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3CE88795-3F31-4017-B476-F002F98E7DB7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3CE88795-3F31-4017-B476-F002F98E7DB7}.Release|Any CPU.Build.0 = Release|Any CPU + {6ABD2807-9569-42E2-A8BF-BE8079821BE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6ABD2807-9569-42E2-A8BF-BE8079821BE7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6ABD2807-9569-42E2-A8BF-BE8079821BE7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6ABD2807-9569-42E2-A8BF-BE8079821BE7}.Release|Any CPU.Build.0 = Release|Any CPU + {C1B05DFA-35B4-4BF7-BE63-B8854AE55B67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C1B05DFA-35B4-4BF7-BE63-B8854AE55B67}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C1B05DFA-35B4-4BF7-BE63-B8854AE55B67}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C1B05DFA-35B4-4BF7-BE63-B8854AE55B67}.Release|Any CPU.Build.0 = Release|Any CPU + {0A130B10-C3DA-48BB-9D2D-8BB1130D299D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0A130B10-C3DA-48BB-9D2D-8BB1130D299D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0A130B10-C3DA-48BB-9D2D-8BB1130D299D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0A130B10-C3DA-48BB-9D2D-8BB1130D299D}.Release|Any CPU.Build.0 = Release|Any CPU + {8FFB8E4C-5858-4D18-B8DA-D669EED643AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8FFB8E4C-5858-4D18-B8DA-D669EED643AB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8FFB8E4C-5858-4D18-B8DA-D669EED643AB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8FFB8E4C-5858-4D18-B8DA-D669EED643AB}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -580,6 +652,18 @@ Global {1D0A68B1-B7B3-49C0-87AB-EC52A192D4A3} = {DD6DE5D2-E17E-4985-AD23-29F83799CAFC} {94B64CCC-D7FA-4781-9236-5227222368FC} = {1D0A68B1-B7B3-49C0-87AB-EC52A192D4A3} {0E10878E-46FB-4512-BE1B-6A97FEDC0C58} = {5DAD6C24-954B-43EC-B7D9-62BC08CF7AC6} + {282FA7A1-42CD-42C4-8B5B-A0012EE23B4E} = {F58E05BE-9DC6-41B4-8324-C006F6CE7CC7} + {0502C599-6B97-44EA-83F5-3FC7FBF91D5E} = {F58E05BE-9DC6-41B4-8324-C006F6CE7CC7} + {796F661A-049A-4B01-B85B-D065761D8866} = {F58E05BE-9DC6-41B4-8324-C006F6CE7CC7} + {42DC26D2-05A6-40AA-8CC2-28EEB38D694C} = {F58E05BE-9DC6-41B4-8324-C006F6CE7CC7} + {749DE514-CD14-4974-B9D8-0D32FF0BF7D3} = {F58E05BE-9DC6-41B4-8324-C006F6CE7CC7} + {7D6B0CBD-DC6E-49EB-8280-1C5F6035A819} = {F58E05BE-9DC6-41B4-8324-C006F6CE7CC7} + {BBC35EAE-AA5B-4DF1-82F1-5DB131D59235} = {F58E05BE-9DC6-41B4-8324-C006F6CE7CC7} + {3CE88795-3F31-4017-B476-F002F98E7DB7} = {F58E05BE-9DC6-41B4-8324-C006F6CE7CC7} + {6ABD2807-9569-42E2-A8BF-BE8079821BE7} = {F58E05BE-9DC6-41B4-8324-C006F6CE7CC7} + {C1B05DFA-35B4-4BF7-BE63-B8854AE55B67} = {F58E05BE-9DC6-41B4-8324-C006F6CE7CC7} + {0A130B10-C3DA-48BB-9D2D-8BB1130D299D} = {F58E05BE-9DC6-41B4-8324-C006F6CE7CC7} + {8FFB8E4C-5858-4D18-B8DA-D669EED643AB} = {F58E05BE-9DC6-41B4-8324-C006F6CE7CC7} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {834FCFB0-DA00-4ABD-9424-6FE1398A9F6E} diff --git a/doc/docfx.json b/doc/docfx.json index eb181a28..6fa0ffb8 100644 --- a/doc/docfx.json +++ b/doc/docfx.json @@ -19,7 +19,7 @@ "dest": "_DocumentationWebsite", "globalMetadata": { "_appFaviconPath": "images/favicon.ico", - "_appFooter": "© 2019 RapidField", + "_appFooter": "© 2020 RapidField", "_appLogoPath": "images/RapidField.Logo.Color.White.Transparent.227w.png", "_appTitle": "Solid Instruments", "_disableBreadcrumb": true, @@ -60,6 +60,9 @@ "metadata": [ { "dest": "api", + "properties": { + "TargetFramework": "netstandard2.1" + }, "src": [ { "files": [ diff --git a/example/RapidField.SolidInstruments.Example.Contracts/RapidField.SolidInstruments.Example.Contracts.csproj b/example/RapidField.SolidInstruments.Example.Contracts/RapidField.SolidInstruments.Example.Contracts.csproj index b196cb26..043ebc8a 100644 --- a/example/RapidField.SolidInstruments.Example.Contracts/RapidField.SolidInstruments.Example.Contracts.csproj +++ b/example/RapidField.SolidInstruments.Example.Contracts/RapidField.SolidInstruments.Example.Contracts.csproj @@ -14,12 +14,12 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in latest - bin\Debug\netstandard2.0\RapidField.SolidInstruments.Example.Contracts.xml + bin\Debug\netstandard2.1\RapidField.SolidInstruments.Example.Contracts.xml true - bin\Release\netstandard2.0\RapidField.SolidInstruments.Example.Contracts.xml + bin\Release\netstandard2.1\RapidField.SolidInstruments.Example.Contracts.xml true diff --git a/example/RapidField.SolidInstruments.Example.DatabaseModel/ExampleDatabaseModelDependencyModule.cs b/example/RapidField.SolidInstruments.Example.DatabaseModel/ExampleDatabaseModelDependencyModule.cs index cd89ab81..ec3b5a16 100644 --- a/example/RapidField.SolidInstruments.Example.DatabaseModel/ExampleDatabaseModelDependencyModule.cs +++ b/example/RapidField.SolidInstruments.Example.DatabaseModel/ExampleDatabaseModelDependencyModule.cs @@ -4,12 +4,12 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.DataAccess.DotNetNative.Extensions; using RapidField.SolidInstruments.Example.DatabaseModel.CommandHandlers; using RapidField.SolidInstruments.Example.DatabaseModel.Commands; -using RapidField.SolidInstruments.Example.DatabaseModel.Repositories; using RapidField.SolidInstruments.InversionOfControl.DotNetNative; using System; +using System.Collections.Generic; namespace RapidField.SolidInstruments.Example.DatabaseModel { @@ -51,14 +51,9 @@ protected override void Configure(ServiceCollection configurator, IConfiguration configurator.AddScoped(); configurator.AddScoped(); - // Register repositories. - configurator.AddScoped(); - configurator.AddScoped(); - configurator.AddScoped(); - - // Register command handlers. - configurator.AddTransient, AddFibonacciNumberCommandHandler>(); - configurator.AddTransient, GetFibonacciNumberValuesCommandHandler>(); + // Register data access command handlers. + configurator.AddDataAccessCommandHandler(); + configurator.AddDataAccessCommandHandler, GetFibonacciNumberValuesCommandHandler>(); } } } \ No newline at end of file diff --git a/example/RapidField.SolidInstruments.Example.DatabaseModel/RapidField.SolidInstruments.Example.DatabaseModel.csproj b/example/RapidField.SolidInstruments.Example.DatabaseModel/RapidField.SolidInstruments.Example.DatabaseModel.csproj index f15c03f1..9398a486 100644 --- a/example/RapidField.SolidInstruments.Example.DatabaseModel/RapidField.SolidInstruments.Example.DatabaseModel.csproj +++ b/example/RapidField.SolidInstruments.Example.DatabaseModel/RapidField.SolidInstruments.Example.DatabaseModel.csproj @@ -14,12 +14,12 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in latest - bin\Debug\netstandard2.0\RapidField.SolidInstruments.Example.DatabaseModel.xml + bin\Debug\netstandard2.1\RapidField.SolidInstruments.Example.DatabaseModel.xml true - bin\Release\netstandard2.0\RapidField.SolidInstruments.Example.DatabaseModel.xml + bin\Release\netstandard2.1\RapidField.SolidInstruments.Example.DatabaseModel.xml true @@ -28,6 +28,7 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in + diff --git a/example/RapidField.SolidInstruments.Example.Domain/RapidField.SolidInstruments.Example.Domain.csproj b/example/RapidField.SolidInstruments.Example.Domain/RapidField.SolidInstruments.Example.Domain.csproj index f0ff5fb3..886c1c80 100644 --- a/example/RapidField.SolidInstruments.Example.Domain/RapidField.SolidInstruments.Example.Domain.csproj +++ b/example/RapidField.SolidInstruments.Example.Domain/RapidField.SolidInstruments.Example.Domain.csproj @@ -14,12 +14,12 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in latest - bin\Debug\netstandard2.0\RapidField.SolidInstruments.Example.Domain.xml + bin\Debug\netstandard2.1\RapidField.SolidInstruments.Example.Domain.xml true - bin\Release\netstandard2.0\RapidField.SolidInstruments.Example.Domain.xml + bin\Release\netstandard2.1\RapidField.SolidInstruments.Example.Domain.xml true diff --git a/example/RapidField.SolidInstruments.Example.ServiceApplication/ApplicationDependencyModule.cs b/example/RapidField.SolidInstruments.Example.ServiceApplication/ApplicationDependencyModule.cs index 52465efc..7e54b00b 100644 --- a/example/RapidField.SolidInstruments.Example.ServiceApplication/ApplicationDependencyModule.cs +++ b/example/RapidField.SolidInstruments.Example.ServiceApplication/ApplicationDependencyModule.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.DependencyInjection; using RapidField.SolidInstruments.Core.Extensions; using RapidField.SolidInstruments.InversionOfControl.DotNetNative; +using RapidField.SolidInstruments.InversionOfControl.DotNetNative.Extensions; using RapidField.SolidInstruments.Messaging; using RapidField.SolidInstruments.Messaging.AzureServiceBus; using System; @@ -47,7 +48,7 @@ public ApplicationDependencyModule(IConfiguration applicationConfiguration) protected override void Configure(ServiceCollection configurator, IConfiguration applicationConfiguration) { // Register the configuration. - configurator.AddSingleton(applicationConfiguration); + configurator.AddApplicationConfiguration(applicationConfiguration); // Register the service bus connection. configurator.AddScoped((serviceProvider) => diff --git a/example/RapidField.SolidInstruments.Example.WebApplication/ApplicationDependencyModule.cs b/example/RapidField.SolidInstruments.Example.WebApplication/ApplicationDependencyModule.cs index faaf94b4..48bda30a 100644 --- a/example/RapidField.SolidInstruments.Example.WebApplication/ApplicationDependencyModule.cs +++ b/example/RapidField.SolidInstruments.Example.WebApplication/ApplicationDependencyModule.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using RapidField.SolidInstruments.InversionOfControl.DotNetNative; +using RapidField.SolidInstruments.InversionOfControl.DotNetNative.Extensions; namespace RapidField.SolidInstruments.Example.WebApplication { @@ -37,6 +38,6 @@ public ApplicationDependencyModule(IConfiguration applicationConfiguration) /// /// Configuration information for the application. /// - protected override void Configure(ServiceCollection configurator, IConfiguration applicationConfiguration) => configurator.AddSingleton(applicationConfiguration); + protected override void Configure(ServiceCollection configurator, IConfiguration applicationConfiguration) => configurator.AddApplicationConfiguration(applicationConfiguration); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Collections/RapidField.SolidInstruments.Collections.csproj b/src/RapidField.SolidInstruments.Collections/RapidField.SolidInstruments.Collections.csproj index dd043d1b..97e17e3b 100644 --- a/src/RapidField.SolidInstruments.Collections/RapidField.SolidInstruments.Collections.csproj +++ b/src/RapidField.SolidInstruments.Collections/RapidField.SolidInstruments.Collections.csproj @@ -22,12 +22,12 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in solid-instruments;collections;circular-buffer;infinite-sequence;pinned-memory;tree - bin\Debug\netstandard2.0\RapidField.SolidInstruments.Collections.xml + bin\Debug\netstandard2.1\RapidField.SolidInstruments.Collections.xml true - bin\Release\netstandard2.0\RapidField.SolidInstruments.Collections.xml + bin\Release\netstandard2.1\RapidField.SolidInstruments.Collections.xml true diff --git a/src/RapidField.SolidInstruments.Command.Autofac/AssemblyAttributes.cs b/src/RapidField.SolidInstruments.Command.Autofac/AssemblyAttributes.cs new file mode 100644 index 00000000..a32e4acd --- /dev/null +++ b/src/RapidField.SolidInstruments.Command.Autofac/AssemblyAttributes.cs @@ -0,0 +1,7 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System.Runtime.CompilerServices; + +[assembly: DisablePrivateReflection()] \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command.Autofac/Extensions/ContainerBuilderExtensions.cs b/src/RapidField.SolidInstruments.Command.Autofac/Extensions/ContainerBuilderExtensions.cs new file mode 100644 index 00000000..e26dbf34 --- /dev/null +++ b/src/RapidField.SolidInstruments.Command.Autofac/Extensions/ContainerBuilderExtensions.cs @@ -0,0 +1,31 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Autofac; + +namespace RapidField.SolidInstruments.Command.Autofac.Extensions +{ + /// + /// Extends the class with inversion of control features to support the command and mediator + /// patterns. + /// + public static class ContainerBuilderExtensions + { + /// + /// Registers a transient command handler for the specified command type. + /// + /// + /// The type of the command for which a handler is registered. + /// + /// + /// The type of the command handler that is registered. + /// + /// + /// The current . + /// + public static void RegisterCommandHandler(this ContainerBuilder target) + where TCommand : class, ICommandBase + where TCommandHandler : class, ICommandHandler => target.RegisterType().As>().InstancePerDependency(); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command.Autofac/README.md b/src/RapidField.SolidInstruments.Command.Autofac/README.md new file mode 100644 index 00000000..95979e7d --- /dev/null +++ b/src/RapidField.SolidInstruments.Command.Autofac/README.md @@ -0,0 +1,57 @@ + + +[![Solid Instruments](../../SolidInstruments.Logo.Color.Transparent.500w.png)](../../README.md) +
     +![Command](../../doc/images/Label.Command.300w.png) +- - - + +# RapidField.SolidInstruments.Command.Autofac + +This document describes the purpose of the [`RapidField.SolidInstruments.Command.Autofac`]() library and offers guidance with respect to licensing, installation and usage. + +## Features + +This library exposes the [**Autofac**](https://autofac.org/) IoC integration for the **Solid Instruments** implementations of the command and mediator patterns. + +## License + +[![License](https://img.shields.io/github/license/rapidfield/solid-instruments?style=flat&color=lightseagreen&label=license&logo=open-access&logoColor=lightgrey)](../../LICENSE.txt) + +**Solid Instruments** is [MIT-licensed](https://en.wikipedia.org/wiki/MIT_License). Review the [license terms](../../LICENSE.txt) for more information. + +## Documentation + +[![Documentation](https://img.shields.io/badge/documentation-website-tan?style=flat&logo=buffer&logoColor=lightgrey)](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Command.Autofac.html) + +Check out the [**API Reference**](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Command.Autofac.html) for usage examples and reference documentation. + +## Installation + +[![Version](https://img.shields.io/nuget/vpre/RapidField.SolidInstruments.Command.Autofac?style=flat&color=blue&label=version&logo=nuget&logoColor=lightgrey)](https://www.nuget.org/packages/RapidField.SolidInstruments.Command.Autofac) +[![Downloads](https://img.shields.io/nuget/dt/RapidField.SolidInstruments.Command.Autofac?style=flat&color=blue&logo=nuget&logoColor=lightgrey)](https://www.nuget.org/packages/RapidField.SolidInstruments.Command.Autofac) + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.Command.Autofac +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.Command.Autofac +``` + +
+ +- - - + +
+ +[![RapidField](../../RapidField.Logo.Color.Black.Transparent.200w.png)](https://www.rapidfield.com) + +###### Copyright (c) RapidField LLC. All rights reserved. "RapidField" and "Solid Instruments" are trademarks of RapidField LLC. \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command.Autofac/RapidField.SolidInstruments.Command.Autofac.csproj b/src/RapidField.SolidInstruments.Command.Autofac/RapidField.SolidInstruments.Command.Autofac.csproj new file mode 100644 index 00000000..f4843ee6 --- /dev/null +++ b/src/RapidField.SolidInstruments.Command.Autofac/RapidField.SolidInstruments.Command.Autofac.csproj @@ -0,0 +1,50 @@ + + + + + Solid Instruments contributors + RapidField + Copyright (c) RapidField LLC. All rights reserved. + Solid Instruments + This library exposes the Autofac IoC integration for the Solid Instruments implementations of the command and mediator patterns. + $(BuildVersion) + netstandard2.1 + latest + git + https://github.com/rapidfield/solid-instruments + true + true + LICENSE.txt + https://www.solidinstruments.com + Icon.Command.128w.png + solid-instruments;command;mediator;inversion-of-control;ioc;dependency-injection;di;autofac + + + bin\Debug\netstandard2.1\RapidField.SolidInstruments.Command.Autofac.xml + true + + + + bin\Release\netstandard2.1\RapidField.SolidInstruments.Command.Autofac.xml + true + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command.DotNetNative/AssemblyAttributes.cs b/src/RapidField.SolidInstruments.Command.DotNetNative/AssemblyAttributes.cs new file mode 100644 index 00000000..a32e4acd --- /dev/null +++ b/src/RapidField.SolidInstruments.Command.DotNetNative/AssemblyAttributes.cs @@ -0,0 +1,7 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System.Runtime.CompilerServices; + +[assembly: DisablePrivateReflection()] \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command.DotNetNative/Extensions/IServiceCollectionExtensions.cs b/src/RapidField.SolidInstruments.Command.DotNetNative/Extensions/IServiceCollectionExtensions.cs new file mode 100644 index 00000000..e5198be0 --- /dev/null +++ b/src/RapidField.SolidInstruments.Command.DotNetNative/Extensions/IServiceCollectionExtensions.cs @@ -0,0 +1,34 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Microsoft.Extensions.DependencyInjection; + +namespace RapidField.SolidInstruments.Command.DotNetNative.Extensions +{ + /// + /// Extends the interface with native .NET inversion of control features to support the + /// command and mediator patterns. + /// + public static class IServiceCollectionExtensions + { + /// + /// Registers a transient command handler for the specified command type. + /// + /// + /// The type of the command for which a handler is registered. + /// + /// + /// The type of the command handler that is registered. + /// + /// + /// The current . + /// + /// + /// The resulting . + /// + public static IServiceCollection AddCommandHandler(this IServiceCollection target) + where TCommand : class, ICommandBase + where TCommandHandler : class, ICommandHandler => target.AddTransient, TCommandHandler>(); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command.DotNetNative/README.md b/src/RapidField.SolidInstruments.Command.DotNetNative/README.md new file mode 100644 index 00000000..12b30882 --- /dev/null +++ b/src/RapidField.SolidInstruments.Command.DotNetNative/README.md @@ -0,0 +1,57 @@ + + +[![Solid Instruments](../../SolidInstruments.Logo.Color.Transparent.500w.png)](../../README.md) +
     +![Command](../../doc/images/Label.Command.300w.png) +- - - + +# RapidField.SolidInstruments.Command.DotNetNative + +This document describes the purpose of the [`RapidField.SolidInstruments.Command.DotNetNative`]() library and offers guidance with respect to licensing, installation and usage. + +## Features + +This library exposes the native .NET IoC integration for the **Solid Instruments** implementations of the command and mediator patterns. + +## License + +[![License](https://img.shields.io/github/license/rapidfield/solid-instruments?style=flat&color=lightseagreen&label=license&logo=open-access&logoColor=lightgrey)](../../LICENSE.txt) + +**Solid Instruments** is [MIT-licensed](https://en.wikipedia.org/wiki/MIT_License). Review the [license terms](../../LICENSE.txt) for more information. + +## Documentation + +[![Documentation](https://img.shields.io/badge/documentation-website-tan?style=flat&logo=buffer&logoColor=lightgrey)](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Command.DotNetNative.html) + +Check out the [**API Reference**](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Command.DotNetNative.html) for usage examples and reference documentation. + +## Installation + +[![Version](https://img.shields.io/nuget/vpre/RapidField.SolidInstruments.Command.DotNetNative?style=flat&color=blue&label=version&logo=nuget&logoColor=lightgrey)](https://www.nuget.org/packages/RapidField.SolidInstruments.Command.DotNetNative) +[![Downloads](https://img.shields.io/nuget/dt/RapidField.SolidInstruments.Command.DotNetNative?style=flat&color=blue&logo=nuget&logoColor=lightgrey)](https://www.nuget.org/packages/RapidField.SolidInstruments.Command.DotNetNative) + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.Command.DotNetNative +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.Command.DotNetNative +``` + +
+ +- - - + +
+ +[![RapidField](../../RapidField.Logo.Color.Black.Transparent.200w.png)](https://www.rapidfield.com) + +###### Copyright (c) RapidField LLC. All rights reserved. "RapidField" and "Solid Instruments" are trademarks of RapidField LLC. \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command.DotNetNative/RapidField.SolidInstruments.Command.DotNetNative.csproj b/src/RapidField.SolidInstruments.Command.DotNetNative/RapidField.SolidInstruments.Command.DotNetNative.csproj new file mode 100644 index 00000000..b93d0d4a --- /dev/null +++ b/src/RapidField.SolidInstruments.Command.DotNetNative/RapidField.SolidInstruments.Command.DotNetNative.csproj @@ -0,0 +1,50 @@ + + + + + Solid Instruments contributors + RapidField + Copyright (c) RapidField LLC. All rights reserved. + Solid Instruments + This library exposes the native .NET IoC integration for the Solid Instruments implementations of the command and mediator patterns. + $(BuildVersion) + netstandard2.1 + latest + git + https://github.com/rapidfield/solid-instruments + true + true + LICENSE.txt + https://www.solidinstruments.com + Icon.Command.128w.png + solid-instruments;command;mediator;inversion-of-control;ioc;dependency-injection;di + + + bin\Debug\netstandard2.1\RapidField.SolidInstruments.Command.Autofac.xml + true + + + + bin\Release\netstandard2.1\RapidField.SolidInstruments.Command.Autofac.xml + true + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/RapidField.SolidInstruments.Command.csproj b/src/RapidField.SolidInstruments.Command/RapidField.SolidInstruments.Command.csproj index e0f4cb00..8c23c762 100644 --- a/src/RapidField.SolidInstruments.Command/RapidField.SolidInstruments.Command.csproj +++ b/src/RapidField.SolidInstruments.Command/RapidField.SolidInstruments.Command.csproj @@ -22,12 +22,12 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in solid-instruments;command;mediator - bin\Debug\netstandard2.0\RapidField.SolidInstruments.Command.xml + bin\Debug\netstandard2.1\RapidField.SolidInstruments.Command.xml true - bin\Release\netstandard2.0\RapidField.SolidInstruments.Command.xml + bin\Release\netstandard2.1\RapidField.SolidInstruments.Command.xml true diff --git a/src/RapidField.SolidInstruments.Core/RapidField.SolidInstruments.Core.csproj b/src/RapidField.SolidInstruments.Core/RapidField.SolidInstruments.Core.csproj index 04d268e7..3f16d29d 100644 --- a/src/RapidField.SolidInstruments.Core/RapidField.SolidInstruments.Core.csproj +++ b/src/RapidField.SolidInstruments.Core/RapidField.SolidInstruments.Core.csproj @@ -22,13 +22,13 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in solid-instruments;core;concurrency;thread-safety;fluent-validation;fluent-guard;object-lifetime - bin\Debug\netstandard2.0\RapidField.SolidInstruments.Core.xml + bin\Debug\netstandard2.1\RapidField.SolidInstruments.Core.xml true 4 - bin\Release\netstandard2.0\RapidField.SolidInstruments.Core.xml + bin\Release\netstandard2.1\RapidField.SolidInstruments.Core.xml true diff --git a/src/RapidField.SolidInstruments.Cryptography/RapidField.SolidInstruments.Cryptography.csproj b/src/RapidField.SolidInstruments.Cryptography/RapidField.SolidInstruments.Cryptography.csproj index f9fbbc3f..1c1182ab 100644 --- a/src/RapidField.SolidInstruments.Cryptography/RapidField.SolidInstruments.Cryptography.csproj +++ b/src/RapidField.SolidInstruments.Cryptography/RapidField.SolidInstruments.Cryptography.csproj @@ -22,12 +22,12 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in solid-instruments;cryptography;bounded-random-value;cascading-encryption;hash-tree;in-memory-security;random-selection;random-shuffle;csprng;rng - bin\Debug\netstandard2.0\RapidField.SolidInstruments.Cryptography.xml + bin\Debug\netstandard2.1\RapidField.SolidInstruments.Cryptography.xml true - bin\Release\netstandard2.0\RapidField.SolidInstruments.Cryptography.xml + bin\Release\netstandard2.1\RapidField.SolidInstruments.Cryptography.xml true @@ -37,7 +37,7 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in
- + diff --git a/src/RapidField.SolidInstruments.DataAccess.Autofac/AssemblyAttributes.cs b/src/RapidField.SolidInstruments.DataAccess.Autofac/AssemblyAttributes.cs new file mode 100644 index 00000000..a32e4acd --- /dev/null +++ b/src/RapidField.SolidInstruments.DataAccess.Autofac/AssemblyAttributes.cs @@ -0,0 +1,7 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System.Runtime.CompilerServices; + +[assembly: DisablePrivateReflection()] \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess.Autofac/Extensions/ContainerBuilderExtensions.cs b/src/RapidField.SolidInstruments.DataAccess.Autofac/Extensions/ContainerBuilderExtensions.cs new file mode 100644 index 00000000..dc59ef23 --- /dev/null +++ b/src/RapidField.SolidInstruments.DataAccess.Autofac/Extensions/ContainerBuilderExtensions.cs @@ -0,0 +1,51 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Autofac; +using RapidField.SolidInstruments.Command.Autofac.Extensions; +using RapidField.SolidInstruments.Core; + +namespace RapidField.SolidInstruments.DataAccess.Autofac.Extensions +{ + /// + /// Extends the class with inversion of control features to support data access abstractions. + /// + public static class ContainerBuilderExtensions + { + /// + /// Registers a transient data access command handler for the specified command type. + /// + /// + /// The type of the data access command for which a handler is registered. + /// + /// + /// The type of the data access command handler that is registered. + /// + /// + /// The current . + /// + public static void RegisterDataAccessCommandHandler(this ContainerBuilder target) + where TCommand : class, IDataAccessCommand + where TCommandHandler : class, IDataAccessCommandHandler => target.RegisterDataAccessCommandHandler(); + + /// + /// Registers a transient data access command handler for the specified command type. + /// + /// + /// The type of the data access command for which a handler is registered. + /// + /// + /// The type of the result that is emitted by the handler when processing a data access command. + /// + /// + /// The type of the data access command handler that is registered. + /// + /// + /// The current . + /// + public static void RegisterDataAccessCommandHandler(this ContainerBuilder target) + where TCommand : class, IDataAccessCommand + where TCommandHandler : class, IDataAccessCommandHandler => target.RegisterCommandHandler(); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess.Autofac/README.md b/src/RapidField.SolidInstruments.DataAccess.Autofac/README.md new file mode 100644 index 00000000..165e4708 --- /dev/null +++ b/src/RapidField.SolidInstruments.DataAccess.Autofac/README.md @@ -0,0 +1,57 @@ + + +[![Solid Instruments](../../SolidInstruments.Logo.Color.Transparent.500w.png)](../../README.md) +
     +![DataAccess](../../doc/images/Label.DataAccess.300w.png) +- - - + +# RapidField.SolidInstruments.DataAccess.Autofac + +This document describes the purpose of the [`RapidField.SolidInstruments.DataAccess.Autofac`]() library and offers guidance with respect to licensing, installation and usage. + +## Features + +This library exposes the [**Autofac**](https://autofac.org/) IoC integration for the **Solid Instruments** implementations of abstractions for the repository and unit-of-work patterns. + +## License + +[![License](https://img.shields.io/github/license/rapidfield/solid-instruments?style=flat&color=lightseagreen&label=license&logo=open-access&logoColor=lightgrey)](../../LICENSE.txt) + +**Solid Instruments** is [MIT-licensed](https://en.wikipedia.org/wiki/MIT_License). Review the [license terms](../../LICENSE.txt) for more information. + +## Documentation + +[![Documentation](https://img.shields.io/badge/documentation-website-tan?style=flat&logo=buffer&logoColor=lightgrey)](https://www.solidinstruments.com/api/RapidField.SolidInstruments.DataAccess.Autofac.html) + +Check out the [**API Reference**](https://www.solidinstruments.com/api/RapidField.SolidInstruments.DataAccess.Autofac.html) for usage examples and reference documentation. + +## Installation + +[![Version](https://img.shields.io/nuget/vpre/RapidField.SolidInstruments.DataAccess.Autofac?style=flat&color=blue&label=version&logo=nuget&logoColor=lightgrey)](https://www.nuget.org/packages/RapidField.SolidInstruments.DataAccess.Autofac) +[![Downloads](https://img.shields.io/nuget/dt/RapidField.SolidInstruments.DataAccess.Autofac?style=flat&color=blue&logo=nuget&logoColor=lightgrey)](https://www.nuget.org/packages/RapidField.SolidInstruments.DataAccess.Autofac) + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.DataAccess.Autofac +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.DataAccess.Autofac +``` + +
+ +- - - + +
+ +[![RapidField](../../RapidField.Logo.Color.Black.Transparent.200w.png)](https://www.rapidfield.com) + +###### Copyright (c) RapidField LLC. All rights reserved. "RapidField" and "Solid Instruments" are trademarks of RapidField LLC. \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess.Autofac/RapidField.SolidInstruments.DataAccess.Autofac.csproj b/src/RapidField.SolidInstruments.DataAccess.Autofac/RapidField.SolidInstruments.DataAccess.Autofac.csproj new file mode 100644 index 00000000..559c8aa6 --- /dev/null +++ b/src/RapidField.SolidInstruments.DataAccess.Autofac/RapidField.SolidInstruments.DataAccess.Autofac.csproj @@ -0,0 +1,51 @@ + + + + + Solid Instruments contributors + RapidField + Copyright (c) RapidField LLC. All rights reserved. + Solid Instruments + This library exposes the Autofac IoC integration for the Solid Instruments implementations of abstractions for the repository and unit-of-work patterns. + $(BuildVersion) + netstandard2.1 + latest + git + https://github.com/rapidfield/solid-instruments + true + true + LICENSE.txt + https://www.solidinstruments.com + Icon.DataAccess.128w.png + solid-instruments;data-access;dal;repository;unit-of-work;orm;inversion-of-control;ioc;dependency-injection;di;autofac + + + bin\Debug\netstandard2.1\RapidField.SolidInstruments.DataAccess.Autofac.xml + true + + + + bin\Release\netstandard2.1\RapidField.SolidInstruments.DataAccess.Autofac.xml + true + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess.DotNetNative/AssemblyAttributes.cs b/src/RapidField.SolidInstruments.DataAccess.DotNetNative/AssemblyAttributes.cs new file mode 100644 index 00000000..a32e4acd --- /dev/null +++ b/src/RapidField.SolidInstruments.DataAccess.DotNetNative/AssemblyAttributes.cs @@ -0,0 +1,7 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System.Runtime.CompilerServices; + +[assembly: DisablePrivateReflection()] \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess.DotNetNative/Extensions/IServiceCollectionExtensions.cs b/src/RapidField.SolidInstruments.DataAccess.DotNetNative/Extensions/IServiceCollectionExtensions.cs new file mode 100644 index 00000000..aacd38ff --- /dev/null +++ b/src/RapidField.SolidInstruments.DataAccess.DotNetNative/Extensions/IServiceCollectionExtensions.cs @@ -0,0 +1,58 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Microsoft.Extensions.DependencyInjection; +using RapidField.SolidInstruments.Command.DotNetNative.Extensions; +using RapidField.SolidInstruments.Core; + +namespace RapidField.SolidInstruments.DataAccess.DotNetNative.Extensions +{ + /// + /// Extends the interface with native .NET inversion of control features to support data + /// access abstractions. + /// + public static class IServiceCollectionExtensions + { + /// + /// Registers a transient data access command handler for the specified command type. + /// + /// + /// The type of the data access command for which a handler is registered. + /// + /// + /// The type of the data access command handler that is registered. + /// + /// + /// The current . + /// + /// + /// The resulting . + /// + public static IServiceCollection AddDataAccessCommandHandler(this IServiceCollection target) + where TCommand : class, IDataAccessCommand + where TCommandHandler : class, IDataAccessCommandHandler => target.AddDataAccessCommandHandler(); + + /// + /// Registers a transient data access command handler for the specified command type. + /// + /// + /// The type of the data access command for which a handler is registered. + /// + /// + /// The type of the result that is emitted by the handler when processing a data access command. + /// + /// + /// The type of the data access command handler that is registered. + /// + /// + /// The current . + /// + /// + /// The resulting . + /// + public static IServiceCollection AddDataAccessCommandHandler(this IServiceCollection target) + where TCommand : class, IDataAccessCommand + where TCommandHandler : class, IDataAccessCommandHandler => target.AddCommandHandler(); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess.DotNetNative/README.md b/src/RapidField.SolidInstruments.DataAccess.DotNetNative/README.md new file mode 100644 index 00000000..8846d347 --- /dev/null +++ b/src/RapidField.SolidInstruments.DataAccess.DotNetNative/README.md @@ -0,0 +1,57 @@ + + +[![Solid Instruments](../../SolidInstruments.Logo.Color.Transparent.500w.png)](../../README.md) +
     +![DataAccess](../../doc/images/Label.DataAccess.300w.png) +- - - + +# RapidField.SolidInstruments.DataAccess.DotNetNative + +This document describes the purpose of the [`RapidField.SolidInstruments.DataAccess.DotNetNative`]() library and offers guidance with respect to licensing, installation and usage. + +## Features + +This library exposes the native .NET IoC integration for the **Solid Instruments** implementations of abstractions for the repository and unit-of-work patterns. + +## License + +[![License](https://img.shields.io/github/license/rapidfield/solid-instruments?style=flat&color=lightseagreen&label=license&logo=open-access&logoColor=lightgrey)](../../LICENSE.txt) + +**Solid Instruments** is [MIT-licensed](https://en.wikipedia.org/wiki/MIT_License). Review the [license terms](../../LICENSE.txt) for more information. + +## Documentation + +[![Documentation](https://img.shields.io/badge/documentation-website-tan?style=flat&logo=buffer&logoColor=lightgrey)](https://www.solidinstruments.com/api/RapidField.SolidInstruments.DataAccess.DotNetNative.html) + +Check out the [**API Reference**](https://www.solidinstruments.com/api/RapidField.SolidInstruments.DataAccess.DotNetNative.html) for usage examples and reference documentation. + +## Installation + +[![Version](https://img.shields.io/nuget/vpre/RapidField.SolidInstruments.DataAccess.DotNetNative?style=flat&color=blue&label=version&logo=nuget&logoColor=lightgrey)](https://www.nuget.org/packages/RapidField.SolidInstruments.DataAccess.DotNetNative) +[![Downloads](https://img.shields.io/nuget/dt/RapidField.SolidInstruments.DataAccess.DotNetNative?style=flat&color=blue&logo=nuget&logoColor=lightgrey)](https://www.nuget.org/packages/RapidField.SolidInstruments.DataAccess.DotNetNative) + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.DataAccess.DotNetNative +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.DataAccess.DotNetNative +``` + +
+ +- - - + +
+ +[![RapidField](../../RapidField.Logo.Color.Black.Transparent.200w.png)](https://www.rapidfield.com) + +###### Copyright (c) RapidField LLC. All rights reserved. "RapidField" and "Solid Instruments" are trademarks of RapidField LLC. \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess.DotNetNative/RapidField.SolidInstruments.DataAccess.DotNetNative.csproj b/src/RapidField.SolidInstruments.DataAccess.DotNetNative/RapidField.SolidInstruments.DataAccess.DotNetNative.csproj new file mode 100644 index 00000000..17ada658 --- /dev/null +++ b/src/RapidField.SolidInstruments.DataAccess.DotNetNative/RapidField.SolidInstruments.DataAccess.DotNetNative.csproj @@ -0,0 +1,51 @@ + + + + + Solid Instruments contributors + RapidField + Copyright (c) RapidField LLC. All rights reserved. + Solid Instruments + This library exposes the native .NET IoC integration for the Solid Instruments implementations of abstractions for the repository and unit-of-work patterns. + $(BuildVersion) + netstandard2.1 + latest + git + https://github.com/rapidfield/solid-instruments + true + true + LICENSE.txt + https://www.solidinstruments.com + Icon.DataAccess.128w.png + solid-instruments;data-access;dal;repository;unit-of-work;orm;inversion-of-control;ioc;dependency-injection;di + + + bin\Debug\netstandard2.1\RapidField.SolidInstruments.DataAccess.DotNetNative.xml + true + + + + bin\Release\netstandard2.1\RapidField.SolidInstruments.DataAccess.DotNetNative.xml + true + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess.EntityFramework/RapidField.SolidInstruments.DataAccess.EntityFramework.csproj b/src/RapidField.SolidInstruments.DataAccess.EntityFramework/RapidField.SolidInstruments.DataAccess.EntityFramework.csproj index 0881bea7..43cbe290 100644 --- a/src/RapidField.SolidInstruments.DataAccess.EntityFramework/RapidField.SolidInstruments.DataAccess.EntityFramework.csproj +++ b/src/RapidField.SolidInstruments.DataAccess.EntityFramework/RapidField.SolidInstruments.DataAccess.EntityFramework.csproj @@ -19,15 +19,15 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in LICENSE.txt https://www.solidinstruments.com Icon.DataAccess.128w.png - solid-instruments;data-access;dal;repository;unit-of-work;orm;entity-framework;ef; + solid-instruments;data-access;dal;repository;unit-of-work;orm;entity-framework;ef - bin\Debug\netstandard2.0\RapidField.SolidInstruments.DataAccess.EntityFramework.xml + bin\Debug\netstandard2.1\RapidField.SolidInstruments.DataAccess.EntityFramework.xml true - bin\Release\netstandard2.0\RapidField.SolidInstruments.DataAccess.EntityFramework.xml + bin\Release\netstandard2.1\RapidField.SolidInstruments.DataAccess.EntityFramework.xml true diff --git a/src/RapidField.SolidInstruments.DataAccess/RapidField.SolidInstruments.DataAccess.csproj b/src/RapidField.SolidInstruments.DataAccess/RapidField.SolidInstruments.DataAccess.csproj index 50ddbd0f..d58698ec 100644 --- a/src/RapidField.SolidInstruments.DataAccess/RapidField.SolidInstruments.DataAccess.csproj +++ b/src/RapidField.SolidInstruments.DataAccess/RapidField.SolidInstruments.DataAccess.csproj @@ -22,12 +22,12 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in solid-instruments;data-access;dal;repository;unit-of-work;orm - bin\Debug\netstandard2.0\RapidField.SolidInstruments.DataAccess.xml + bin\Debug\netstandard2.1\RapidField.SolidInstruments.DataAccess.xml true - bin\Release\netstandard2.0\RapidField.SolidInstruments.DataAccess.xml + bin\Release\netstandard2.1\RapidField.SolidInstruments.DataAccess.xml true diff --git a/src/RapidField.SolidInstruments.EventAuthoring.Autofac/AssemblyAttributes.cs b/src/RapidField.SolidInstruments.EventAuthoring.Autofac/AssemblyAttributes.cs new file mode 100644 index 00000000..a32e4acd --- /dev/null +++ b/src/RapidField.SolidInstruments.EventAuthoring.Autofac/AssemblyAttributes.cs @@ -0,0 +1,7 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System.Runtime.CompilerServices; + +[assembly: DisablePrivateReflection()] \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring.Autofac/Extensions/ContainerBuilderExtensions.cs b/src/RapidField.SolidInstruments.EventAuthoring.Autofac/Extensions/ContainerBuilderExtensions.cs new file mode 100644 index 00000000..48e5c118 --- /dev/null +++ b/src/RapidField.SolidInstruments.EventAuthoring.Autofac/Extensions/ContainerBuilderExtensions.cs @@ -0,0 +1,31 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Autofac; +using RapidField.SolidInstruments.Command.Autofac.Extensions; + +namespace RapidField.SolidInstruments.EventAuthoring.Autofac.Extensions +{ + /// + /// Extends the class with inversion of control features to support event authoring. + /// + public static class ContainerBuilderExtensions + { + /// + /// Registers a transient event handler for the specified event type. + /// + /// + /// The type of the event for which a handler is registered. + /// + /// + /// The type of the event handler that is registered. + /// + /// + /// The current . + /// + public static void RegisterEventHandler(this ContainerBuilder target) + where TEvent : class, IEvent + where TEventHandler : class, IEventHandler => target.RegisterCommandHandler(); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring.Autofac/README.md b/src/RapidField.SolidInstruments.EventAuthoring.Autofac/README.md new file mode 100644 index 00000000..c1eb80df --- /dev/null +++ b/src/RapidField.SolidInstruments.EventAuthoring.Autofac/README.md @@ -0,0 +1,57 @@ + + +[![Solid Instruments](../../SolidInstruments.Logo.Color.Transparent.500w.png)](../../README.md) +
     +![EventAuthoring](../../doc/images/Label.EventAuthoring.300w.png) +- - - + +# RapidField.SolidInstruments.EventAuthoring.Autofac + +This document describes the purpose of the [`RapidField.SolidInstruments.EventAuthoring.Autofac`]() library and offers guidance with respect to licensing, installation and usage. + +## Features + +This library exposes the [**Autofac**](https://autofac.org/) IoC integration for the **Solid Instruments** event authoring abstractions. + +## License + +[![License](https://img.shields.io/github/license/rapidfield/solid-instruments?style=flat&color=lightseagreen&label=license&logo=open-access&logoColor=lightgrey)](../../LICENSE.txt) + +**Solid Instruments** is [MIT-licensed](https://en.wikipedia.org/wiki/MIT_License). Review the [license terms](../../LICENSE.txt) for more information. + +## Documentation + +[![Documentation](https://img.shields.io/badge/documentation-website-tan?style=flat&logo=buffer&logoColor=lightgrey)](https://www.solidinstruments.com/api/RapidField.SolidInstruments.EventAuthoring.Autofac.html) + +Check out the [**API Reference**](https://www.solidinstruments.com/api/RapidField.SolidInstruments.EventAuthoring.Autofac.html) for usage examples and reference documentation. + +## Installation + +[![Version](https://img.shields.io/nuget/vpre/RapidField.SolidInstruments.EventAuthoring.Autofac?style=flat&color=blue&label=version&logo=nuget&logoColor=lightgrey)](https://www.nuget.org/packages/RapidField.SolidInstruments.EventAuthoring.Autofac) +[![Downloads](https://img.shields.io/nuget/dt/RapidField.SolidInstruments.EventAuthoring.Autofac?style=flat&color=blue&logo=nuget&logoColor=lightgrey)](https://www.nuget.org/packages/RapidField.SolidInstruments.EventAuthoring.Autofac) + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.EventAuthoring.Autofac +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.EventAuthoring.Autofac +``` + +
+ +- - - + +
+ +[![RapidField](../../RapidField.Logo.Color.Black.Transparent.200w.png)](https://www.rapidfield.com) + +###### Copyright (c) RapidField LLC. All rights reserved. "RapidField" and "Solid Instruments" are trademarks of RapidField LLC. \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring.Autofac/RapidField.SolidInstruments.EventAuthoring.Autofac.csproj b/src/RapidField.SolidInstruments.EventAuthoring.Autofac/RapidField.SolidInstruments.EventAuthoring.Autofac.csproj new file mode 100644 index 00000000..2c69ae69 --- /dev/null +++ b/src/RapidField.SolidInstruments.EventAuthoring.Autofac/RapidField.SolidInstruments.EventAuthoring.Autofac.csproj @@ -0,0 +1,51 @@ + + + + + Solid Instruments contributors + RapidField + Copyright (c) RapidField LLC. All rights reserved. + Solid Instruments + This library exposes the Autofac IoC integration for the Solid Instruments event authoring abstractions. + $(BuildVersion) + netstandard2.1 + latest + git + https://github.com/rapidfield/solid-instruments + true + true + LICENSE.txt + https://www.solidinstruments.com + Icon.EventAuthoring.128w.png + solid-instruments;event-authoring;events;logging;inversion-of-control;ioc;dependency-injection;di;autofac + + + bin\Debug\netstandard2.1\RapidField.SolidInstruments.EventAuthoring.Autofac.xml + true + + + + bin\Release\netstandard2.1\RapidField.SolidInstruments.EventAuthoring.Autofac.xml + true + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring.DotNetNative/AssemblyAttributes.cs b/src/RapidField.SolidInstruments.EventAuthoring.DotNetNative/AssemblyAttributes.cs new file mode 100644 index 00000000..a32e4acd --- /dev/null +++ b/src/RapidField.SolidInstruments.EventAuthoring.DotNetNative/AssemblyAttributes.cs @@ -0,0 +1,7 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System.Runtime.CompilerServices; + +[assembly: DisablePrivateReflection()] \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring.DotNetNative/Extensions/IServiceCollectionExtensions.cs b/src/RapidField.SolidInstruments.EventAuthoring.DotNetNative/Extensions/IServiceCollectionExtensions.cs new file mode 100644 index 00000000..c1aa8588 --- /dev/null +++ b/src/RapidField.SolidInstruments.EventAuthoring.DotNetNative/Extensions/IServiceCollectionExtensions.cs @@ -0,0 +1,35 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Microsoft.Extensions.DependencyInjection; +using RapidField.SolidInstruments.Command.DotNetNative.Extensions; + +namespace RapidField.SolidInstruments.EventAuthoring.DotNetNative.Extensions +{ + /// + /// Extends the interface with native .NET inversion of control features to support event + /// authoring. + /// + public static class IServiceCollectionExtensions + { + /// + /// Registers a transient event handler for the specified event type. + /// + /// + /// The type of the event for which a handler is registered. + /// + /// + /// The type of the event handler that is registered. + /// + /// + /// The current . + /// + /// + /// The resulting . + /// + public static IServiceCollection AddEventHandler(this IServiceCollection target) + where TEvent : class, IEvent + where TEventHandler : class, IEventHandler => target.AddCommandHandler(); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring.DotNetNative/README.md b/src/RapidField.SolidInstruments.EventAuthoring.DotNetNative/README.md new file mode 100644 index 00000000..a5b78230 --- /dev/null +++ b/src/RapidField.SolidInstruments.EventAuthoring.DotNetNative/README.md @@ -0,0 +1,57 @@ + + +[![Solid Instruments](../../SolidInstruments.Logo.Color.Transparent.500w.png)](../../README.md) +
     +![EventAuthoring](../../doc/images/Label.EventAuthoring.300w.png) +- - - + +# RapidField.SolidInstruments.EventAuthoring.DotNetNative + +This document describes the purpose of the [`RapidField.SolidInstruments.EventAuthoring.DotNetNative`]() library and offers guidance with respect to licensing, installation and usage. + +## Features + +This library exposes the native .NET IoC integration for the **Solid Instruments** event authoring abstractions. + +## License + +[![License](https://img.shields.io/github/license/rapidfield/solid-instruments?style=flat&color=lightseagreen&label=license&logo=open-access&logoColor=lightgrey)](../../LICENSE.txt) + +**Solid Instruments** is [MIT-licensed](https://en.wikipedia.org/wiki/MIT_License). Review the [license terms](../../LICENSE.txt) for more information. + +## Documentation + +[![Documentation](https://img.shields.io/badge/documentation-website-tan?style=flat&logo=buffer&logoColor=lightgrey)](https://www.solidinstruments.com/api/RapidField.SolidInstruments.EventAuthoring.DotNetNative.html) + +Check out the [**API Reference**](https://www.solidinstruments.com/api/RapidField.SolidInstruments.EventAuthoring.DotNetNative.html) for usage examples and reference documentation. + +## Installation + +[![Version](https://img.shields.io/nuget/vpre/RapidField.SolidInstruments.EventAuthoring.DotNetNative?style=flat&color=blue&label=version&logo=nuget&logoColor=lightgrey)](https://www.nuget.org/packages/RapidField.SolidInstruments.EventAuthoring.DotNetNative) +[![Downloads](https://img.shields.io/nuget/dt/RapidField.SolidInstruments.EventAuthoring.DotNetNative?style=flat&color=blue&logo=nuget&logoColor=lightgrey)](https://www.nuget.org/packages/RapidField.SolidInstruments.EventAuthoring.DotNetNative) + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.EventAuthoring.DotNetNative +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.EventAuthoring.DotNetNative +``` + +
+ +- - - + +
+ +[![RapidField](../../RapidField.Logo.Color.Black.Transparent.200w.png)](https://www.rapidfield.com) + +###### Copyright (c) RapidField LLC. All rights reserved. "RapidField" and "Solid Instruments" are trademarks of RapidField LLC. \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring.DotNetNative/RapidField.SolidInstruments.EventAuthoring.DotNetNative.csproj b/src/RapidField.SolidInstruments.EventAuthoring.DotNetNative/RapidField.SolidInstruments.EventAuthoring.DotNetNative.csproj new file mode 100644 index 00000000..e173f137 --- /dev/null +++ b/src/RapidField.SolidInstruments.EventAuthoring.DotNetNative/RapidField.SolidInstruments.EventAuthoring.DotNetNative.csproj @@ -0,0 +1,51 @@ + + + + + Solid Instruments contributors + RapidField + Copyright (c) RapidField LLC. All rights reserved. + Solid Instruments + This library exposes the native .NET IoC integration for the Solid Instruments event authoring abstractions. + $(BuildVersion) + netstandard2.1 + latest + git + https://github.com/rapidfield/solid-instruments + true + true + LICENSE.txt + https://www.solidinstruments.com + Icon.EventAuthoring.128w.png + solid-instruments;event-authoring;events;logging;inversion-of-control;ioc;dependency-injection;di + + + bin\Debug\netstandard2.1\RapidField.SolidInstruments.EventAuthoring.DotNetNative.xml + true + + + + bin\Release\netstandard2.1\RapidField.SolidInstruments.EventAuthoring.DotNetNative.xml + true + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring/EventHandler.cs b/src/RapidField.SolidInstruments.EventAuthoring/EventHandler.cs index 21a2bdc0..f149465e 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/EventHandler.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/EventHandler.cs @@ -10,10 +10,13 @@ namespace RapidField.SolidInstruments.EventAuthoring /// /// Processes a single . /// + /// + /// is the default implementation of . + /// /// /// The type of the event that is processed by the handler. /// - public abstract class EventHandler : CommandHandler + public abstract class EventHandler : CommandHandler, IEventHandler where TEvent : class, IEvent { /// diff --git a/src/RapidField.SolidInstruments.EventAuthoring/Extensions/EventCategoryExtensions.cs b/src/RapidField.SolidInstruments.EventAuthoring/Extensions/EventCategoryExtensions.cs index 92760982..72b838d8 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/Extensions/EventCategoryExtensions.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/Extensions/EventCategoryExtensions.cs @@ -7,7 +7,7 @@ namespace RapidField.SolidInstruments.EventAuthoring.Extensions { /// - /// Extends the enumeration with general purpose features. + /// Extends the enumeration with event authoring features. /// public static class EventCategoryExtensions { diff --git a/src/RapidField.SolidInstruments.EventAuthoring/Extensions/EventVerbosityExtensions.cs b/src/RapidField.SolidInstruments.EventAuthoring/Extensions/EventVerbosityExtensions.cs index c2ce710c..e56327ee 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/Extensions/EventVerbosityExtensions.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/Extensions/EventVerbosityExtensions.cs @@ -7,7 +7,7 @@ namespace RapidField.SolidInstruments.EventAuthoring.Extensions { /// - /// Extends the enumeration with general purpose features. + /// Extends the enumeration with event authoring features. /// public static class EventVerbosityExtensions { diff --git a/src/RapidField.SolidInstruments.EventAuthoring/Extensions/TransactionEventOutcomeExtensions.cs b/src/RapidField.SolidInstruments.EventAuthoring/Extensions/TransactionEventOutcomeExtensions.cs index 5a0759e2..28411708 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/Extensions/TransactionEventOutcomeExtensions.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/Extensions/TransactionEventOutcomeExtensions.cs @@ -7,7 +7,7 @@ namespace RapidField.SolidInstruments.EventAuthoring.Extensions { /// - /// Extends the enumeration with general purpose features. + /// Extends the enumeration with event authoring features. /// public static class TransactionEventOutcomeExtensions { diff --git a/src/RapidField.SolidInstruments.EventAuthoring/Extensions/UserActionEventOutcomeExtensions.cs b/src/RapidField.SolidInstruments.EventAuthoring/Extensions/UserActionEventOutcomeExtensions.cs index ff320a6a..a14edba5 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/Extensions/UserActionEventOutcomeExtensions.cs +++ b/src/RapidField.SolidInstruments.EventAuthoring/Extensions/UserActionEventOutcomeExtensions.cs @@ -7,7 +7,7 @@ namespace RapidField.SolidInstruments.EventAuthoring.Extensions { /// - /// Extends the enumeration with general purpose features. + /// Extends the enumeration with event authoring features. /// public static class UserActionEventOutcomeExtensions { diff --git a/src/RapidField.SolidInstruments.EventAuthoring/IEventHandler.cs b/src/RapidField.SolidInstruments.EventAuthoring/IEventHandler.cs new file mode 100644 index 00000000..2771a195 --- /dev/null +++ b/src/RapidField.SolidInstruments.EventAuthoring/IEventHandler.cs @@ -0,0 +1,19 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; + +namespace RapidField.SolidInstruments.EventAuthoring +{ + /// + /// Processes a single . + /// + /// + /// The type of the event that is processed by the handler. + /// + public interface IEventHandler : ICommandHandler + where TEvent : class, IEvent + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring/RapidField.SolidInstruments.EventAuthoring.csproj b/src/RapidField.SolidInstruments.EventAuthoring/RapidField.SolidInstruments.EventAuthoring.csproj index 8f22ce89..ff3bcbb8 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring/RapidField.SolidInstruments.EventAuthoring.csproj +++ b/src/RapidField.SolidInstruments.EventAuthoring/RapidField.SolidInstruments.EventAuthoring.csproj @@ -19,15 +19,15 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in LICENSE.txt https://www.solidinstruments.com Icon.EventAuthoring.128w.png - solid-instruments;event-authoring;events;logging; + solid-instruments;event-authoring;events;logging - bin\Debug\netstandard2.0\RapidField.SolidInstruments.EventAuthoring.xml + bin\Debug\netstandard2.1\RapidField.SolidInstruments.EventAuthoring.xml true - bin\Release\netstandard2.0\RapidField.SolidInstruments.EventAuthoring.xml + bin\Release\netstandard2.1\RapidField.SolidInstruments.EventAuthoring.xml true diff --git a/src/RapidField.SolidInstruments.InversionOfControl.Autofac/Extensions/ContainerBuilderExtensions.cs b/src/RapidField.SolidInstruments.InversionOfControl.Autofac/Extensions/ContainerBuilderExtensions.cs new file mode 100644 index 00000000..3287bac8 --- /dev/null +++ b/src/RapidField.SolidInstruments.InversionOfControl.Autofac/Extensions/ContainerBuilderExtensions.cs @@ -0,0 +1,26 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Autofac; +using Microsoft.Extensions.Configuration; + +namespace RapidField.SolidInstruments.InversionOfControl.Autofac.Extensions +{ + /// + /// Extends the class with inversion of control features. + /// + public static class ContainerBuilderExtensions + { + /// + /// Registers the specified instance as a singleton. + /// + /// + /// The current . + /// + /// + /// Configuration information for the application. + /// + public static void RegisterApplicationConfiguration(this ContainerBuilder target, IConfiguration applicationConfiguration) => target.RegisterInstance(applicationConfiguration).SingleInstance(); + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.InversionOfControl.Autofac/RapidField.SolidInstruments.InversionOfControl.Autofac.csproj b/src/RapidField.SolidInstruments.InversionOfControl.Autofac/RapidField.SolidInstruments.InversionOfControl.Autofac.csproj index dfd359f1..cc78680e 100644 --- a/src/RapidField.SolidInstruments.InversionOfControl.Autofac/RapidField.SolidInstruments.InversionOfControl.Autofac.csproj +++ b/src/RapidField.SolidInstruments.InversionOfControl.Autofac/RapidField.SolidInstruments.InversionOfControl.Autofac.csproj @@ -22,12 +22,12 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in solid-instruments;inversion-of-control;ioc;dependency-injection;di;container-abstraction;autofac - bin\Debug\netstandard2.0\RapidField.SolidInstruments.InversionOfControl.Autofac.xml + bin\Debug\netstandard2.1\RapidField.SolidInstruments.InversionOfControl.Autofac.xml true - bin\Release\netstandard2.0\RapidField.SolidInstruments.InversionOfControl.Autofac.xml + bin\Release\netstandard2.1\RapidField.SolidInstruments.InversionOfControl.Autofac.xml false NU1605 diff --git a/src/RapidField.SolidInstruments.InversionOfControl.DotNetNative/Extensions/IServiceCollectionExtensions.cs b/src/RapidField.SolidInstruments.InversionOfControl.DotNetNative/Extensions/IServiceCollectionExtensions.cs index 6e150d5a..441f2319 100644 --- a/src/RapidField.SolidInstruments.InversionOfControl.DotNetNative/Extensions/IServiceCollectionExtensions.cs +++ b/src/RapidField.SolidInstruments.InversionOfControl.DotNetNative/Extensions/IServiceCollectionExtensions.cs @@ -15,7 +15,21 @@ namespace RapidField.SolidInstruments.InversionOfControl.DotNetNative.Extensions public static class IServiceCollectionExtensions { /// - /// Registers an DotNetNative dependency engine and provider factory with the current . + /// Registers the specified instance as a singleton. + /// + /// + /// The current . + /// + /// + /// Configuration information for the application. + /// + /// + /// The resulting . + /// + public static IServiceCollection AddApplicationConfiguration(this IServiceCollection target, IConfiguration applicationConfiguration) => target.AddSingleton(applicationConfiguration); + + /// + /// Registers an native .NET dependency engine and provider factory with the current . /// /// /// The package that configures the engine. @@ -42,7 +56,7 @@ public static IServiceCollection AddDependencyPackage(this IServiceCol where TPackage : DotNetNativeDependencyPackage, new() => target.AddDependencyPackage(applicationConfiguration); /// - /// Registers an DotNetNative dependency engine and provider factory with the current . + /// Registers an native .NET dependency engine and provider factory with the current . /// /// /// The package that configures the engine. diff --git a/src/RapidField.SolidInstruments.InversionOfControl.DotNetNative/RapidField.SolidInstruments.InversionOfControl.DotNetNative.csproj b/src/RapidField.SolidInstruments.InversionOfControl.DotNetNative/RapidField.SolidInstruments.InversionOfControl.DotNetNative.csproj index a47aae29..e35d5eb0 100644 --- a/src/RapidField.SolidInstruments.InversionOfControl.DotNetNative/RapidField.SolidInstruments.InversionOfControl.DotNetNative.csproj +++ b/src/RapidField.SolidInstruments.InversionOfControl.DotNetNative/RapidField.SolidInstruments.InversionOfControl.DotNetNative.csproj @@ -22,12 +22,12 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in solid-instruments;inversion-of-control;ioc;dependency-injection;di;container-abstraction - bin\Debug\netstandard2.0\RapidField.SolidInstruments.InversionOfControl.DotNetNative.xml + bin\Debug\netstandard2.1\RapidField.SolidInstruments.InversionOfControl.DotNetNative.xml true - bin\Release\netstandard2.0\RapidField.SolidInstruments.InversionOfControl.DotNetNative.xml + bin\Release\netstandard2.1\RapidField.SolidInstruments.InversionOfControl.DotNetNative.xml false NU1605 diff --git a/src/RapidField.SolidInstruments.InversionOfControl/RapidField.SolidInstruments.InversionOfControl.csproj b/src/RapidField.SolidInstruments.InversionOfControl/RapidField.SolidInstruments.InversionOfControl.csproj index dcceaac4..32dcf2f3 100644 --- a/src/RapidField.SolidInstruments.InversionOfControl/RapidField.SolidInstruments.InversionOfControl.csproj +++ b/src/RapidField.SolidInstruments.InversionOfControl/RapidField.SolidInstruments.InversionOfControl.csproj @@ -22,12 +22,12 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in solid-instruments;inversion-of-control;ioc;dependency-injection;di;container-abstraction - bin\Debug\netstandard2.0\RapidField.SolidInstruments.InversionOfControl.xml + bin\Debug\netstandard2.1\RapidField.SolidInstruments.InversionOfControl.xml true - bin\Release\netstandard2.0\RapidField.SolidInstruments.InversionOfControl.xml + bin\Release\netstandard2.1\RapidField.SolidInstruments.InversionOfControl.xml true diff --git a/src/RapidField.SolidInstruments.Mathematics/RapidField.SolidInstruments.Mathematics.csproj b/src/RapidField.SolidInstruments.Mathematics/RapidField.SolidInstruments.Mathematics.csproj index 4fd3cb08..f1294693 100644 --- a/src/RapidField.SolidInstruments.Mathematics/RapidField.SolidInstruments.Mathematics.csproj +++ b/src/RapidField.SolidInstruments.Mathematics/RapidField.SolidInstruments.Mathematics.csproj @@ -22,12 +22,12 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in solid-instruments;mathematics;sequences;fibonacci;primes;physics;unit-conversion;area;length;mass;temperature;volume;interpolation - bin\Debug\netstandard2.0\RapidField.SolidInstruments.Mathematics.xml + bin\Debug\netstandard2.1\RapidField.SolidInstruments.Mathematics.xml true - bin\Release\netstandard2.0\RapidField.SolidInstruments.Mathematics.xml + bin\Release\netstandard2.1\RapidField.SolidInstruments.Mathematics.xml true diff --git a/src/RapidField.SolidInstruments.Messaging.Autofac.Asb/AssemblyAttributes.cs b/src/RapidField.SolidInstruments.Messaging.Autofac.Asb/AssemblyAttributes.cs new file mode 100644 index 00000000..a32e4acd --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.Autofac.Asb/AssemblyAttributes.cs @@ -0,0 +1,7 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System.Runtime.CompilerServices; + +[assembly: DisablePrivateReflection()] \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.Autofac.Asb/README.md b/src/RapidField.SolidInstruments.Messaging.Autofac.Asb/README.md new file mode 100644 index 00000000..65a5225b --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.Autofac.Asb/README.md @@ -0,0 +1,57 @@ + + +[![Solid Instruments](../../SolidInstruments.Logo.Color.Transparent.500w.png)](../../README.md) +
     +![Messaging](../../doc/images/Label.Messaging.300w.png) +- - - + +# RapidField.SolidInstruments.Messaging.Autofac.Asb + +This document describes the purpose of the [`RapidField.SolidInstruments.Messaging.Autofac.Asb`]() library and offers guidance with respect to licensing, installation and usage. + +## Features + +This library exposes the [**Autofac**](https://autofac.org/) IoC integration for the **Solid Instruments** [**Azure Service Bus**](https://docs.microsoft.com/en-us/azure/service-bus-messaging/) messaging abstractions. + +## License + +[![License](https://img.shields.io/github/license/rapidfield/solid-instruments?style=flat&color=lightseagreen&label=license&logo=open-access&logoColor=lightgrey)](../../LICENSE.txt) + +**Solid Instruments** is [MIT-licensed](https://en.wikipedia.org/wiki/MIT_License). Review the [license terms](../../LICENSE.txt) for more information. + +## Documentation + +[![Documentation](https://img.shields.io/badge/documentation-website-tan?style=flat&logo=buffer&logoColor=lightgrey)](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Messaging.Autofac.Asb.html) + +Check out the [**API Reference**](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Messaging.Autofac.Asb.html) for usage examples and reference documentation. + +## Installation + +[![Version](https://img.shields.io/nuget/vpre/RapidField.SolidInstruments.Messaging.Autofac.Asb?style=flat&color=blue&label=version&logo=nuget&logoColor=lightgrey)](https://www.nuget.org/packages/RapidField.SolidInstruments.Messaging.Autofac.Asb) +[![Downloads](https://img.shields.io/nuget/dt/RapidField.SolidInstruments.Messaging.Autofac.Asb?style=flat&color=blue&logo=nuget&logoColor=lightgrey)](https://www.nuget.org/packages/RapidField.SolidInstruments.Messaging.Autofac.Asb) + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.Messaging.Autofac.Asb +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.Messaging.Autofac.Asb +``` + +
+ +- - - + +
+ +[![RapidField](../../RapidField.Logo.Color.Black.Transparent.200w.png)](https://www.rapidfield.com) + +###### Copyright (c) RapidField LLC. All rights reserved. "RapidField" and "Solid Instruments" are trademarks of RapidField LLC. \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.Autofac.Asb/RapidField.SolidInstruments.Messaging.Autofac.Asb.csproj b/src/RapidField.SolidInstruments.Messaging.Autofac.Asb/RapidField.SolidInstruments.Messaging.Autofac.Asb.csproj new file mode 100644 index 00000000..030619ad --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.Autofac.Asb/RapidField.SolidInstruments.Messaging.Autofac.Asb.csproj @@ -0,0 +1,52 @@ + + + + + Solid Instruments contributors + RapidField + Copyright (c) RapidField LLC. All rights reserved. + Solid Instruments + This library exposes the Autofac IoC integration for the Solid Instruments Azure Service Bus messaging abstractions. + $(BuildVersion) + netstandard2.1 + latest + git + https://github.com/rapidfield/solid-instruments + true + true + LICENSE.txt + https://www.solidinstruments.com + Icon.Messaging.128w.png + solid-instruments;messaging;service-bus;bus;request-response;pub-sub;eventing;azure-service-bus;inversion-of-control;ioc;dependency-injection;di;autofac + + + bin\Debug\netstandard2.1\RapidField.SolidInstruments.Messaging.Autofac.Asb.xml + true + + + + bin\Release\netstandard2.1\RapidField.SolidInstruments.Messaging.Autofac.Asb.xml + true + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.Autofac.Rmq/AssemblyAttributes.cs b/src/RapidField.SolidInstruments.Messaging.Autofac.Rmq/AssemblyAttributes.cs new file mode 100644 index 00000000..a32e4acd --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.Autofac.Rmq/AssemblyAttributes.cs @@ -0,0 +1,7 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System.Runtime.CompilerServices; + +[assembly: DisablePrivateReflection()] \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.Autofac.Rmq/README.md b/src/RapidField.SolidInstruments.Messaging.Autofac.Rmq/README.md new file mode 100644 index 00000000..f5d9aa39 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.Autofac.Rmq/README.md @@ -0,0 +1,57 @@ + + +[![Solid Instruments](../../SolidInstruments.Logo.Color.Transparent.500w.png)](../../README.md) +
     +![Messaging](../../doc/images/Label.Messaging.300w.png) +- - - + +# RapidField.SolidInstruments.Messaging.Autofac.Rmq + +This document describes the purpose of the [`RapidField.SolidInstruments.Messaging.Autofac.Rmq`]() library and offers guidance with respect to licensing, installation and usage. + +## Features + +This library exposes the [**Autofac**](https://autofac.org/) IoC integration for the **Solid Instruments** [**RabbitMQ**](https://www.rabbitmq.com/) messaging abstractions. + +## License + +[![License](https://img.shields.io/github/license/rapidfield/solid-instruments?style=flat&color=lightseagreen&label=license&logo=open-access&logoColor=lightgrey)](../../LICENSE.txt) + +**Solid Instruments** is [MIT-licensed](https://en.wikipedia.org/wiki/MIT_License). Review the [license terms](../../LICENSE.txt) for more information. + +## Documentation + +[![Documentation](https://img.shields.io/badge/documentation-website-tan?style=flat&logo=buffer&logoColor=lightgrey)](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Messaging.Autofac.Rmq.html) + +Check out the [**API Reference**](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Messaging.Autofac.Rmq.html) for usage examples and reference documentation. + +## Installation + +[![Version](https://img.shields.io/nuget/vpre/RapidField.SolidInstruments.Messaging.Autofac.Rmq?style=flat&color=blue&label=version&logo=nuget&logoColor=lightgrey)](https://www.nuget.org/packages/RapidField.SolidInstruments.Messaging.Autofac.Rmq) +[![Downloads](https://img.shields.io/nuget/dt/RapidField.SolidInstruments.Messaging.Autofac.Rmq?style=flat&color=blue&logo=nuget&logoColor=lightgrey)](https://www.nuget.org/packages/RapidField.SolidInstruments.Messaging.Autofac.Rmq) + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.Messaging.Autofac.Rmq +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.Messaging.Autofac.Rmq +``` + +
+ +- - - + +
+ +[![RapidField](../../RapidField.Logo.Color.Black.Transparent.200w.png)](https://www.rapidfield.com) + +###### Copyright (c) RapidField LLC. All rights reserved. "RapidField" and "Solid Instruments" are trademarks of RapidField LLC. \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.Autofac.Rmq/RapidField.SolidInstruments.Messaging.Autofac.Rmq.csproj b/src/RapidField.SolidInstruments.Messaging.Autofac.Rmq/RapidField.SolidInstruments.Messaging.Autofac.Rmq.csproj new file mode 100644 index 00000000..877d7d3f --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.Autofac.Rmq/RapidField.SolidInstruments.Messaging.Autofac.Rmq.csproj @@ -0,0 +1,52 @@ + + + + + Solid Instruments contributors + RapidField + Copyright (c) RapidField LLC. All rights reserved. + Solid Instruments + This library exposes the Autofac IoC integration for the Solid Instruments RabbitMQ messaging abstractions. + $(BuildVersion) + netstandard2.1 + latest + git + https://github.com/rapidfield/solid-instruments + true + true + LICENSE.txt + https://www.solidinstruments.com + Icon.Messaging.128w.png + solid-instruments;messaging;service-bus;bus;request-response;pub-sub;eventing;rabbitmq;inversion-of-control;ioc;dependency-injection;di;autofac + + + bin\Debug\netstandard2.1\RapidField.SolidInstruments.Messaging.Autofac.Rmq.xml + true + + + + bin\Release\netstandard2.1\RapidField.SolidInstruments.Messaging.Autofac.Rmq.xml + true + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.Autofac/AssemblyAttributes.cs b/src/RapidField.SolidInstruments.Messaging.Autofac/AssemblyAttributes.cs new file mode 100644 index 00000000..a32e4acd --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.Autofac/AssemblyAttributes.cs @@ -0,0 +1,7 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System.Runtime.CompilerServices; + +[assembly: DisablePrivateReflection()] \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.Autofac/Extensions/ContainerBuilderExtensions.cs b/src/RapidField.SolidInstruments.Messaging.Autofac/Extensions/ContainerBuilderExtensions.cs new file mode 100644 index 00000000..d579c1c2 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.Autofac/Extensions/ContainerBuilderExtensions.cs @@ -0,0 +1,239 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Autofac; +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Command.Autofac.Extensions; +using RapidField.SolidInstruments.EventAuthoring; +using RapidField.SolidInstruments.EventAuthoring.Autofac.Extensions; +using RapidField.SolidInstruments.Messaging.CommandMessages; +using RapidField.SolidInstruments.Messaging.EventMessages; +using RapidField.SolidInstruments.Messaging.InMemory; +using RapidField.SolidInstruments.Messaging.TransportPrimitives; + +namespace RapidField.SolidInstruments.Messaging.Autofac.Extensions +{ + /// + /// Extends the class with inversion of control features to support messaging. + /// + public static class ContainerBuilderExtensions + { + /// + /// Registers a transient command message listener for the specified message type. + /// + /// + /// The type of the associated command. + /// + /// + /// The type of the command message for which a listener is registered. + /// + /// + /// The current . + /// + public static void RegisterCommandMessageListener(this ContainerBuilder target) + where TCommand : class, ICommand + where TMessage : class, ICommandMessage => target.RegisterMessageListener>(); + + /// + /// Registers a transient command message listener and an associated command handler for the specified message type. + /// + /// + /// The type of the associated command. + /// + /// + /// The type of the command message for which a listener is registered. + /// + /// + /// The type of the command handler that is registered. + /// + /// + /// The current . + /// + public static void RegisterCommandMessageListenerAndHandler(this ContainerBuilder target) + where TCommand : class, ICommand + where TMessage : class, ICommandMessage + where TCommandHandler : class, ICommandHandler + { + target.RegisterCommandMessageListener(); + target.RegisterCommandHandler(); + } + + /// + /// Registers a transient command message transmitter for the specified message type. + /// + /// + /// The type of the associated command. + /// + /// + /// The type of the command message for which a transmitter is registered. + /// + /// + /// The current . + /// + public static void RegisterCommandMessageTransmitter(this ContainerBuilder target) + where TCommand : class, ICommand + where TMessage : class, ICommandMessage => target.RegisterMessageTransmitter>(); + + /// + /// Registers a transient event message listener for the specified message type. + /// + /// + /// The type of the associated event. + /// + /// + /// The type of the event message for which a listener is registered. + /// + /// + /// The current . + /// + public static void RegisterEventMessageListener(this ContainerBuilder target) + where TEvent : class, IEvent + where TMessage : class, IEventMessage => target.RegisterMessageListener>(); + + /// + /// Registers a transient event message listener and an associated event handler for the specified message type. + /// + /// + /// The type of the associated event. + /// + /// + /// The type of the event message for which a listener is registered. + /// + /// + /// The type of the event handler that is registered. + /// + /// + /// The current . + /// + public static void RegisterEventMessageListenerAndHandler(this ContainerBuilder target) + where TEvent : class, IEvent + where TMessage : class, IEventMessage + where TEventHandler : class, IEventHandler + { + target.RegisterEventMessageListener(); + target.RegisterEventHandler(); + } + + /// + /// Registers a transient event message transmitter for the specified message type. + /// + /// + /// The type of the associated event. + /// + /// + /// The type of the event message for which a transmitter is registered. + /// + /// + /// The current . + /// + public static void RegisterEventMessageTransmitter(this ContainerBuilder target) + where TEvent : class, IEvent + where TMessage : class, IEventMessage => target.RegisterMessageTransmitter>(); + + /// + /// Registers a transient message listener for the specified message type. + /// + /// + /// The type of the message for which a listener is registered. + /// + /// + /// The type of the message listener that is registered. + /// + /// + /// The current . + /// + public static void RegisterMessageListener(this ContainerBuilder target) + where TMessage : class, IMessage + where TMessageListener : class, IMessageListener => target.RegisterType().As>().InstancePerDependency(); + + /// + /// Registers a transient message transmitter for the specified message type. + /// + /// + /// The type of the message for which a transmitter is registered. + /// + /// + /// The type of the message transmitter that is registered. + /// + /// + /// The current . + /// + public static void RegisterMessageTransmitter(this ContainerBuilder target) + where TMessage : class, IMessage + where TMessageTransmitter : class, IMessageTransmitter => target.RegisterCommandHandler(); + + /// + /// Registers a transient message listener for the specified request message type. + /// + /// + /// The type of the request message that is transmitted by the listener. + /// + /// + /// The type of the response message that is transmitted in response to the request. + /// + /// + /// The type of the message listener that is registered. + /// + /// + /// The current . + /// + public static void RegisterRequestMessageListener(this ContainerBuilder target) + where TRequestMessage : class, IRequestMessage + where TResponseMessage : class, IResponseMessage + where TMessageListener : class, IMessageListener => target.RegisterType().As>().InstancePerDependency(); + + /// + /// Registers a transient message transmitter for the specified request message type. + /// + /// + /// The type of the request message that is transmitted by the transmitter. + /// + /// + /// The type of the response message that is transmitted in response to the request. + /// + /// + /// The current . + /// + public static void RegisterRequestMessageTransmitter(this ContainerBuilder target) + where TRequestMessage : class, IRequestMessage + where TResponseMessage : class, IResponseMessage => target.RegisterRequestMessageTransmitter>(); + + /// + /// Registers a transient message transmitter for the specified request message type. + /// + /// + /// The type of the request message that is transmitted by the transmitter. + /// + /// + /// The type of the response message that is transmitted in response to the request. + /// + /// + /// The type of the message transmitter that is registered. + /// + /// + /// The current . + /// + public static void RegisterRequestMessageTransmitter(this ContainerBuilder target) + where TRequestMessage : class, IRequestMessage + where TResponseMessage : class, IResponseMessage + where TMessageTransmitter : class, IMessageTransmitter => target.RegisterCommandHandler(); + + /// + /// Registers a collection of types that establish support for in-memory service bus functionality. + /// + /// + /// The current . + /// + public static void RegisterSupportingTypesForInMemoryMessaging(this ContainerBuilder target) + { + target.RegisterInstance(MessageTransport.Instance).SingleInstance(); + target.RegisterInstance(MessageTransport.Instance.CreateConnection()).SingleInstance(); + target.RegisterType().As>().AsSelf().InstancePerLifetimeScope(); + target.RegisterType().As>().AsSelf().InstancePerLifetimeScope(); + target.RegisterType().As().AsSelf().InstancePerLifetimeScope(); + target.RegisterType().As().AsSelf().SingleInstance(); + target.RegisterType().As().AsSelf().SingleInstance(); + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.Autofac/README.md b/src/RapidField.SolidInstruments.Messaging.Autofac/README.md new file mode 100644 index 00000000..d8da4573 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.Autofac/README.md @@ -0,0 +1,57 @@ + + +[![Solid Instruments](../../SolidInstruments.Logo.Color.Transparent.500w.png)](../../README.md) +
     +![Messaging](../../doc/images/Label.Messaging.300w.png) +- - - + +# RapidField.SolidInstruments.Messaging.Autofac + +This document describes the purpose of the [`RapidField.SolidInstruments.Messaging.Autofac`]() library and offers guidance with respect to licensing, installation and usage. + +## Features + +This library exposes the [**Autofac**](https://autofac.org/) IoC integration for the **Solid Instruments** messaging abstractions. + +## License + +[![License](https://img.shields.io/github/license/rapidfield/solid-instruments?style=flat&color=lightseagreen&label=license&logo=open-access&logoColor=lightgrey)](../../LICENSE.txt) + +**Solid Instruments** is [MIT-licensed](https://en.wikipedia.org/wiki/MIT_License). Review the [license terms](../../LICENSE.txt) for more information. + +## Documentation + +[![Documentation](https://img.shields.io/badge/documentation-website-tan?style=flat&logo=buffer&logoColor=lightgrey)](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Messaging.Autofac.html) + +Check out the [**API Reference**](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Messaging.Autofac.html) for usage examples and reference documentation. + +## Installation + +[![Version](https://img.shields.io/nuget/vpre/RapidField.SolidInstruments.Messaging.Autofac?style=flat&color=blue&label=version&logo=nuget&logoColor=lightgrey)](https://www.nuget.org/packages/RapidField.SolidInstruments.Messaging.Autofac) +[![Downloads](https://img.shields.io/nuget/dt/RapidField.SolidInstruments.Messaging.Autofac?style=flat&color=blue&logo=nuget&logoColor=lightgrey)](https://www.nuget.org/packages/RapidField.SolidInstruments.Messaging.Autofac) + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.Messaging.Autofac +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.Messaging.Autofac +``` + +
+ +- - - + +
+ +[![RapidField](../../RapidField.Logo.Color.Black.Transparent.200w.png)](https://www.rapidfield.com) + +###### Copyright (c) RapidField LLC. All rights reserved. "RapidField" and "Solid Instruments" are trademarks of RapidField LLC. \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.Autofac/RapidField.SolidInstruments.Messaging.Autofac.csproj b/src/RapidField.SolidInstruments.Messaging.Autofac/RapidField.SolidInstruments.Messaging.Autofac.csproj new file mode 100644 index 00000000..5915fb5a --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.Autofac/RapidField.SolidInstruments.Messaging.Autofac.csproj @@ -0,0 +1,53 @@ + + + + + Solid Instruments contributors + RapidField + Copyright (c) RapidField LLC. All rights reserved. + Solid Instruments + This library exposes the Autofac IoC integration for the Solid Instruments messaging abstractions. + $(BuildVersion) + netstandard2.1 + latest + git + https://github.com/rapidfield/solid-instruments + true + true + LICENSE.txt + https://www.solidinstruments.com + Icon.Messaging.128w.png + solid-instruments;messaging;service-bus;bus;request-response;pub-sub;eventing;inversion-of-control;ioc;dependency-injection;di;autofac + + + bin\Debug\netstandard2.1\RapidField.SolidInstruments.Messaging.Autofac.xml + true + + + + bin\Release\netstandard2.1\RapidField.SolidInstruments.Messaging.Autofac.xml + true + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/RapidField.SolidInstruments.Messaging.AzureServiceBus.csproj b/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/RapidField.SolidInstruments.Messaging.AzureServiceBus.csproj index a78e9486..c43bba5f 100644 --- a/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/RapidField.SolidInstruments.Messaging.AzureServiceBus.csproj +++ b/src/RapidField.SolidInstruments.Messaging.AzureServiceBus/RapidField.SolidInstruments.Messaging.AzureServiceBus.csproj @@ -22,12 +22,12 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in solid-instruments;messaging;service-bus;bus;request-response;pub-sub;eventing;azure-service-bus - bin\Debug\netstandard2.0\RapidField.SolidInstruments.Messaging.AzureServiceBus.xml + bin\Debug\netstandard2.1\RapidField.SolidInstruments.Messaging.AzureServiceBus.xml true - bin\Release\netstandard2.0\RapidField.SolidInstruments.Messaging.AzureServiceBus.xml + bin\Release\netstandard2.1\RapidField.SolidInstruments.Messaging.AzureServiceBus.xml true diff --git a/src/RapidField.SolidInstruments.Messaging.DotNetNative.Asb/AssemblyAttributes.cs b/src/RapidField.SolidInstruments.Messaging.DotNetNative.Asb/AssemblyAttributes.cs new file mode 100644 index 00000000..a32e4acd --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.DotNetNative.Asb/AssemblyAttributes.cs @@ -0,0 +1,7 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System.Runtime.CompilerServices; + +[assembly: DisablePrivateReflection()] \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.DotNetNative.Asb/README.md b/src/RapidField.SolidInstruments.Messaging.DotNetNative.Asb/README.md new file mode 100644 index 00000000..38eb762a --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.DotNetNative.Asb/README.md @@ -0,0 +1,57 @@ + + +[![Solid Instruments](../../SolidInstruments.Logo.Color.Transparent.500w.png)](../../README.md) +
     +![Messaging](../../doc/images/Label.Messaging.300w.png) +- - - + +# RapidField.SolidInstruments.Messaging.DotNetNative.Asb + +This document describes the purpose of the [`RapidField.SolidInstruments.Messaging.DotNetNative.Asb`]() library and offers guidance with respect to licensing, installation and usage. + +## Features + +This library exposes the native .NET IoC integration for the **Solid Instruments** [**Azure Service Bus**](https://docs.microsoft.com/en-us/azure/service-bus-messaging/) messaging abstractions. + +## License + +[![License](https://img.shields.io/github/license/rapidfield/solid-instruments?style=flat&color=lightseagreen&label=license&logo=open-access&logoColor=lightgrey)](../../LICENSE.txt) + +**Solid Instruments** is [MIT-licensed](https://en.wikipedia.org/wiki/MIT_License). Review the [license terms](../../LICENSE.txt) for more information. + +## Documentation + +[![Documentation](https://img.shields.io/badge/documentation-website-tan?style=flat&logo=buffer&logoColor=lightgrey)](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Messaging.DotNetNative.Asb.html) + +Check out the [**API Reference**](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Messaging.DotNetNative.Asb.html) for usage examples and reference documentation. + +## Installation + +[![Version](https://img.shields.io/nuget/vpre/RapidField.SolidInstruments.Messaging.DotNetNative.Asb?style=flat&color=blue&label=version&logo=nuget&logoColor=lightgrey)](https://www.nuget.org/packages/RapidField.SolidInstruments.Messaging.DotNetNative.Asb) +[![Downloads](https://img.shields.io/nuget/dt/RapidField.SolidInstruments.Messaging.DotNetNative.Asb?style=flat&color=blue&logo=nuget&logoColor=lightgrey)](https://www.nuget.org/packages/RapidField.SolidInstruments.Messaging.DotNetNative.Asb) + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.Messaging.DotNetNative.Asb +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.Messaging.DotNetNative.Asb +``` + +
+ +- - - + +
+ +[![RapidField](../../RapidField.Logo.Color.Black.Transparent.200w.png)](https://www.rapidfield.com) + +###### Copyright (c) RapidField LLC. All rights reserved. "RapidField" and "Solid Instruments" are trademarks of RapidField LLC. \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.DotNetNative.Asb/RapidField.SolidInstruments.Messaging.DotNetNative.Asb.csproj b/src/RapidField.SolidInstruments.Messaging.DotNetNative.Asb/RapidField.SolidInstruments.Messaging.DotNetNative.Asb.csproj new file mode 100644 index 00000000..dbd6d4b3 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.DotNetNative.Asb/RapidField.SolidInstruments.Messaging.DotNetNative.Asb.csproj @@ -0,0 +1,52 @@ + + + + + Solid Instruments contributors + RapidField + Copyright (c) RapidField LLC. All rights reserved. + Solid Instruments + This library exposes the native .NET IoC integration for the Solid Instruments Azure Service Bus messaging abstractions. + $(BuildVersion) + netstandard2.1 + latest + git + https://github.com/rapidfield/solid-instruments + true + true + LICENSE.txt + https://www.solidinstruments.com + Icon.Messaging.128w.png + solid-instruments;messaging;service-bus;bus;request-response;pub-sub;eventing;azure-service-bus;inversion-of-control;ioc;dependency-injection;di + + + bin\Debug\netstandard2.1\RapidField.SolidInstruments.Messaging.DotNetNative.Asb.xml + true + + + + bin\Release\netstandard2.1\RapidField.SolidInstruments.Messaging.DotNetNative.Asb.xml + true + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq/AssemblyAttributes.cs b/src/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq/AssemblyAttributes.cs new file mode 100644 index 00000000..a32e4acd --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq/AssemblyAttributes.cs @@ -0,0 +1,7 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System.Runtime.CompilerServices; + +[assembly: DisablePrivateReflection()] \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq/README.md b/src/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq/README.md new file mode 100644 index 00000000..2d0f759d --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq/README.md @@ -0,0 +1,57 @@ + + +[![Solid Instruments](../../SolidInstruments.Logo.Color.Transparent.500w.png)](../../README.md) +
     +![Messaging](../../doc/images/Label.Messaging.300w.png) +- - - + +# RapidField.SolidInstruments.Messaging.DotNetNative.Rmq + +This document describes the purpose of the [`RapidField.SolidInstruments.Messaging.DotNetNative.Rmq`]() library and offers guidance with respect to licensing, installation and usage. + +## Features + +This library exposes the native .NET IoC integration for the **Solid Instruments** [**RabbitMQ**](https://www.rabbitmq.com/) messaging abstractions. + +## License + +[![License](https://img.shields.io/github/license/rapidfield/solid-instruments?style=flat&color=lightseagreen&label=license&logo=open-access&logoColor=lightgrey)](../../LICENSE.txt) + +**Solid Instruments** is [MIT-licensed](https://en.wikipedia.org/wiki/MIT_License). Review the [license terms](../../LICENSE.txt) for more information. + +## Documentation + +[![Documentation](https://img.shields.io/badge/documentation-website-tan?style=flat&logo=buffer&logoColor=lightgrey)](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq.html) + +Check out the [**API Reference**](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq.html) for usage examples and reference documentation. + +## Installation + +[![Version](https://img.shields.io/nuget/vpre/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq?style=flat&color=blue&label=version&logo=nuget&logoColor=lightgrey)](https://www.nuget.org/packages/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq) +[![Downloads](https://img.shields.io/nuget/dt/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq?style=flat&color=blue&logo=nuget&logoColor=lightgrey)](https://www.nuget.org/packages/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq) + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.Messaging.DotNetNative.Rmq +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.Messaging.DotNetNative.Rmq +``` + +
+ +- - - + +
+ +[![RapidField](../../RapidField.Logo.Color.Black.Transparent.200w.png)](https://www.rapidfield.com) + +###### Copyright (c) RapidField LLC. All rights reserved. "RapidField" and "Solid Instruments" are trademarks of RapidField LLC. \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq.csproj b/src/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq.csproj new file mode 100644 index 00000000..d5cc1f97 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq.csproj @@ -0,0 +1,52 @@ + + + + + Solid Instruments contributors + RapidField + Copyright (c) RapidField LLC. All rights reserved. + Solid Instruments + This library exposes the native .NET IoC integration for the Solid Instruments RabbitMQ messaging abstractions. + $(BuildVersion) + netstandard2.1 + latest + git + https://github.com/rapidfield/solid-instruments + true + true + LICENSE.txt + https://www.solidinstruments.com + Icon.Messaging.128w.png + solid-instruments;messaging;service-bus;bus;request-response;pub-sub;eventing;rabbitmq;inversion-of-control;ioc;dependency-injection;di + + + bin\Debug\netstandard2.1\RapidField.SolidInstruments.Messaging.DotNetNative.Rmq.xml + true + + + + bin\Release\netstandard2.1\RapidField.SolidInstruments.Messaging.DotNetNative.Rmq.xml + true + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.DotNetNative/AssemblyAttributes.cs b/src/RapidField.SolidInstruments.Messaging.DotNetNative/AssemblyAttributes.cs new file mode 100644 index 00000000..a32e4acd --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.DotNetNative/AssemblyAttributes.cs @@ -0,0 +1,7 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using System.Runtime.CompilerServices; + +[assembly: DisablePrivateReflection()] \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.DotNetNative/Extensions/IServiceCollectionExtensions.cs b/src/RapidField.SolidInstruments.Messaging.DotNetNative/Extensions/IServiceCollectionExtensions.cs new file mode 100644 index 00000000..7772cd0c --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.DotNetNative/Extensions/IServiceCollectionExtensions.cs @@ -0,0 +1,271 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Microsoft.Extensions.DependencyInjection; +using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Command.DotNetNative.Extensions; +using RapidField.SolidInstruments.EventAuthoring; +using RapidField.SolidInstruments.EventAuthoring.DotNetNative.Extensions; +using RapidField.SolidInstruments.Messaging.CommandMessages; +using RapidField.SolidInstruments.Messaging.EventMessages; +using RapidField.SolidInstruments.Messaging.InMemory; +using RapidField.SolidInstruments.Messaging.TransportPrimitives; + +namespace RapidField.SolidInstruments.Messaging.DotNetNative.Extensions +{ + /// + /// Extends the interface with native .NET inversion of control features to support messaging. + /// + public static class IServiceCollectionExtensions + { + /// + /// Registers a transient command message listener for the specified message type. + /// + /// + /// The type of the associated command. + /// + /// + /// The type of the command message for which a listener is registered. + /// + /// + /// The current . + /// + public static IServiceCollection AddCommandMessageListener(this IServiceCollection target) + where TCommand : class, ICommand + where TMessage : class, ICommandMessage => target.AddMessageListener>(); + + /// + /// Registers a transient command message listener and an associated command handler for the specified message type. + /// + /// + /// The type of the associated command. + /// + /// + /// The type of the command message for which a listener is registered. + /// + /// + /// The type of the command handler that is registered. + /// + /// + /// The current . + /// + /// + /// The resulting . + /// + public static IServiceCollection AddCommandMessageListenerAndHandler(this IServiceCollection target) + where TCommand : class, ICommand + where TMessage : class, ICommandMessage + where TCommandHandler : class, ICommandHandler => target.AddCommandMessageListener().AddCommandHandler(); + + /// + /// Registers a transient command message transmitter for the specified message type. + /// + /// + /// The type of the associated command. + /// + /// + /// The type of the command message for which a transmitter is registered. + /// + /// + /// The current . + /// + /// + /// The resulting . + /// + public static IServiceCollection AddCommandMessageTransmitter(this IServiceCollection target) + where TCommand : class, ICommand + where TMessage : class, ICommandMessage => target.AddMessageTransmitter>(); + + /// + /// Registers a transient event message listener for the specified message type. + /// + /// + /// The type of the associated event. + /// + /// + /// The type of the event message for which a listener is registered. + /// + /// + /// The current . + /// + /// + /// The resulting . + /// + public static IServiceCollection AddEventMessageListener(this IServiceCollection target) + where TEvent : class, IEvent + where TMessage : class, IEventMessage => target.AddMessageListener>(); + + /// + /// Registers a transient event message listener and an associated event handler for the specified message type. + /// + /// + /// The type of the associated event. + /// + /// + /// The type of the event message for which a listener is registered. + /// + /// + /// The type of the event handler that is registered. + /// + /// + /// The current . + /// + /// + /// The resulting . + /// + public static IServiceCollection AddEventMessageListenerAndHandler(this IServiceCollection target) + where TEvent : class, IEvent + where TMessage : class, IEventMessage + where TEventHandler : class, IEventHandler => target.AddEventMessageListener().AddEventHandler(); + + /// + /// Registers a transient event message transmitter for the specified message type. + /// + /// + /// The type of the associated event. + /// + /// + /// The type of the event message for which a transmitter is registered. + /// + /// + /// The current . + /// + /// + /// The resulting . + /// + public static IServiceCollection AddEventMessageTransmitter(this IServiceCollection target) + where TEvent : class, IEvent + where TMessage : class, IEventMessage => target.AddMessageTransmitter>(); + + /// + /// Registers a transient message listener for the specified message type. + /// + /// + /// The type of the message for which a listener is registered. + /// + /// + /// The type of the message listener that is registered. + /// + /// + /// The current . + /// + /// + /// The resulting . + /// + public static IServiceCollection AddMessageListener(this IServiceCollection target) + where TMessage : class, IMessage + where TMessageListener : class, IMessageListener => target.AddTransient, TMessageListener>(); + + /// + /// Registers a transient message transmitter for the specified message type. + /// + /// + /// The type of the message for which a transmitter is registered. + /// + /// + /// The type of the message transmitter that is registered. + /// + /// + /// The current . + /// + /// + /// The resulting . + /// + public static IServiceCollection AddMessageTransmitter(this IServiceCollection target) + where TMessage : class, IMessage + where TMessageTransmitter : class, IMessageTransmitter => target.AddCommandHandler(); + + /// + /// Registers a transient message listener for the specified request message type. + /// + /// + /// The type of the request message that is transmitted by the listener. + /// + /// + /// The type of the response message that is transmitted in response to the request. + /// + /// + /// The type of the message listener that is registered. + /// + /// + /// The current . + /// + /// + /// The resulting . + /// + public static IServiceCollection AddRequestMessageListener(this IServiceCollection target) + where TRequestMessage : class, IRequestMessage + where TResponseMessage : class, IResponseMessage + where TMessageListener : class, IMessageListener => target.AddTransient, TMessageListener>(); + + /// + /// Registers a transient message transmitter for the specified request message type. + /// + /// + /// The type of the request message that is transmitted by the transmitter. + /// + /// + /// The type of the response message that is transmitted in response to the request. + /// + /// + /// The current . + /// + /// + /// The resulting . + /// + public static IServiceCollection AddRequestMessageTransmitter(this IServiceCollection target) + where TRequestMessage : class, IRequestMessage + where TResponseMessage : class, IResponseMessage => target.AddRequestMessageTransmitter>(); + + /// + /// Registers a transient message transmitter for the specified request message type. + /// + /// + /// The type of the request message that is transmitted by the transmitter. + /// + /// + /// The type of the response message that is transmitted in response to the request. + /// + /// + /// The type of the message transmitter that is registered. + /// + /// + /// The current . + /// + /// + /// The resulting . + /// + public static IServiceCollection AddRequestMessageTransmitter(this IServiceCollection target) + where TRequestMessage : class, IRequestMessage + where TResponseMessage : class, IResponseMessage + where TMessageTransmitter : class, IMessageTransmitter => target.AddCommandHandler(); + + /// + /// Registers a collection of types that establish support for in-memory service bus functionality. + /// + /// + /// The current . + /// + /// + /// The resulting . + /// + public static IServiceCollection AddSupportingTypesForInMemoryMessaging(this IServiceCollection target) + { + target.AddSingleton(MessageTransport.Instance); + target.AddSingleton((serviceProvider) => serviceProvider.GetService()); + target.AddSingleton(MessageTransport.Instance.CreateConnection()); + target.AddScoped(); + target.AddScoped, InMemoryMessageAdapter>((serviceProvider) => serviceProvider.GetService()); + target.AddScoped(); + target.AddScoped, InMemoryClientFactory>((serviceProvider) => serviceProvider.GetService()); + target.AddScoped(); + target.AddScoped((serviceProvider) => serviceProvider.GetService()); + target.AddSingleton(); + target.AddSingleton((serviceProvider) => serviceProvider.GetService()); + target.AddSingleton(); + target.AddSingleton((serviceProvider) => serviceProvider.GetService()); + return target; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.DotNetNative/README.md b/src/RapidField.SolidInstruments.Messaging.DotNetNative/README.md new file mode 100644 index 00000000..3a9c4008 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.DotNetNative/README.md @@ -0,0 +1,57 @@ + + +[![Solid Instruments](../../SolidInstruments.Logo.Color.Transparent.500w.png)](../../README.md) +
     +![Messaging](../../doc/images/Label.Messaging.300w.png) +- - - + +# RapidField.SolidInstruments.Messaging.DotNetNative + +This document describes the purpose of the [`RapidField.SolidInstruments.Messaging.DotNetNative`]() library and offers guidance with respect to licensing, installation and usage. + +## Features + +This library exposes the native .NET IoC integration for the **Solid Instruments** messaging abstractions. + +## License + +[![License](https://img.shields.io/github/license/rapidfield/solid-instruments?style=flat&color=lightseagreen&label=license&logo=open-access&logoColor=lightgrey)](../../LICENSE.txt) + +**Solid Instruments** is [MIT-licensed](https://en.wikipedia.org/wiki/MIT_License). Review the [license terms](../../LICENSE.txt) for more information. + +## Documentation + +[![Documentation](https://img.shields.io/badge/documentation-website-tan?style=flat&logo=buffer&logoColor=lightgrey)](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Messaging.DotNetNative.html) + +Check out the [**API Reference**](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Messaging.DotNetNative.html) for usage examples and reference documentation. + +## Installation + +[![Version](https://img.shields.io/nuget/vpre/RapidField.SolidInstruments.Messaging.DotNetNative?style=flat&color=blue&label=version&logo=nuget&logoColor=lightgrey)](https://www.nuget.org/packages/RapidField.SolidInstruments.Messaging.DotNetNative) +[![Downloads](https://img.shields.io/nuget/dt/RapidField.SolidInstruments.Messaging.DotNetNative?style=flat&color=blue&logo=nuget&logoColor=lightgrey)](https://www.nuget.org/packages/RapidField.SolidInstruments.Messaging.DotNetNative) + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.Messaging.DotNetNative +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.Messaging.DotNetNative +``` + +
+ +- - - + +
+ +[![RapidField](../../RapidField.Logo.Color.Black.Transparent.200w.png)](https://www.rapidfield.com) + +###### Copyright (c) RapidField LLC. All rights reserved. "RapidField" and "Solid Instruments" are trademarks of RapidField LLC. \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.DotNetNative/RapidField.SolidInstruments.Messaging.DotNetNative.csproj b/src/RapidField.SolidInstruments.Messaging.DotNetNative/RapidField.SolidInstruments.Messaging.DotNetNative.csproj new file mode 100644 index 00000000..22ad0da0 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.DotNetNative/RapidField.SolidInstruments.Messaging.DotNetNative.csproj @@ -0,0 +1,53 @@ + + + + + Solid Instruments contributors + RapidField + Copyright (c) RapidField LLC. All rights reserved. + Solid Instruments + This library exposes the native .NET IoC integration for the Solid Instruments messaging abstractions. + $(BuildVersion) + netstandard2.1 + latest + git + https://github.com/rapidfield/solid-instruments + true + true + LICENSE.txt + https://www.solidinstruments.com + Icon.Messaging.128w.png + solid-instruments;messaging;service-bus;bus;request-response;pub-sub;eventing;inversion-of-control;ioc;dependency-injection;di + + + bin\Debug\netstandard2.1\RapidField.SolidInstruments.Messaging.DotNetNative.xml + true + + + + bin\Release\netstandard2.1\RapidField.SolidInstruments.Messaging.DotNetNative.xml + true + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.InMemory/RapidField.SolidInstruments.Messaging.InMemory.csproj b/src/RapidField.SolidInstruments.Messaging.InMemory/RapidField.SolidInstruments.Messaging.InMemory.csproj index 5836ac68..df228660 100644 --- a/src/RapidField.SolidInstruments.Messaging.InMemory/RapidField.SolidInstruments.Messaging.InMemory.csproj +++ b/src/RapidField.SolidInstruments.Messaging.InMemory/RapidField.SolidInstruments.Messaging.InMemory.csproj @@ -22,12 +22,12 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in solid-instruments;messaging;service-bus;bus;request-response;pub-sub;eventing;in-memory - bin\Debug\netstandard2.0\RapidField.SolidInstruments.Messaging.InMemory.xml + bin\Debug\netstandard2.1\RapidField.SolidInstruments.Messaging.InMemory.xml true - bin\Release\netstandard2.0\RapidField.SolidInstruments.Messaging.InMemory.xml + bin\Release\netstandard2.1\RapidField.SolidInstruments.Messaging.InMemory.xml true diff --git a/src/RapidField.SolidInstruments.Messaging.RabbitMq/RapidField.SolidInstruments.Messaging.RabbitMq.csproj b/src/RapidField.SolidInstruments.Messaging.RabbitMq/RapidField.SolidInstruments.Messaging.RabbitMq.csproj index cbf098aa..4d515285 100644 --- a/src/RapidField.SolidInstruments.Messaging.RabbitMq/RapidField.SolidInstruments.Messaging.RabbitMq.csproj +++ b/src/RapidField.SolidInstruments.Messaging.RabbitMq/RapidField.SolidInstruments.Messaging.RabbitMq.csproj @@ -22,12 +22,12 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in solid-instruments;messaging;service-bus;bus;request-response;pub-sub;eventing;rabbitmq - bin\Debug\netstandard2.0\RapidField.SolidInstruments.Messaging.RabbitMq.xml + bin\Debug\netstandard2.1\RapidField.SolidInstruments.Messaging.RabbitMq.xml true - bin\Release\netstandard2.0\RapidField.SolidInstruments.Messaging.RabbitMq.xml + bin\Release\netstandard2.1\RapidField.SolidInstruments.Messaging.RabbitMq.xml true diff --git a/src/RapidField.SolidInstruments.Messaging/AssemblyAttributes.cs b/src/RapidField.SolidInstruments.Messaging/AssemblyAttributes.cs index e91aa9a8..1502201a 100644 --- a/src/RapidField.SolidInstruments.Messaging/AssemblyAttributes.cs +++ b/src/RapidField.SolidInstruments.Messaging/AssemblyAttributes.cs @@ -5,7 +5,9 @@ using System.Runtime.CompilerServices; [assembly: DisablePrivateReflection()] +[assembly: InternalsVisibleTo("RapidField.SolidInstruments.Messaging.Autofac")] [assembly: InternalsVisibleTo("RapidField.SolidInstruments.Messaging.AzureServiceBus")] +[assembly: InternalsVisibleTo("RapidField.SolidInstruments.Messaging.DotNetNative")] [assembly: InternalsVisibleTo("RapidField.SolidInstruments.Messaging.InMemory")] [assembly: InternalsVisibleTo("RapidField.SolidInstruments.Messaging.InMemory.UnitTests")] [assembly: InternalsVisibleTo("RapidField.SolidInstruments.Messaging.RabbitMq")] diff --git a/src/RapidField.SolidInstruments.Messaging/RapidField.SolidInstruments.Messaging.csproj b/src/RapidField.SolidInstruments.Messaging/RapidField.SolidInstruments.Messaging.csproj index 4f58ebfc..d3aacf1d 100644 --- a/src/RapidField.SolidInstruments.Messaging/RapidField.SolidInstruments.Messaging.csproj +++ b/src/RapidField.SolidInstruments.Messaging/RapidField.SolidInstruments.Messaging.csproj @@ -22,12 +22,12 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in solid-instruments;messaging;service-bus;bus;request-response;pub-sub;eventing - bin\Debug\netstandard2.0\RapidField.SolidInstruments.Messaging.xml + bin\Debug\netstandard2.1\RapidField.SolidInstruments.Messaging.xml true - bin\Release\netstandard2.0\RapidField.SolidInstruments.Messaging.xml + bin\Release\netstandard2.1\RapidField.SolidInstruments.Messaging.xml true diff --git a/src/RapidField.SolidInstruments.ObjectComposition/RapidField.SolidInstruments.ObjectComposition.csproj b/src/RapidField.SolidInstruments.ObjectComposition/RapidField.SolidInstruments.ObjectComposition.csproj index 931e4ef9..77bbba0f 100644 --- a/src/RapidField.SolidInstruments.ObjectComposition/RapidField.SolidInstruments.ObjectComposition.csproj +++ b/src/RapidField.SolidInstruments.ObjectComposition/RapidField.SolidInstruments.ObjectComposition.csproj @@ -22,12 +22,12 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in solid-instruments;object-composition;factory;composite-factory;factory-pattern - bin\Debug\netstandard2.0\RapidField.SolidInstruments.ObjectComposition.xml + bin\Debug\netstandard2.1\RapidField.SolidInstruments.ObjectComposition.xml true - bin\Release\netstandard2.0\RapidField.SolidInstruments.ObjectComposition.xml + bin\Release\netstandard2.1\RapidField.SolidInstruments.ObjectComposition.xml true diff --git a/src/RapidField.SolidInstruments.Serialization/RapidField.SolidInstruments.Serialization.csproj b/src/RapidField.SolidInstruments.Serialization/RapidField.SolidInstruments.Serialization.csproj index 847edfae..3cbbdc7a 100644 --- a/src/RapidField.SolidInstruments.Serialization/RapidField.SolidInstruments.Serialization.csproj +++ b/src/RapidField.SolidInstruments.Serialization/RapidField.SolidInstruments.Serialization.csproj @@ -22,12 +22,12 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in solid-instruments;serialization;serializer;xml-serializer;json-serializer;compressed-serialization - bin\Debug\netstandard2.0\RapidField.SolidInstruments.Serialization.xml + bin\Debug\netstandard2.1\RapidField.SolidInstruments.Serialization.xml true - bin\Release\netstandard2.0\RapidField.SolidInstruments.Serialization.xml + bin\Release\netstandard2.1\RapidField.SolidInstruments.Serialization.xml true diff --git a/src/RapidField.SolidInstruments.Service/RapidField.SolidInstruments.Service.csproj b/src/RapidField.SolidInstruments.Service/RapidField.SolidInstruments.Service.csproj index bbf61816..051f33c0 100644 --- a/src/RapidField.SolidInstruments.Service/RapidField.SolidInstruments.Service.csproj +++ b/src/RapidField.SolidInstruments.Service/RapidField.SolidInstruments.Service.csproj @@ -22,12 +22,12 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in solid-instruments;service;service-application - bin\Debug\netstandard2.0\RapidField.SolidInstruments.Service.xml + bin\Debug\netstandard2.1\RapidField.SolidInstruments.Service.xml true - bin\Release\netstandard2.0\RapidField.SolidInstruments.Service.xml + bin\Release\netstandard2.1\RapidField.SolidInstruments.Service.xml true diff --git a/src/RapidField.SolidInstruments.SignalProcessing/RapidField.SolidInstruments.SignalProcessing.csproj b/src/RapidField.SolidInstruments.SignalProcessing/RapidField.SolidInstruments.SignalProcessing.csproj index 11ce7503..4383d9dd 100644 --- a/src/RapidField.SolidInstruments.SignalProcessing/RapidField.SolidInstruments.SignalProcessing.csproj +++ b/src/RapidField.SolidInstruments.SignalProcessing/RapidField.SolidInstruments.SignalProcessing.csproj @@ -22,12 +22,12 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in solid-instruments;signal-processing;dsp - bin\Debug\netstandard2.0\RapidField.SolidInstruments.SignalProcessing.xml + bin\Debug\netstandard2.1\RapidField.SolidInstruments.SignalProcessing.xml true - bin\Release\netstandard2.0\RapidField.SolidInstruments.SignalProcessing.xml + bin\Release\netstandard2.1\RapidField.SolidInstruments.SignalProcessing.xml true diff --git a/src/RapidField.SolidInstruments.TextEncoding/RapidField.SolidInstruments.TextEncoding.csproj b/src/RapidField.SolidInstruments.TextEncoding/RapidField.SolidInstruments.TextEncoding.csproj index f37c130e..1b5ee932 100644 --- a/src/RapidField.SolidInstruments.TextEncoding/RapidField.SolidInstruments.TextEncoding.csproj +++ b/src/RapidField.SolidInstruments.TextEncoding/RapidField.SolidInstruments.TextEncoding.csproj @@ -22,12 +22,12 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in solid-instruments;text-encoding;base32;z-base-32;hexadecimal - bin\Debug\netstandard2.0\RapidField.SolidInstruments.TextEncoding.xml + bin\Debug\netstandard2.1\RapidField.SolidInstruments.TextEncoding.xml true - bin\Release\netstandard2.0\RapidField.SolidInstruments.TextEncoding.xml + bin\Release\netstandard2.1\RapidField.SolidInstruments.TextEncoding.xml true diff --git a/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/SingleThreadSpinLockControlTests.cs b/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/SingleThreadSpinLockControlTests.cs index f15de863..a4321afa 100644 --- a/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/SingleThreadSpinLockControlTests.cs +++ b/test/RapidField.SolidInstruments.Core.UnitTests/Concurrency/SingleThreadSpinLockControlTests.cs @@ -27,7 +27,7 @@ public void OperationLatency_ShouldBeLow() { // Arrange. var mode = ConcurrencyControlMode.SingleThreadSpinLock; - var latencyThresholdInTicks = 987; + var latencyThresholdInTicks = 2584; // Assert. ConcurrencyControlTests.OperationLatency_ShouldBeLow(mode, PerformUsingPrimitive, latencyThresholdInTicks); diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/RapidField.SolidInstruments.Messaging.InMemory.UnitTests.csproj b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/RapidField.SolidInstruments.Messaging.InMemory.UnitTests.csproj index f7a60df8..09a4d5ee 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/RapidField.SolidInstruments.Messaging.InMemory.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/RapidField.SolidInstruments.Messaging.InMemory.UnitTests.csproj @@ -26,6 +26,7 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in + diff --git a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/SimulatedDependencyModule.cs b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/SimulatedDependencyModule.cs index 01143974..6dccfd10 100644 --- a/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/SimulatedDependencyModule.cs +++ b/test/RapidField.SolidInstruments.Messaging.InMemory.UnitTests/SimulatedDependencyModule.cs @@ -4,11 +4,9 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using RapidField.SolidInstruments.Command; using RapidField.SolidInstruments.InversionOfControl.DotNetNative; -using RapidField.SolidInstruments.Messaging.CommandMessages; -using RapidField.SolidInstruments.Messaging.EventMessages; -using RapidField.SolidInstruments.Messaging.TransportPrimitives; +using RapidField.SolidInstruments.InversionOfControl.DotNetNative.Extensions; +using RapidField.SolidInstruments.Messaging.DotNetNative.Extensions; using System; namespace RapidField.SolidInstruments.Messaging.InMemory.UnitTests @@ -45,96 +43,60 @@ public SimulatedDependencyModule(IConfiguration applicationConfiguration) protected override void Configure(ServiceCollection configurator, IConfiguration applicationConfiguration) { // Register the configuration. - configurator.AddSingleton(applicationConfiguration); + configurator.AddApplicationConfiguration(applicationConfiguration); - // Register messaging types. - var messageTransport = MessageTransport.Instance; - var messageTransportConnection = messageTransport.CreateConnection(); - configurator.AddSingleton(messageTransport); - configurator.AddSingleton((serviceProvider) => serviceProvider.GetService()); - configurator.AddSingleton(messageTransportConnection); - configurator.AddScoped(); - configurator.AddScoped, InMemoryMessageAdapter>((serviceProvider) => serviceProvider.GetService()); - configurator.AddScoped(); - configurator.AddScoped, InMemoryClientFactory>((serviceProvider) => serviceProvider.GetService()); - configurator.AddScoped(); - configurator.AddScoped((serviceProvider) => serviceProvider.GetService()); - configurator.AddSingleton(); - configurator.AddSingleton((serviceProvider) => serviceProvider.GetService()); - configurator.AddSingleton(); - configurator.AddSingleton((serviceProvider) => serviceProvider.GetService()); + // Register messaging support types. + configurator.AddSupportingTypesForInMemoryMessaging(); - // Register queue transmitters. - configurator.AddTransient, CommandMessageTransmitter>(); - configurator.AddTransient, CommandMessageTransmitter>(); - configurator.AddTransient, CommandMessageTransmitter>(); - configurator.AddTransient, CommandMessageTransmitter>(); - configurator.AddTransient, CommandMessageTransmitter>(); - configurator.AddTransient, CommandMessageTransmitter>(); - configurator.AddTransient, CommandMessageTransmitter>(); - configurator.AddTransient, CommandMessageTransmitter>(); - configurator.AddTransient, CommandMessageTransmitter>(); + // Register command message transmitters. + configurator.AddCommandMessageTransmitter(); + configurator.AddCommandMessageTransmitter(); + configurator.AddCommandMessageTransmitter(); + configurator.AddCommandMessageTransmitter(); + configurator.AddCommandMessageTransmitter(); + configurator.AddCommandMessageTransmitter(); + configurator.AddCommandMessageTransmitter(); + configurator.AddCommandMessageTransmitter(); + configurator.AddCommandMessageTransmitter(); - // Register topic transmitters. - configurator.AddTransient, EventMessageTransmitter>(); - configurator.AddTransient, EventMessageTransmitter>(); - configurator.AddTransient, EventMessageTransmitter>(); - configurator.AddTransient, EventMessageTransmitter>(); - configurator.AddTransient, EventMessageTransmitter>(); - configurator.AddTransient, EventMessageTransmitter>(); - configurator.AddTransient, EventMessageTransmitter>(); - configurator.AddTransient, EventMessageTransmitter>(); - configurator.AddTransient, EventMessageTransmitter>(); + // Register event message transmitters. + configurator.AddEventMessageTransmitter(); + configurator.AddEventMessageTransmitter(); + configurator.AddEventMessageTransmitter(); + configurator.AddEventMessageTransmitter(); + configurator.AddEventMessageTransmitter(); + configurator.AddEventMessageTransmitter(); + configurator.AddEventMessageTransmitter(); + configurator.AddEventMessageTransmitter(); + configurator.AddEventMessageTransmitter(); - // Register request transmitters. - configurator.AddTransient, RequestTransmitter>(); + // Register request message transmitters. + configurator.AddRequestMessageTransmitter(); - // Register queue listeners. - configurator.AddTransient, CommandMessageListener>(); - configurator.AddTransient, CommandMessageListener>(); - configurator.AddTransient, CommandMessageListener>(); - configurator.AddTransient, CommandMessageListener>(); - configurator.AddTransient, CommandMessageListener>(); - configurator.AddTransient, CommandMessageListener>(); - configurator.AddTransient, CommandMessageListener>(); - configurator.AddTransient, CommandMessageListener>(); - configurator.AddTransient, CommandMessageListener>(); + // Register command message listeners and handlers. + configurator.AddCommandMessageListenerAndHandler(); + configurator.AddCommandMessageListenerAndHandler(); + configurator.AddCommandMessageListenerAndHandler(); + configurator.AddCommandMessageListenerAndHandler(); + configurator.AddCommandMessageListenerAndHandler(); + configurator.AddCommandMessageListenerAndHandler(); + configurator.AddCommandMessageListenerAndHandler(); + configurator.AddCommandMessageListenerAndHandler(); + configurator.AddCommandMessageListenerAndHandler(); - // Register topic listeners. - configurator.AddTransient, EventMessageListener>(); - configurator.AddTransient, EventMessageListener>(); - configurator.AddTransient, EventMessageListener>(); - configurator.AddTransient, EventMessageListener>(); - configurator.AddTransient, EventMessageListener>(); - configurator.AddTransient, EventMessageListener>(); - configurator.AddTransient, EventMessageListener>(); - configurator.AddTransient, EventMessageListener>(); - configurator.AddTransient, EventMessageListener>(); + // Register event message listeners and handlers. + configurator.AddEventMessageListenerAndHandler(); + configurator.AddEventMessageListenerAndHandler(); + configurator.AddEventMessageListenerAndHandler(); + configurator.AddEventMessageListenerAndHandler(); + configurator.AddEventMessageListenerAndHandler(); + configurator.AddEventMessageListenerAndHandler(); + configurator.AddEventMessageListenerAndHandler(); + configurator.AddEventMessageListenerAndHandler(); + configurator.AddEventMessageListenerAndHandler(); - // Register request listeners. - configurator.AddTransient, MessageListeners.RequestResponse.Ping.RequestListener>(); - - // Register command handlers. - configurator.AddTransient, CommandHandlers.ModelState.Customer.CreateDomainModelCommandHandler>(); - configurator.AddTransient, CommandHandlers.ModelState.Customer.DeleteDomainModelCommandHandler>(); - configurator.AddTransient, CommandHandlers.ModelState.Customer.UpdateDomainModelCommandHandler>(); - configurator.AddTransient, CommandHandlers.ModelState.CustomerOrder.CreateDomainModelCommandHandler>(); - configurator.AddTransient, CommandHandlers.ModelState.CustomerOrder.DeleteDomainModelCommandHandler>(); - configurator.AddTransient, CommandHandlers.ModelState.CustomerOrder.UpdateDomainModelCommandHandler>(); - configurator.AddTransient, CommandHandlers.ModelState.Product.CreateDomainModelCommandHandler>(); - configurator.AddTransient, CommandHandlers.ModelState.Product.DeleteDomainModelCommandHandler>(); - configurator.AddTransient, CommandHandlers.ModelState.Product.UpdateDomainModelCommandHandler>(); - - // Register event handlers. - configurator.AddTransient, EventHandlers.ModelState.Customer.DomainModelCreatedEventHandler>(); - configurator.AddTransient, EventHandlers.ModelState.Customer.DomainModelDeletedEventHandler>(); - configurator.AddTransient, EventHandlers.ModelState.Customer.DomainModelUpdatedEventHandler>(); - configurator.AddTransient, EventHandlers.ModelState.CustomerOrder.DomainModelCreatedEventHandler>(); - configurator.AddTransient, EventHandlers.ModelState.CustomerOrder.DomainModelDeletedEventHandler>(); - configurator.AddTransient, EventHandlers.ModelState.CustomerOrder.DomainModelUpdatedEventHandler>(); - configurator.AddTransient, EventHandlers.ModelState.Product.DomainModelCreatedEventHandler>(); - configurator.AddTransient, EventHandlers.ModelState.Product.DomainModelDeletedEventHandler>(); - configurator.AddTransient, EventHandlers.ModelState.Product.DomainModelUpdatedEventHandler>(); + // Register request message listeners. + configurator.AddRequestMessageListener(); } } } \ No newline at end of file From 828020a1ccb700e591f3caf359db4ae05815dfa6 Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Sat, 25 Jul 2020 03:06:09 -0500 Subject: [PATCH 49/55] Resolve two RabbitMQ-related defects. --- .../ExampleContractsDependencyModule.cs | 16 +- ....SolidInstruments.Example.Contracts.csproj | 1 + .../ExampleDatabaseModelDependencyModule.cs | 3 - .../ExampleDomainDependencyModule.cs | 12 +- .../ApplicationStartedEventMessageListener.cs | 2 +- .../ApplicationStoppedEventMessageListener.cs | 2 +- .../ExceptionRaisedEventMessageListener.cs | 2 +- ...eld.SolidInstruments.Example.Domain.csproj | 1 + .../ApplicationDependencyModule.cs | 40 +- .../ExampleMessagingServiceExecutor.cs | 8 +- ...truments.Example.ServiceApplication.csproj | 7 +- .../appsettings.json | 2 +- ...dInstruments.Example.WebApplication.csproj | 4 +- ...ld.SolidInstruments.Command.Autofac.csproj | 2 +- ...lidInstruments.Command.DotNetNative.csproj | 6 +- .../RapidField.SolidInstruments.Core.csproj | 2 +- ...SolidInstruments.DataAccess.Autofac.csproj | 2 +- ...Instruments.DataAccess.DotNetNative.csproj | 6 +- ...truments.DataAccess.EntityFramework.csproj | 6 +- ...dInstruments.EventAuthoring.Autofac.csproj | 2 +- ...ruments.EventAuthoring.DotNetNative.csproj | 6 +- .../Extensions/ContainerBuilderExtensions.cs | 9 +- ...truments.InversionOfControl.Autofac.csproj | 2 +- .../IServiceCollectionExtensions.cs | 13 +- ...nts.InversionOfControl.DotNetNative.csproj | 6 +- ...SolidInstruments.InversionOfControl.csproj | 6 +- .../Extensions/ContainerBuilderExtensions.cs | 68 ++++ ...idInstruments.Messaging.Autofac.Asb.csproj | 2 +- .../Extensions/ContainerBuilderExtensions.cs | 232 +++++++++++ ...idInstruments.Messaging.Autofac.Rmq.csproj | 2 +- ....SolidInstruments.Messaging.Autofac.csproj | 2 +- .../IServiceCollectionExtensions.cs | 79 ++++ ...truments.Messaging.DotNetNative.Asb.csproj | 6 +- .../IServiceCollectionExtensions.cs | 260 +++++++++++++ ...truments.Messaging.DotNetNative.Rmq.csproj | 6 +- ...dInstruments.Messaging.DotNetNative.csproj | 6 +- .../AssemblyAttributes.cs | 4 +- .../RabbitMqMessageTransport.cs | 367 +++++++++++------- .../RabbitMqMessageTransportConnection.cs | 184 +++++---- .../IMessageProcessingInformation.cs | 2 +- .../MessageProcessingInformation.cs | 2 +- .../MessagingClientFactory.cs | 6 +- .../TransportPrimitives/MessageLockToken.cs | 10 +- .../TransportPrimitives/MessageTransport.cs | 4 +- ....SolidInstruments.ObjectComposition.csproj | 2 +- ...eld.SolidInstruments.Core.UnitTests.csproj | 2 +- ...nversionOfControl.Autofac.UnitTests.csproj | 2 +- ...ionOfControl.DotNetNative.UnitTests.csproj | 2 +- ...uments.InversionOfControl.UnitTests.csproj | 2 +- ...ruments.ObjectComposition.UnitTests.csproj | 2 +- 50 files changed, 1072 insertions(+), 350 deletions(-) create mode 100644 src/RapidField.SolidInstruments.Messaging.Autofac.Asb/Extensions/ContainerBuilderExtensions.cs create mode 100644 src/RapidField.SolidInstruments.Messaging.Autofac.Rmq/Extensions/ContainerBuilderExtensions.cs create mode 100644 src/RapidField.SolidInstruments.Messaging.DotNetNative.Asb/Extensions/IServiceCollectionExtensions.cs create mode 100644 src/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq/Extensions/IServiceCollectionExtensions.cs diff --git a/example/RapidField.SolidInstruments.Example.Contracts/ExampleContractsDependencyModule.cs b/example/RapidField.SolidInstruments.Example.Contracts/ExampleContractsDependencyModule.cs index 37cb80e2..fd5028db 100644 --- a/example/RapidField.SolidInstruments.Example.Contracts/ExampleContractsDependencyModule.cs +++ b/example/RapidField.SolidInstruments.Example.Contracts/ExampleContractsDependencyModule.cs @@ -4,10 +4,10 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.EventAuthoring; using RapidField.SolidInstruments.Example.Contracts.Messages; using RapidField.SolidInstruments.InversionOfControl.DotNetNative; -using RapidField.SolidInstruments.Messaging; +using RapidField.SolidInstruments.Messaging.DotNetNative.Extensions; using RapidField.SolidInstruments.Messaging.EventMessages; using System; @@ -44,15 +44,13 @@ public ExampleContractsDependencyModule(IConfiguration applicationConfiguration) /// protected override void Configure(ServiceCollection configurator, IConfiguration applicationConfiguration) { - // Register queue transmitters. - configurator.AddTransient, QueueTransmitter>(); - configurator.AddTransient, QueueTransmitter>(); - - // Register topic transmitters. - configurator.AddTransient, TopicTransmitter>(); + // Register event transmitters. + configurator.AddEventMessageTransmitter(); + configurator.AddEventMessageTransmitter(); + configurator.AddEventMessageTransmitter(); // Register request transmitters. - configurator.AddTransient, RequestTransmitter>(); + configurator.AddRequestMessageTransmitter(); } } } \ No newline at end of file diff --git a/example/RapidField.SolidInstruments.Example.Contracts/RapidField.SolidInstruments.Example.Contracts.csproj b/example/RapidField.SolidInstruments.Example.Contracts/RapidField.SolidInstruments.Example.Contracts.csproj index 043ebc8a..0f27549e 100644 --- a/example/RapidField.SolidInstruments.Example.Contracts/RapidField.SolidInstruments.Example.Contracts.csproj +++ b/example/RapidField.SolidInstruments.Example.Contracts/RapidField.SolidInstruments.Example.Contracts.csproj @@ -30,6 +30,7 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in + \ No newline at end of file diff --git a/example/RapidField.SolidInstruments.Example.DatabaseModel/ExampleDatabaseModelDependencyModule.cs b/example/RapidField.SolidInstruments.Example.DatabaseModel/ExampleDatabaseModelDependencyModule.cs index ec3b5a16..0972912a 100644 --- a/example/RapidField.SolidInstruments.Example.DatabaseModel/ExampleDatabaseModelDependencyModule.cs +++ b/example/RapidField.SolidInstruments.Example.DatabaseModel/ExampleDatabaseModelDependencyModule.cs @@ -45,10 +45,7 @@ public ExampleDatabaseModelDependencyModule(IConfiguration applicationConfigurat protected override void Configure(ServiceCollection configurator, IConfiguration applicationConfiguration) { // Register unit-of-work types. - configurator.AddScoped(); - configurator.AddScoped(); configurator.AddScoped(provider => new ExampleInMemoryContext(provider.GetService(), "Example")); - configurator.AddScoped(); configurator.AddScoped(); // Register data access command handlers. diff --git a/example/RapidField.SolidInstruments.Example.Domain/ExampleDomainDependencyModule.cs b/example/RapidField.SolidInstruments.Example.Domain/ExampleDomainDependencyModule.cs index 93ce0a27..c8b4e02e 100644 --- a/example/RapidField.SolidInstruments.Example.Domain/ExampleDomainDependencyModule.cs +++ b/example/RapidField.SolidInstruments.Example.Domain/ExampleDomainDependencyModule.cs @@ -7,7 +7,7 @@ using RapidField.SolidInstruments.Example.Contracts.Messages; using RapidField.SolidInstruments.Example.Domain.MessageListeners; using RapidField.SolidInstruments.InversionOfControl.DotNetNative; -using RapidField.SolidInstruments.Messaging; +using RapidField.SolidInstruments.Messaging.DotNetNative.Extensions; using RapidField.SolidInstruments.Messaging.EventMessages; using RapidField.SolidInstruments.Messaging.Service; using System; @@ -46,15 +46,15 @@ public ExampleDomainDependencyModule(IConfiguration applicationConfiguration) protected override void Configure(ServiceCollection configurator, IConfiguration applicationConfiguration) { // Register queue listeners. - configurator.AddTransient, ApplicationStartedEventMessageListener>(); - configurator.AddTransient, ApplicationStoppedEventMessageListener>(); - configurator.AddTransient, ExceptionRaisedEventMessageListener>(); + configurator.AddMessageListener(); + configurator.AddMessageListener(); + configurator.AddMessageListener(); // Register topic listeners. - configurator.AddTransient, HeartbeatMessageListener>(); + configurator.AddMessageListener(); // Register request listeners. - configurator.AddTransient, PingRequestMessageListener>(); + configurator.AddRequestMessageListener(); } } } \ No newline at end of file diff --git a/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/ApplicationStartedEventMessageListener.cs b/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/ApplicationStartedEventMessageListener.cs index 70747f6e..207e49c4 100644 --- a/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/ApplicationStartedEventMessageListener.cs +++ b/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/ApplicationStartedEventMessageListener.cs @@ -13,7 +13,7 @@ namespace RapidField.SolidInstruments.Example.Domain.MessageListeners /// /// Listens for and processes instances. /// - public sealed class ApplicationStartedEventMessageListener : QueueListener + public sealed class ApplicationStartedEventMessageListener : TopicListener { /// /// Initializes a new instance of the class. diff --git a/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/ApplicationStoppedEventMessageListener.cs b/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/ApplicationStoppedEventMessageListener.cs index 2533b894..6602f771 100644 --- a/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/ApplicationStoppedEventMessageListener.cs +++ b/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/ApplicationStoppedEventMessageListener.cs @@ -13,7 +13,7 @@ namespace RapidField.SolidInstruments.Example.Domain.MessageListeners /// /// Listens for and processes instances. /// - public sealed class ApplicationStoppedEventMessageListener : QueueListener + public sealed class ApplicationStoppedEventMessageListener : TopicListener { /// /// Initializes a new instance of the class. diff --git a/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/ExceptionRaisedEventMessageListener.cs b/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/ExceptionRaisedEventMessageListener.cs index 931a89b3..fd3d337e 100644 --- a/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/ExceptionRaisedEventMessageListener.cs +++ b/example/RapidField.SolidInstruments.Example.Domain/MessageListeners/ExceptionRaisedEventMessageListener.cs @@ -13,7 +13,7 @@ namespace RapidField.SolidInstruments.Example.Domain.MessageListeners /// /// Listens for and processes instances. /// - public sealed class ExceptionRaisedEventMessageListener : QueueListener + public sealed class ExceptionRaisedEventMessageListener : TopicListener { /// /// Initializes a new instance of the class. diff --git a/example/RapidField.SolidInstruments.Example.Domain/RapidField.SolidInstruments.Example.Domain.csproj b/example/RapidField.SolidInstruments.Example.Domain/RapidField.SolidInstruments.Example.Domain.csproj index 886c1c80..245e268e 100644 --- a/example/RapidField.SolidInstruments.Example.Domain/RapidField.SolidInstruments.Example.Domain.csproj +++ b/example/RapidField.SolidInstruments.Example.Domain/RapidField.SolidInstruments.Example.Domain.csproj @@ -29,6 +29,7 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in + diff --git a/example/RapidField.SolidInstruments.Example.ServiceApplication/ApplicationDependencyModule.cs b/example/RapidField.SolidInstruments.Example.ServiceApplication/ApplicationDependencyModule.cs index 7e54b00b..d9447bf4 100644 --- a/example/RapidField.SolidInstruments.Example.ServiceApplication/ApplicationDependencyModule.cs +++ b/example/RapidField.SolidInstruments.Example.ServiceApplication/ApplicationDependencyModule.cs @@ -2,17 +2,11 @@ // Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. // ================================================================================================================================= -using Microsoft.Azure.ServiceBus; -using Microsoft.Azure.ServiceBus.Core; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using RapidField.SolidInstruments.Core.Extensions; using RapidField.SolidInstruments.InversionOfControl.DotNetNative; -using RapidField.SolidInstruments.InversionOfControl.DotNetNative.Extensions; -using RapidField.SolidInstruments.Messaging; -using RapidField.SolidInstruments.Messaging.AzureServiceBus; +using RapidField.SolidInstruments.Messaging.DotNetNative.Rmq.Extensions; using System; -using AzureServiceBusMessage = Microsoft.Azure.ServiceBus.Message; namespace RapidField.SolidInstruments.Example.ServiceApplication { @@ -45,36 +39,6 @@ public ApplicationDependencyModule(IConfiguration applicationConfiguration) /// /// Configuration information for the application. /// - protected override void Configure(ServiceCollection configurator, IConfiguration applicationConfiguration) - { - // Register the configuration. - configurator.AddApplicationConfiguration(applicationConfiguration); - - // Register the service bus connection. - configurator.AddScoped((serviceProvider) => - { - var configuration = serviceProvider.GetService(); - var serviceBusConnectionString = configuration.GetConnectionString("SolidInstrumentsServiceBusDev"); - - if (serviceBusConnectionString.IsNullOrWhiteSpace()) - { - throw new ApplicationException("The service bus connection string was not found in configuration."); - } - - return new ServiceBusConnection(serviceBusConnectionString); - }); - - // Register messaging types. - configurator.AddScoped(); - configurator.AddScoped, AzureServiceBusMessageAdapter>((serviceProvider) => serviceProvider.GetService()); - configurator.AddScoped(); - configurator.AddScoped, AzureServiceBusClientFactory>((serviceProvider) => serviceProvider.GetService()); - configurator.AddScoped(); - configurator.AddScoped((serviceProvider) => serviceProvider.GetService()); - configurator.AddSingleton(); - configurator.AddSingleton((serviceProvider) => serviceProvider.GetService()); - configurator.AddSingleton(); - configurator.AddSingleton((serviceProvider) => serviceProvider.GetService()); - } + protected override void Configure(ServiceCollection configurator, IConfiguration applicationConfiguration) => configurator.AddSupportingTypesForRabbitMqMessaging(applicationConfiguration, "SolidInstrumentsServiceBusDev"); } } \ No newline at end of file diff --git a/example/RapidField.SolidInstruments.Example.ServiceApplication/ExampleMessagingServiceExecutor.cs b/example/RapidField.SolidInstruments.Example.ServiceApplication/ExampleMessagingServiceExecutor.cs index c697a8f5..9d50479a 100644 --- a/example/RapidField.SolidInstruments.Example.ServiceApplication/ExampleMessagingServiceExecutor.cs +++ b/example/RapidField.SolidInstruments.Example.ServiceApplication/ExampleMessagingServiceExecutor.cs @@ -41,12 +41,10 @@ protected override void AddSubscriptions(IMessageListeningProfile subscriptionPr { try { - // Add queue listeners. - subscriptionProfile.AddQueueListener(); - subscriptionProfile.AddQueueListener(); - subscriptionProfile.AddQueueListener(); - // Add topic listeners. + subscriptionProfile.AddTopicListener(); + subscriptionProfile.AddTopicListener(); + subscriptionProfile.AddTopicListener(); subscriptionProfile.AddTopicListener(); // Add request listeners. diff --git a/example/RapidField.SolidInstruments.Example.ServiceApplication/RapidField.SolidInstruments.Example.ServiceApplication.csproj b/example/RapidField.SolidInstruments.Example.ServiceApplication/RapidField.SolidInstruments.Example.ServiceApplication.csproj index 971218e3..0039640c 100644 --- a/example/RapidField.SolidInstruments.Example.ServiceApplication/RapidField.SolidInstruments.Example.ServiceApplication.csproj +++ b/example/RapidField.SolidInstruments.Example.ServiceApplication/RapidField.SolidInstruments.Example.ServiceApplication.csproj @@ -37,8 +37,8 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - + + @@ -46,6 +46,9 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in + + + diff --git a/example/RapidField.SolidInstruments.Example.ServiceApplication/appsettings.json b/example/RapidField.SolidInstruments.Example.ServiceApplication/appsettings.json index a247b242..0327bb4d 100644 --- a/example/RapidField.SolidInstruments.Example.ServiceApplication/appsettings.json +++ b/example/RapidField.SolidInstruments.Example.ServiceApplication/appsettings.json @@ -4,7 +4,7 @@ { "ConnectionStrings": { - "SolidInstrumentsServiceBusDev": "" // IMPORTANT Intentionally blank. Replace when testing. Do not commit. + "SolidInstrumentsServiceBusDev": "amqp://guest:guest@localhost:5672" // IMPORTANT Intentionally blank. Replace when testing. Do not commit. }, "Logging": { "IncludeScopes": false, diff --git a/example/RapidField.SolidInstruments.Example.WebApplication/RapidField.SolidInstruments.Example.WebApplication.csproj b/example/RapidField.SolidInstruments.Example.WebApplication/RapidField.SolidInstruments.Example.WebApplication.csproj index cd451e83..89996e76 100644 --- a/example/RapidField.SolidInstruments.Example.WebApplication/RapidField.SolidInstruments.Example.WebApplication.csproj +++ b/example/RapidField.SolidInstruments.Example.WebApplication/RapidField.SolidInstruments.Example.WebApplication.csproj @@ -17,8 +17,8 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - + + diff --git a/src/RapidField.SolidInstruments.Command.Autofac/RapidField.SolidInstruments.Command.Autofac.csproj b/src/RapidField.SolidInstruments.Command.Autofac/RapidField.SolidInstruments.Command.Autofac.csproj index f4843ee6..29500ecc 100644 --- a/src/RapidField.SolidInstruments.Command.Autofac/RapidField.SolidInstruments.Command.Autofac.csproj +++ b/src/RapidField.SolidInstruments.Command.Autofac/RapidField.SolidInstruments.Command.Autofac.csproj @@ -39,7 +39,7 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - + diff --git a/src/RapidField.SolidInstruments.Command.DotNetNative/RapidField.SolidInstruments.Command.DotNetNative.csproj b/src/RapidField.SolidInstruments.Command.DotNetNative/RapidField.SolidInstruments.Command.DotNetNative.csproj index b93d0d4a..ce043821 100644 --- a/src/RapidField.SolidInstruments.Command.DotNetNative/RapidField.SolidInstruments.Command.DotNetNative.csproj +++ b/src/RapidField.SolidInstruments.Command.DotNetNative/RapidField.SolidInstruments.Command.DotNetNative.csproj @@ -37,9 +37,9 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - + + + diff --git a/src/RapidField.SolidInstruments.Core/RapidField.SolidInstruments.Core.csproj b/src/RapidField.SolidInstruments.Core/RapidField.SolidInstruments.Core.csproj index 3f16d29d..0702c763 100644 --- a/src/RapidField.SolidInstruments.Core/RapidField.SolidInstruments.Core.csproj +++ b/src/RapidField.SolidInstruments.Core/RapidField.SolidInstruments.Core.csproj @@ -38,6 +38,6 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - + \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess.Autofac/RapidField.SolidInstruments.DataAccess.Autofac.csproj b/src/RapidField.SolidInstruments.DataAccess.Autofac/RapidField.SolidInstruments.DataAccess.Autofac.csproj index 559c8aa6..481bead2 100644 --- a/src/RapidField.SolidInstruments.DataAccess.Autofac/RapidField.SolidInstruments.DataAccess.Autofac.csproj +++ b/src/RapidField.SolidInstruments.DataAccess.Autofac/RapidField.SolidInstruments.DataAccess.Autofac.csproj @@ -39,7 +39,7 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - + diff --git a/src/RapidField.SolidInstruments.DataAccess.DotNetNative/RapidField.SolidInstruments.DataAccess.DotNetNative.csproj b/src/RapidField.SolidInstruments.DataAccess.DotNetNative/RapidField.SolidInstruments.DataAccess.DotNetNative.csproj index 17ada658..54dc3f06 100644 --- a/src/RapidField.SolidInstruments.DataAccess.DotNetNative/RapidField.SolidInstruments.DataAccess.DotNetNative.csproj +++ b/src/RapidField.SolidInstruments.DataAccess.DotNetNative/RapidField.SolidInstruments.DataAccess.DotNetNative.csproj @@ -37,9 +37,9 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - + + + diff --git a/src/RapidField.SolidInstruments.DataAccess.EntityFramework/RapidField.SolidInstruments.DataAccess.EntityFramework.csproj b/src/RapidField.SolidInstruments.DataAccess.EntityFramework/RapidField.SolidInstruments.DataAccess.EntityFramework.csproj index 43cbe290..ea040984 100644 --- a/src/RapidField.SolidInstruments.DataAccess.EntityFramework/RapidField.SolidInstruments.DataAccess.EntityFramework.csproj +++ b/src/RapidField.SolidInstruments.DataAccess.EntityFramework/RapidField.SolidInstruments.DataAccess.EntityFramework.csproj @@ -37,9 +37,9 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - + + + diff --git a/src/RapidField.SolidInstruments.EventAuthoring.Autofac/RapidField.SolidInstruments.EventAuthoring.Autofac.csproj b/src/RapidField.SolidInstruments.EventAuthoring.Autofac/RapidField.SolidInstruments.EventAuthoring.Autofac.csproj index 2c69ae69..3842c77e 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring.Autofac/RapidField.SolidInstruments.EventAuthoring.Autofac.csproj +++ b/src/RapidField.SolidInstruments.EventAuthoring.Autofac/RapidField.SolidInstruments.EventAuthoring.Autofac.csproj @@ -39,7 +39,7 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - + diff --git a/src/RapidField.SolidInstruments.EventAuthoring.DotNetNative/RapidField.SolidInstruments.EventAuthoring.DotNetNative.csproj b/src/RapidField.SolidInstruments.EventAuthoring.DotNetNative/RapidField.SolidInstruments.EventAuthoring.DotNetNative.csproj index e173f137..27db93cd 100644 --- a/src/RapidField.SolidInstruments.EventAuthoring.DotNetNative/RapidField.SolidInstruments.EventAuthoring.DotNetNative.csproj +++ b/src/RapidField.SolidInstruments.EventAuthoring.DotNetNative/RapidField.SolidInstruments.EventAuthoring.DotNetNative.csproj @@ -37,9 +37,9 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - + + + diff --git a/src/RapidField.SolidInstruments.InversionOfControl.Autofac/Extensions/ContainerBuilderExtensions.cs b/src/RapidField.SolidInstruments.InversionOfControl.Autofac/Extensions/ContainerBuilderExtensions.cs index 3287bac8..4710e8c5 100644 --- a/src/RapidField.SolidInstruments.InversionOfControl.Autofac/Extensions/ContainerBuilderExtensions.cs +++ b/src/RapidField.SolidInstruments.InversionOfControl.Autofac/Extensions/ContainerBuilderExtensions.cs @@ -4,6 +4,8 @@ using Autofac; using Microsoft.Extensions.Configuration; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using System; namespace RapidField.SolidInstruments.InversionOfControl.Autofac.Extensions { @@ -13,7 +15,7 @@ namespace RapidField.SolidInstruments.InversionOfControl.Autofac.Extensions public static class ContainerBuilderExtensions { /// - /// Registers the specified instance as a singleton. + /// Registers the specified instance as a singleton as an idempotent, safe operation. /// /// /// The current . @@ -21,6 +23,9 @@ public static class ContainerBuilderExtensions /// /// Configuration information for the application. /// - public static void RegisterApplicationConfiguration(this ContainerBuilder target, IConfiguration applicationConfiguration) => target.RegisterInstance(applicationConfiguration).SingleInstance(); + /// + /// is . + /// + public static void RegisterApplicationConfiguration(this ContainerBuilder target, IConfiguration applicationConfiguration) => target.RegisterInstance(applicationConfiguration.RejectIf().IsNull(nameof(applicationConfiguration))).IfNotRegistered(typeof(IConfiguration)).SingleInstance(); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.InversionOfControl.Autofac/RapidField.SolidInstruments.InversionOfControl.Autofac.csproj b/src/RapidField.SolidInstruments.InversionOfControl.Autofac/RapidField.SolidInstruments.InversionOfControl.Autofac.csproj index cc78680e..3cf84079 100644 --- a/src/RapidField.SolidInstruments.InversionOfControl.Autofac/RapidField.SolidInstruments.InversionOfControl.Autofac.csproj +++ b/src/RapidField.SolidInstruments.InversionOfControl.Autofac/RapidField.SolidInstruments.InversionOfControl.Autofac.csproj @@ -39,7 +39,7 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - + diff --git a/src/RapidField.SolidInstruments.InversionOfControl.DotNetNative/Extensions/IServiceCollectionExtensions.cs b/src/RapidField.SolidInstruments.InversionOfControl.DotNetNative/Extensions/IServiceCollectionExtensions.cs index 441f2319..129655ea 100644 --- a/src/RapidField.SolidInstruments.InversionOfControl.DotNetNative/Extensions/IServiceCollectionExtensions.cs +++ b/src/RapidField.SolidInstruments.InversionOfControl.DotNetNative/Extensions/IServiceCollectionExtensions.cs @@ -4,6 +4,8 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using RapidField.SolidInstruments.Core.ArgumentValidation; using RapidField.SolidInstruments.InversionOfControl.Extensions; using System; @@ -15,7 +17,7 @@ namespace RapidField.SolidInstruments.InversionOfControl.DotNetNative.Extensions public static class IServiceCollectionExtensions { /// - /// Registers the specified instance as a singleton. + /// Registers the specified instance as a singleton as an idempotent, safe operation. /// /// /// The current . @@ -26,7 +28,14 @@ public static class IServiceCollectionExtensions /// /// The resulting . /// - public static IServiceCollection AddApplicationConfiguration(this IServiceCollection target, IConfiguration applicationConfiguration) => target.AddSingleton(applicationConfiguration); + /// + /// is . + /// + public static IServiceCollection AddApplicationConfiguration(this IServiceCollection target, IConfiguration applicationConfiguration) + { + target.TryAddSingleton(applicationConfiguration.RejectIf().IsNull(nameof(applicationConfiguration))); + return target; + } /// /// Registers an native .NET dependency engine and provider factory with the current . diff --git a/src/RapidField.SolidInstruments.InversionOfControl.DotNetNative/RapidField.SolidInstruments.InversionOfControl.DotNetNative.csproj b/src/RapidField.SolidInstruments.InversionOfControl.DotNetNative/RapidField.SolidInstruments.InversionOfControl.DotNetNative.csproj index e35d5eb0..bf31618c 100644 --- a/src/RapidField.SolidInstruments.InversionOfControl.DotNetNative/RapidField.SolidInstruments.InversionOfControl.DotNetNative.csproj +++ b/src/RapidField.SolidInstruments.InversionOfControl.DotNetNative/RapidField.SolidInstruments.InversionOfControl.DotNetNative.csproj @@ -37,9 +37,9 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - + + + diff --git a/src/RapidField.SolidInstruments.InversionOfControl/RapidField.SolidInstruments.InversionOfControl.csproj b/src/RapidField.SolidInstruments.InversionOfControl/RapidField.SolidInstruments.InversionOfControl.csproj index 32dcf2f3..3588301e 100644 --- a/src/RapidField.SolidInstruments.InversionOfControl/RapidField.SolidInstruments.InversionOfControl.csproj +++ b/src/RapidField.SolidInstruments.InversionOfControl/RapidField.SolidInstruments.InversionOfControl.csproj @@ -37,9 +37,9 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - + + + diff --git a/src/RapidField.SolidInstruments.Messaging.Autofac.Asb/Extensions/ContainerBuilderExtensions.cs b/src/RapidField.SolidInstruments.Messaging.Autofac.Asb/Extensions/ContainerBuilderExtensions.cs new file mode 100644 index 00000000..ac1f0af8 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.Autofac.Asb/Extensions/ContainerBuilderExtensions.cs @@ -0,0 +1,68 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Autofac; +using Microsoft.Azure.ServiceBus; +using Microsoft.Extensions.Configuration; +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Core.Extensions; +using RapidField.SolidInstruments.InversionOfControl; +using RapidField.SolidInstruments.InversionOfControl.Autofac.Extensions; +using RapidField.SolidInstruments.Messaging.AzureServiceBus; +using RapidField.SolidInstruments.Messaging.TransportPrimitives; +using System; + +namespace RapidField.SolidInstruments.Messaging.Autofac.Asb.Extensions +{ + /// + /// Extends the class with inversion of control features to support Azure Service Bus messaging. + /// + public static class ContainerBuilderExtensions + { + /// + /// Registers a collection of types that establish support for in-memory service bus functionality. + /// + /// + /// The current . + /// + /// + /// Configuration information for the application. + /// + /// + /// The configuration connection string key name for the Azure Service Bus connection. + /// + /// + /// is empty. + /// + /// + /// is -or- + /// is . + /// + public static void RegisterSupportingTypesForAzureServiceBusMessaging(this ContainerBuilder target, IConfiguration applicationConfiguration, String connectionStringConfigurationKeyName) + { + target.RegisterApplicationConfiguration(applicationConfiguration); + _ = connectionStringConfigurationKeyName.RejectIf().IsNullOrEmpty(nameof(connectionStringConfigurationKeyName)); + + target.Register(context => + { + var configuration = context.Resolve(); + var serviceBusConnectionString = configuration.GetConnectionString(connectionStringConfigurationKeyName); + + if (serviceBusConnectionString.IsNullOrWhiteSpace()) + { + throw new DependencyResolutionException("The service bus connection string was not found in configuration."); + } + + return new ServiceBusConnection(serviceBusConnectionString); + }).AsSelf().InstancePerLifetimeScope(); + + target.RegisterType().As>().AsSelf().InstancePerLifetimeScope(); + target.RegisterType().As>().AsSelf().InstancePerLifetimeScope(); + target.RegisterType().As().AsSelf().InstancePerLifetimeScope(); + target.RegisterType().As().AsSelf().SingleInstance(); + target.RegisterType().As().AsSelf().SingleInstance(); + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.Autofac.Asb/RapidField.SolidInstruments.Messaging.Autofac.Asb.csproj b/src/RapidField.SolidInstruments.Messaging.Autofac.Asb/RapidField.SolidInstruments.Messaging.Autofac.Asb.csproj index 030619ad..1fe02de1 100644 --- a/src/RapidField.SolidInstruments.Messaging.Autofac.Asb/RapidField.SolidInstruments.Messaging.Autofac.Asb.csproj +++ b/src/RapidField.SolidInstruments.Messaging.Autofac.Asb/RapidField.SolidInstruments.Messaging.Autofac.Asb.csproj @@ -39,7 +39,7 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - + diff --git a/src/RapidField.SolidInstruments.Messaging.Autofac.Rmq/Extensions/ContainerBuilderExtensions.cs b/src/RapidField.SolidInstruments.Messaging.Autofac.Rmq/Extensions/ContainerBuilderExtensions.cs new file mode 100644 index 00000000..1c47e589 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.Autofac.Rmq/Extensions/ContainerBuilderExtensions.cs @@ -0,0 +1,232 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Autofac; +using Microsoft.Extensions.Configuration; +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Core.Extensions; +using RapidField.SolidInstruments.InversionOfControl.Autofac.Extensions; +using RapidField.SolidInstruments.Messaging.RabbitMq; +using RapidField.SolidInstruments.Messaging.RabbitMq.TransportPrimitives; +using RapidField.SolidInstruments.Messaging.TransportPrimitives; +using RapidField.SolidInstruments.Serialization; +using System; +using System.Diagnostics; + +namespace RapidField.SolidInstruments.Messaging.Autofac.Rmq.Extensions +{ + /// + /// Extends the class with inversion of control features to support RabbitMQ messaging. + /// + public static class ContainerBuilderExtensions + { + /// + /// Registers a collection of types that establish support for RabbitMQ functionality. + /// + /// + /// The current . + /// + /// + /// Configuration information for the application. + /// + /// + /// is . + /// + public static void RegisterSupportingTypesForRabbitMqMessaging(this ContainerBuilder target, IConfiguration applicationConfiguration) => target.RegisterSupportingTypesForRabbitMqMessaging(applicationConfiguration, new RabbitMqMessageTransport()); + + /// + /// Registers a collection of types that establish support for RabbitMQ functionality. + /// + /// + /// The current . + /// + /// + /// Configuration information for the application. + /// + /// + /// The connection URI for the target RabbitMQ instance. + /// + /// + /// is empty. + /// + /// + /// The connection URI defined by is not valid for AMQP + /// connections. + /// + /// + /// is -or- + /// is . + /// + public static void RegisterSupportingTypesForRabbitMqMessaging(this ContainerBuilder target, IConfiguration applicationConfiguration, String connectionStringConfigurationKeyName) + { + var connectionString = applicationConfiguration.RejectIf().IsNull(nameof(applicationConfiguration)).TargetArgument.GetConnectionString(connectionStringConfigurationKeyName)?.Trim(); + + if (connectionString.IsNullOrWhiteSpace()) + { + throw new ArgumentException("The service bus connection string was not found in configuration.", nameof(connectionStringConfigurationKeyName)); + } + + try + { + var connectionUri = new Uri(connectionString); + target.RegisterSupportingTypesForRabbitMqMessaging(applicationConfiguration, connectionUri); + } + catch (Exception exception) + { + throw new ArgumentException("The specified connection string does not contain a valid AMQP connection URI.", nameof(connectionStringConfigurationKeyName), exception); + } + } + + /// + /// Registers a collection of types that establish support for RabbitMQ functionality. + /// + /// + /// The current . + /// + /// + /// Configuration information for the application. + /// + /// + /// The connection URI for the target RabbitMQ instance. The default value is "amqp://guest:guest@localhost:5672". + /// + /// + /// is not valid for AMQP connections. + /// + /// + /// is -or- is + /// . + /// + public static void RegisterSupportingTypesForRabbitMqMessaging(this ContainerBuilder target, IConfiguration applicationConfiguration, Uri connectionUri) => target.RegisterSupportingTypesForRabbitMqMessaging(applicationConfiguration, new RabbitMqMessageTransport(connectionUri)); + + /// + /// Registers a collection of types that establish support for RabbitMQ functionality. + /// + /// + /// The current . + /// + /// + /// Configuration information for the application. + /// + /// + /// The connection URI for the target RabbitMQ instance. The default value is "amqp://guest:guest@localhost:5672". + /// + /// + /// The format that is used to serialize enqueued message bodies. The default value is + /// . + /// + /// + /// is not valid for AMQP connections. + /// + /// + /// is -or- is + /// . + /// + /// + /// is equal to . + /// + public static void RegisterSupportingTypesForRabbitMqMessaging(this ContainerBuilder target, IConfiguration applicationConfiguration, Uri connectionUri, SerializationFormat messageBodySerializationFormat) => target.RegisterSupportingTypesForRabbitMqMessaging(applicationConfiguration, new RabbitMqMessageTransport(connectionUri, messageBodySerializationFormat)); + + /// + /// Registers a collection of types that establish support for RabbitMQ functionality. + /// + /// + /// The current . + /// + /// + /// Configuration information for the application. + /// + /// + /// The name of the host to connect to. The default value is "localhost". + /// + /// + /// The port number to connect to, or to use the default port number (5672). + /// + /// + /// The name of the virtual host to connect to, or to omit a virtual host. + /// + /// + /// The user name for the connection, or to use the default user name ("guest"). + /// + /// + /// The password for the connection, or to use the default password ("guest"). + /// + /// + /// is empty. + /// + /// + /// is -or- is + /// . + /// + public static void RegisterSupportingTypesForRabbitMqMessaging(this ContainerBuilder target, IConfiguration applicationConfiguration, String hostName, Int32? portNumber, String virtualHost, String userName, String password) => target.RegisterSupportingTypesForRabbitMqMessaging(applicationConfiguration, new RabbitMqMessageTransport(hostName, portNumber, virtualHost, userName, password)); + + /// + /// Registers a collection of types that establish support for RabbitMQ functionality. + /// + /// + /// The current . + /// + /// + /// Configuration information for the application. + /// + /// + /// The name of the host to connect to. The default value is "localhost". + /// + /// + /// The port number to connect to, or to use the default port number (5672). + /// + /// + /// The name of the virtual host to connect to, or to omit a virtual host. + /// + /// + /// The user name for the connection, or to use the default user name ("guest"). + /// + /// + /// The password for the connection, or to use the default password ("guest"). + /// + /// + /// The format that is used to serialize enqueued message bodies. The default value is + /// . + /// + /// + /// is empty. + /// + /// + /// is -or- is + /// . + /// + /// + /// is equal to . + /// + public static void RegisterSupportingTypesForRabbitMqMessaging(this ContainerBuilder target, IConfiguration applicationConfiguration, String hostName, Int32? portNumber, String virtualHost, String userName, String password, SerializationFormat messageBodySerializationFormat) => target.RegisterSupportingTypesForRabbitMqMessaging(applicationConfiguration, new RabbitMqMessageTransport(hostName, portNumber, virtualHost, userName, password, messageBodySerializationFormat)); + + /// + /// Registers a collection of types that establish support for in-memory service bus functionality. + /// + /// + /// The current . + /// + /// + /// Configuration information for the application. + /// + /// + /// The transport instance that supports RabbitMQ messaging. + /// + /// + /// is . + /// + [DebuggerHidden] + private static void RegisterSupportingTypesForRabbitMqMessaging(this ContainerBuilder target, IConfiguration applicationConfiguration, RabbitMqMessageTransport transport) + { + target.RegisterApplicationConfiguration(applicationConfiguration); + target.RegisterInstance(transport).AsSelf().SingleInstance(); + target.RegisterInstance(transport.CreateConnection()).AsSelf().SingleInstance(); + target.RegisterType().As>().AsSelf().InstancePerLifetimeScope(); + target.RegisterType().As>().AsSelf().InstancePerLifetimeScope(); + target.RegisterType().As().AsSelf().InstancePerLifetimeScope(); + target.RegisterType().As().AsSelf().SingleInstance(); + target.RegisterType().As().AsSelf().SingleInstance(); + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.Autofac.Rmq/RapidField.SolidInstruments.Messaging.Autofac.Rmq.csproj b/src/RapidField.SolidInstruments.Messaging.Autofac.Rmq/RapidField.SolidInstruments.Messaging.Autofac.Rmq.csproj index 877d7d3f..e8bfba8b 100644 --- a/src/RapidField.SolidInstruments.Messaging.Autofac.Rmq/RapidField.SolidInstruments.Messaging.Autofac.Rmq.csproj +++ b/src/RapidField.SolidInstruments.Messaging.Autofac.Rmq/RapidField.SolidInstruments.Messaging.Autofac.Rmq.csproj @@ -39,7 +39,7 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - + diff --git a/src/RapidField.SolidInstruments.Messaging.Autofac/RapidField.SolidInstruments.Messaging.Autofac.csproj b/src/RapidField.SolidInstruments.Messaging.Autofac/RapidField.SolidInstruments.Messaging.Autofac.csproj index 5915fb5a..ca53641b 100644 --- a/src/RapidField.SolidInstruments.Messaging.Autofac/RapidField.SolidInstruments.Messaging.Autofac.csproj +++ b/src/RapidField.SolidInstruments.Messaging.Autofac/RapidField.SolidInstruments.Messaging.Autofac.csproj @@ -39,7 +39,7 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - + diff --git a/src/RapidField.SolidInstruments.Messaging.DotNetNative.Asb/Extensions/IServiceCollectionExtensions.cs b/src/RapidField.SolidInstruments.Messaging.DotNetNative.Asb/Extensions/IServiceCollectionExtensions.cs new file mode 100644 index 00000000..9d94739b --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.DotNetNative.Asb/Extensions/IServiceCollectionExtensions.cs @@ -0,0 +1,79 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Microsoft.Azure.ServiceBus; +using Microsoft.Azure.ServiceBus.Core; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Core.Extensions; +using RapidField.SolidInstruments.InversionOfControl; +using RapidField.SolidInstruments.InversionOfControl.DotNetNative.Extensions; +using RapidField.SolidInstruments.Messaging.AzureServiceBus; +using System; +using AzureServiceBusMessage = Microsoft.Azure.ServiceBus.Message; + +namespace RapidField.SolidInstruments.Messaging.DotNetNative.Asb.Extensions +{ + /// + /// Extends the interface with native .NET inversion of control features to support Azure + /// Service Bus messaging. + /// + public static class IServiceCollectionExtensions + { + /// + /// Registers a collection of types that establish support for Azure Service Bus functionality. + /// + /// + /// The current . + /// + /// + /// Configuration information for the application. + /// + /// + /// The configuration connection string key name for the Azure Service Bus connection. + /// + /// + /// The resulting . + /// + /// + /// is empty. + /// + /// + /// is -or- + /// is . + /// + public static IServiceCollection AddSupportingTypesForAzureServiceBusMessaging(this IServiceCollection target, IConfiguration applicationConfiguration, String connectionStringConfigurationKeyName) + { + target.AddApplicationConfiguration(applicationConfiguration); + _ = connectionStringConfigurationKeyName.RejectIf().IsNullOrEmpty(nameof(connectionStringConfigurationKeyName)); + + target.AddScoped((serviceProvider) => + { + var configuration = serviceProvider.GetService(); + var serviceBusConnectionString = configuration.GetConnectionString(connectionStringConfigurationKeyName)?.Trim(); + + if (serviceBusConnectionString.IsNullOrWhiteSpace()) + { + throw new DependencyResolutionException("The service bus connection string was not found in configuration."); + } + + return new ServiceBusConnection(serviceBusConnectionString); + }); + + target.AddScoped(); + target.AddScoped, AzureServiceBusMessageAdapter>((serviceProvider) => serviceProvider.GetService()); + target.AddScoped(); + target.AddScoped, AzureServiceBusClientFactory>((serviceProvider) => serviceProvider.GetService()); + target.AddScoped(); + target.AddScoped((serviceProvider) => serviceProvider.GetService()); + target.AddSingleton(); + target.AddSingleton((serviceProvider) => serviceProvider.GetService()); + target.AddSingleton(); + target.AddSingleton((serviceProvider) => serviceProvider.GetService()); + return target; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.DotNetNative.Asb/RapidField.SolidInstruments.Messaging.DotNetNative.Asb.csproj b/src/RapidField.SolidInstruments.Messaging.DotNetNative.Asb/RapidField.SolidInstruments.Messaging.DotNetNative.Asb.csproj index dbd6d4b3..33f3c665 100644 --- a/src/RapidField.SolidInstruments.Messaging.DotNetNative.Asb/RapidField.SolidInstruments.Messaging.DotNetNative.Asb.csproj +++ b/src/RapidField.SolidInstruments.Messaging.DotNetNative.Asb/RapidField.SolidInstruments.Messaging.DotNetNative.Asb.csproj @@ -37,9 +37,9 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - + + + diff --git a/src/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq/Extensions/IServiceCollectionExtensions.cs b/src/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq/Extensions/IServiceCollectionExtensions.cs new file mode 100644 index 00000000..da1b5c98 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq/Extensions/IServiceCollectionExtensions.cs @@ -0,0 +1,260 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.Core.Extensions; +using RapidField.SolidInstruments.InversionOfControl.DotNetNative.Extensions; +using RapidField.SolidInstruments.Messaging.RabbitMq; +using RapidField.SolidInstruments.Messaging.RabbitMq.TransportPrimitives; +using RapidField.SolidInstruments.Messaging.TransportPrimitives; +using RapidField.SolidInstruments.Serialization; +using System; +using System.Diagnostics; + +namespace RapidField.SolidInstruments.Messaging.DotNetNative.Rmq.Extensions +{ + /// + /// Extends the interface with native .NET inversion of control features to support RabbitMQ + /// messaging. + /// + public static class IServiceCollectionExtensions + { + /// + /// Registers a collection of types that establish support for RabbitMQ functionality. + /// + /// + /// The current . + /// + /// + /// Configuration information for the application. + /// + /// + /// The resulting . + /// + /// + /// is . + /// + public static IServiceCollection AddSupportingTypesForRabbitMqMessaging(this IServiceCollection target, IConfiguration applicationConfiguration) => target.AddSupportingTypesForRabbitMqMessaging(applicationConfiguration, new RabbitMqMessageTransport()); + + /// + /// Registers a collection of types that establish support for RabbitMQ functionality. + /// + /// + /// The current . + /// + /// + /// Configuration information for the application. + /// + /// + /// The connection URI for the target RabbitMQ instance. + /// + /// + /// The resulting . + /// + /// + /// is empty. + /// + /// + /// The connection URI defined by is not valid for AMQP + /// connections. + /// + /// + /// is -or- + /// is . + /// + public static IServiceCollection AddSupportingTypesForRabbitMqMessaging(this IServiceCollection target, IConfiguration applicationConfiguration, String connectionStringConfigurationKeyName) + { + var connectionString = applicationConfiguration.RejectIf().IsNull(nameof(applicationConfiguration)).TargetArgument.GetConnectionString(connectionStringConfigurationKeyName)?.Trim(); + + if (connectionString.IsNullOrWhiteSpace()) + { + throw new ArgumentException("The service bus connection string was not found in configuration.", nameof(connectionStringConfigurationKeyName)); + } + + try + { + var connectionUri = new Uri(connectionString); + return target.AddSupportingTypesForRabbitMqMessaging(applicationConfiguration, connectionUri); + } + catch (Exception exception) + { + throw new ArgumentException("The specified connection string does not contain a valid AMQP connection URI.", nameof(connectionStringConfigurationKeyName), exception); + } + } + + /// + /// Registers a collection of types that establish support for RabbitMQ functionality. + /// + /// + /// The current . + /// + /// + /// Configuration information for the application. + /// + /// + /// The connection URI for the target RabbitMQ instance. The default value is "amqp://guest:guest@localhost:5672". + /// + /// + /// The resulting . + /// + /// + /// is not valid for AMQP connections. + /// + /// + /// is -or- is + /// . + /// + public static IServiceCollection AddSupportingTypesForRabbitMqMessaging(this IServiceCollection target, IConfiguration applicationConfiguration, Uri connectionUri) => target.AddSupportingTypesForRabbitMqMessaging(applicationConfiguration, new RabbitMqMessageTransport(connectionUri)); + + /// + /// Registers a collection of types that establish support for RabbitMQ functionality. + /// + /// + /// The current . + /// + /// + /// Configuration information for the application. + /// + /// + /// The connection URI for the target RabbitMQ instance. The default value is "amqp://guest:guest@localhost:5672". + /// + /// + /// The format that is used to serialize enqueued message bodies. The default value is + /// . + /// + /// + /// The resulting . + /// + /// + /// is not valid for AMQP connections. + /// + /// + /// is -or- is + /// . + /// + /// + /// is equal to . + /// + public static IServiceCollection AddSupportingTypesForRabbitMqMessaging(this IServiceCollection target, IConfiguration applicationConfiguration, Uri connectionUri, SerializationFormat messageBodySerializationFormat) => target.AddSupportingTypesForRabbitMqMessaging(applicationConfiguration, new RabbitMqMessageTransport(connectionUri, messageBodySerializationFormat)); + + /// + /// Registers a collection of types that establish support for RabbitMQ functionality. + /// + /// + /// The current . + /// + /// + /// Configuration information for the application. + /// + /// + /// The name of the host to connect to. The default value is "localhost". + /// + /// + /// The port number to connect to, or to use the default port number (5672). + /// + /// + /// The name of the virtual host to connect to, or to omit a virtual host. + /// + /// + /// The user name for the connection, or to use the default user name ("guest"). + /// + /// + /// The password for the connection, or to use the default password ("guest"). + /// + /// + /// The resulting . + /// + /// + /// is empty. + /// + /// + /// is -or- is + /// . + /// + public static IServiceCollection AddSupportingTypesForRabbitMqMessaging(this IServiceCollection target, IConfiguration applicationConfiguration, String hostName, Int32? portNumber, String virtualHost, String userName, String password) => target.AddSupportingTypesForRabbitMqMessaging(applicationConfiguration, new RabbitMqMessageTransport(hostName, portNumber, virtualHost, userName, password)); + + /// + /// Registers a collection of types that establish support for RabbitMQ functionality. + /// + /// + /// The current . + /// + /// + /// Configuration information for the application. + /// + /// + /// The name of the host to connect to. The default value is "localhost". + /// + /// + /// The port number to connect to, or to use the default port number (5672). + /// + /// + /// The name of the virtual host to connect to, or to omit a virtual host. + /// + /// + /// The user name for the connection, or to use the default user name ("guest"). + /// + /// + /// The password for the connection, or to use the default password ("guest"). + /// + /// + /// The format that is used to serialize enqueued message bodies. The default value is + /// . + /// + /// + /// The resulting . + /// + /// + /// is empty. + /// + /// + /// is -or- is + /// . + /// + /// + /// is equal to . + /// + public static IServiceCollection AddSupportingTypesForRabbitMqMessaging(this IServiceCollection target, IConfiguration applicationConfiguration, String hostName, Int32? portNumber, String virtualHost, String userName, String password, SerializationFormat messageBodySerializationFormat) => target.AddSupportingTypesForRabbitMqMessaging(applicationConfiguration, new RabbitMqMessageTransport(hostName, portNumber, virtualHost, userName, password, messageBodySerializationFormat)); + + /// + /// Registers a collection of types that establish support for RabbitMQ functionality. + /// + /// + /// The current . + /// + /// + /// Configuration information for the application. + /// + /// + /// The transport instance that supports RabbitMQ messaging. + /// + /// + /// The resulting . + /// + /// + /// is . + /// + [DebuggerHidden] + private static IServiceCollection AddSupportingTypesForRabbitMqMessaging(this IServiceCollection target, IConfiguration applicationConfiguration, RabbitMqMessageTransport transport) + { + target.AddApplicationConfiguration(applicationConfiguration); + target.AddSingleton(transport); + target.AddSingleton(transport.CreateConnection()); + target.AddScoped(); + target.AddScoped, RabbitMqMessageAdapter>((serviceProvider) => serviceProvider.GetService()); + target.AddScoped(); + target.AddScoped, RabbitMqClientFactory>((serviceProvider) => serviceProvider.GetService()); + target.AddScoped(); + target.AddScoped((serviceProvider) => serviceProvider.GetService()); + target.AddSingleton(); + target.AddSingleton((serviceProvider) => serviceProvider.GetService()); + target.AddSingleton(); + target.AddSingleton((serviceProvider) => serviceProvider.GetService()); + return target; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq.csproj b/src/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq.csproj index d5cc1f97..3ba2c17f 100644 --- a/src/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq.csproj +++ b/src/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq.csproj @@ -37,9 +37,9 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - + + + diff --git a/src/RapidField.SolidInstruments.Messaging.DotNetNative/RapidField.SolidInstruments.Messaging.DotNetNative.csproj b/src/RapidField.SolidInstruments.Messaging.DotNetNative/RapidField.SolidInstruments.Messaging.DotNetNative.csproj index 22ad0da0..3394f059 100644 --- a/src/RapidField.SolidInstruments.Messaging.DotNetNative/RapidField.SolidInstruments.Messaging.DotNetNative.csproj +++ b/src/RapidField.SolidInstruments.Messaging.DotNetNative/RapidField.SolidInstruments.Messaging.DotNetNative.csproj @@ -37,9 +37,9 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - - - + + + diff --git a/src/RapidField.SolidInstruments.Messaging.RabbitMq/AssemblyAttributes.cs b/src/RapidField.SolidInstruments.Messaging.RabbitMq/AssemblyAttributes.cs index a32e4acd..dc8ea60f 100644 --- a/src/RapidField.SolidInstruments.Messaging.RabbitMq/AssemblyAttributes.cs +++ b/src/RapidField.SolidInstruments.Messaging.RabbitMq/AssemblyAttributes.cs @@ -4,4 +4,6 @@ using System.Runtime.CompilerServices; -[assembly: DisablePrivateReflection()] \ No newline at end of file +[assembly: DisablePrivateReflection()] +[assembly: InternalsVisibleTo("RapidField.SolidInstruments.Messaging.Autofac.Rmq")] +[assembly: InternalsVisibleTo("RapidField.SolidInstruments.Messaging.DotNetNative.Rmq")] \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.RabbitMq/TransportPrimitives/RabbitMqMessageTransport.cs b/src/RapidField.SolidInstruments.Messaging.RabbitMq/TransportPrimitives/RabbitMqMessageTransport.cs index e46d21fb..ede3567c 100644 --- a/src/RapidField.SolidInstruments.Messaging.RabbitMq/TransportPrimitives/RabbitMqMessageTransport.cs +++ b/src/RapidField.SolidInstruments.Messaging.RabbitMq/TransportPrimitives/RabbitMqMessageTransport.cs @@ -13,8 +13,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading; using System.Threading.Tasks; -using IRabbitMqChannel = RabbitMQ.Client.IModel; using IRabbitMqConnection = RabbitMQ.Client.IConnection; using IRabbitMqConnectionFactory = RabbitMQ.Client.IConnectionFactory; using RabbitMqConnectionFactory = RabbitMQ.Client.ConnectionFactory; @@ -40,7 +40,7 @@ internal RabbitMqMessageTransport() /// Initializes a new instance of the class. /// /// - /// The connection URI for the target RabbitMQ instance. + /// The connection URI for the target RabbitMQ instance. The default value is "amqp://guest:guest@localhost:5672". /// /// /// is not valid for AMQP connections. @@ -59,7 +59,7 @@ internal RabbitMqMessageTransport(Uri connectionUri) /// Initializes a new instance of the class. /// /// - /// The connection URI for the target RabbitMQ instance. + /// The connection URI for the target RabbitMQ instance. The default value is "amqp://guest:guest@localhost:5672". /// /// /// The format that is used to serialize enqueued message bodies. The default value is @@ -168,10 +168,11 @@ internal RabbitMqMessageTransport(String hostName, Int32? portNumber, String vir /// [DebuggerHidden] private RabbitMqMessageTransport(IRabbitMqConnectionFactory connectionFactory, SerializationFormat messageBodySerializationFormat) - : base() + : base(Core.Concurrency.ConcurrencyControlMode.ProcessorCountSemaphore) { + connectionFactory.RequestedHeartbeat = HeartbeatFrequency; + connectionFactory.UseBackgroundThreadsForIO = true; SharedConnection = connectionFactory.RejectIf().IsNull(nameof(connectionFactory)).TargetArgument.CreateConnection(); - Channel = SharedConnection.CreateModel(); MessageBodySerializationFormat = messageBodySerializationFormat.RejectIf().IsEqualToValue(SerializationFormat.Unspecified, nameof(messageBodySerializationFormat)); } @@ -225,11 +226,21 @@ public Task ConveyFailureToQueueAsync(MessageLockToken lockToken, IMessagingEnti { RejectIfDisposed(); - return Task.Factory.StartNew(() => + return AutoAcknowledgeIsEnabled ? Task.CompletedTask : Task.Factory.StartNew(() => { try { - Channel.BasicReject(lockToken.DeliveryTag, true); + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + + using (var channel = SharedConnection.CreateModel()) + { + channel.ConfirmSelect(); + channel.BasicReject(lockToken.DeliveryTag, true); + channel.WaitForConfirmsOrDie(); + } + } } catch (Exception exception) { @@ -268,11 +279,21 @@ public Task ConveyFailureToSubscriptionAsync(MessageLockToken lockToken, IMessag { RejectIfDisposed(); - return Task.Factory.StartNew(() => + return AutoAcknowledgeIsEnabled ? Task.CompletedTask : Task.Factory.StartNew(() => { try { - Channel.BasicReject(lockToken.DeliveryTag, true); + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + + using (var channel = SharedConnection.CreateModel()) + { + channel.ConfirmSelect(); + channel.BasicReject(lockToken.DeliveryTag, true); + channel.WaitForConfirmsOrDie(); + } + } } catch (Exception exception) { @@ -308,11 +329,21 @@ public Task ConveySuccessToQueueAsync(MessageLockToken lockToken, IMessagingEnti { RejectIfDisposed(); - return Task.Factory.StartNew(() => + return AutoAcknowledgeIsEnabled ? Task.CompletedTask : Task.Factory.StartNew(() => { try { - Channel.BasicAck(lockToken.DeliveryTag, false); + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + + using (var channel = SharedConnection.CreateModel()) + { + channel.ConfirmSelect(); + channel.BasicAck(lockToken.DeliveryTag, false); + channel.WaitForConfirmsOrDie(); + } + } } catch (Exception exception) { @@ -348,11 +379,21 @@ public Task ConveySuccessToSubscriptionAsync(MessageLockToken lockToken, IMessag { RejectIfDisposed(); - return Task.Factory.StartNew(() => + return AutoAcknowledgeIsEnabled ? Task.CompletedTask : Task.Factory.StartNew(() => { try { - Channel.BasicAck(lockToken.DeliveryTag, false); + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + + using (var channel = SharedConnection.CreateModel()) + { + channel.ConfirmSelect(); + channel.BasicAck(lockToken.DeliveryTag, false); + channel.WaitForConfirmsOrDie(); + } + } } catch (Exception exception) { @@ -502,7 +543,7 @@ public Task CreateSubscriptionAsync(IMessagingEntityPath path, String subscripti { RejectIfDisposed(); - if (TryCreateSubscription(path.RejectIf().IsNull(nameof(path)).TargetArgument, subscriptionName.RejectIf().IsNullOrEmpty(nameof(subscriptionName)))) + if (TryCreateSubscription(path.RejectIf().IsNull(nameof(path)).TargetArgument, subscriptionName.RejectIf().IsNullOrEmpty(nameof(subscriptionName)).TargetArgument)) { return; } @@ -658,7 +699,7 @@ public Task DestroySubscriptionAsync(IMessagingEntityPath path, String subscript { RejectIfDisposed(); - if (TryDestroySubscription(path.RejectIf().IsNull(nameof(path)).TargetArgument, subscriptionName.RejectIf().IsNullOrEmpty(nameof(subscriptionName)))) + if (TryDestroySubscription(path.RejectIf().IsNull(nameof(path)).TargetArgument, subscriptionName.RejectIf().IsNullOrEmpty(nameof(subscriptionName)).TargetArgument)) { return; } @@ -714,20 +755,8 @@ public Task DestroyTopicAsync(IMessagingEntityPath path) => Task.Factory.StartNe public Boolean QueueExists(IMessagingEntityPath path) { RejectIfDisposed(); - - try - { - DeclareAndBindQueue(path); - return true; - } - catch (ArgumentNullException) - { - throw; - } - catch - { - return false; - } + DeclareAndBindQueue(path); + return true; } /// @@ -821,7 +850,7 @@ public Boolean QueueExists(IMessagingEntityPath path) /// public Task SendToQueueAsync(IMessagingEntityPath path, PrimitiveMessage message) { - var queueName = path.RejectIf().IsNull(nameof(path)).ToString(); + var queueName = path.RejectIf().IsNull(nameof(path)).TargetArgument.ToString(); var exchangeName = $"{EntityPathExchangePrefix}{DelimitingCharacterForExchangePrefix}{queueName}"; _ = message.RejectIf().IsNull(nameof(message)); @@ -830,7 +859,21 @@ public Task SendToQueueAsync(IMessagingEntityPath path, PrimitiveMessage message try { var serializer = new DynamicSerializer(MessageBodySerializationFormat); - Channel.BasicPublish(exchangeName, queueName, false, null, new ReadOnlyMemory(serializer.Serialize(message))); + var body = new ReadOnlyMemory(serializer.Serialize(message)); + + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + + using (var channel = SharedConnection.CreateModel()) + { + var basicProperties = channel.CreateBasicProperties(); + basicProperties.Persistent = true; + channel.ConfirmSelect(); + channel.BasicPublish(exchangeName, queueName, true, basicProperties, body); + channel.WaitForConfirmsOrDie(); + } + } } catch (Exception exception) { @@ -865,7 +908,7 @@ public Task SendToQueueAsync(IMessagingEntityPath path, PrimitiveMessage message /// public Task SendToTopicAsync(IMessagingEntityPath path, PrimitiveMessage message) { - var topicName = path.RejectIf().IsNull(nameof(path)).ToString(); + var topicName = path.RejectIf().IsNull(nameof(path)).TargetArgument.ToString(); var exchangeName = $"{EntityPathExchangePrefix}{DelimitingCharacterForExchangePrefix}{topicName}"; _ = message.RejectIf().IsNull(nameof(message)); @@ -874,7 +917,21 @@ public Task SendToTopicAsync(IMessagingEntityPath path, PrimitiveMessage message try { var serializer = new DynamicSerializer(MessageBodySerializationFormat); - Channel.BasicPublish(exchangeName, null, false, null, new ReadOnlyMemory(serializer.Serialize(message))); + var body = new ReadOnlyMemory(serializer.Serialize(message)); + + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + + using (var channel = SharedConnection.CreateModel()) + { + var basicProperties = channel.CreateBasicProperties(); + basicProperties.Persistent = true; + channel.ConfirmSelect(); + channel.BasicPublish(exchangeName, topicName, true, basicProperties, body); + channel.WaitForConfirmsOrDie(); + } + } } catch (Exception exception) { @@ -911,23 +968,8 @@ public Boolean SubscriptionExists(IMessagingEntityPath path, String subscription if (TopicExists(path)) { - try - { - DeclareAndBindSubscription(path, subscriptionName); - return true; - } - catch (ArgumentEmptyException) - { - throw; - } - catch (ArgumentNullException) - { - throw; - } - catch - { - return false; - } + DeclareAndBindSubscription(path, subscriptionName); + return true; } return false; @@ -951,20 +993,8 @@ public Boolean SubscriptionExists(IMessagingEntityPath path, String subscription public Boolean TopicExists(IMessagingEntityPath path) { RejectIfDisposed(); - - try - { - DeclareTopic(path); - return true; - } - catch (ArgumentNullException) - { - throw; - } - catch - { - return false; - } + DeclareTopic(path); + return true; } /// @@ -1017,15 +1047,7 @@ public Boolean TryCreateQueue(IMessagingEntityPath path, TimeSpan messageLockExp return false; } - using (var controlToken = StateControl.Enter()) - { - if (IsDisposedOrDisposing) - { - return false; - } - - return QueueExists(path); - } + return QueueExists(path); } /// @@ -1047,15 +1069,7 @@ public Boolean TryCreateSubscription(IMessagingEntityPath path, String subscript return false; } - using (var controlToken = StateControl.Enter()) - { - if (IsDisposedOrDisposing) - { - return false; - } - - return SubscriptionExists(path, subscriptionName); - } + return SubscriptionExists(path, subscriptionName); } /// @@ -1108,15 +1122,7 @@ public Boolean TryCreateTopic(IMessagingEntityPath path, TimeSpan messageLockExp return false; } - using (var controlToken = StateControl.Enter()) - { - if (IsDisposedOrDisposing) - { - return false; - } - - return TopicExists(path); - } + return TopicExists(path); } /// @@ -1135,27 +1141,24 @@ public Boolean TryDestroyQueue(IMessagingEntityPath path) return false; } + var queueName = path.ToString(); + var exchangeName = $"{EntityPathExchangePrefix}{DelimitingCharacterForExchangePrefix}{queueName}"; + using (var controlToken = StateControl.Enter()) { - if (IsDisposedOrDisposing) - { - return false; - } + RejectIfDisposed(); - try + using (var channel = SharedConnection.CreateModel()) { - var queueName = path.ToString(); - var exchangeName = $"{EntityPathExchangePrefix}{DelimitingCharacterForExchangePrefix}{queueName}"; - Channel.QueueUnbind(queueName, exchangeName, queueName); - Channel.QueueDelete(queueName); - Channel.ExchangeDelete(exchangeName); - return true; - } - catch - { - return false; + channel.ConfirmSelect(); + channel.QueueUnbind(queueName, exchangeName, queueName); + channel.QueueDelete(queueName); + channel.ExchangeDelete(exchangeName); + channel.WaitForConfirmsOrDie(); } } + + return true; } /// @@ -1178,27 +1181,24 @@ public Boolean TryDestroySubscription(IMessagingEntityPath path, String subscrip return false; } + var topicName = path.ToString(); + var queueName = subscriptionName; + var exchangeName = $"{EntityPathExchangePrefix}{DelimitingCharacterForExchangePrefix}{topicName}"; + using (var controlToken = StateControl.Enter()) { - if (IsDisposedOrDisposing) - { - return false; - } + RejectIfDisposed(); - try + using (var channel = SharedConnection.CreateModel()) { - var topicName = path.ToString(); - var queueName = subscriptionName; - var exchangeName = $"{EntityPathExchangePrefix}{DelimitingCharacterForExchangePrefix}{topicName}"; - Channel.QueueUnbind(queueName, exchangeName, queueName); - Channel.QueueDelete(queueName); - return true; - } - catch - { - return false; + channel.ConfirmSelect(); + channel.QueueUnbind(queueName, exchangeName, queueName); + channel.QueueDelete(queueName); + channel.WaitForConfirmsOrDie(); } } + + return true; } /// @@ -1217,25 +1217,22 @@ public Boolean TryDestroyTopic(IMessagingEntityPath path) return false; } + var topicName = path.ToString(); + var exchangeName = $"{EntityPathExchangePrefix}{DelimitingCharacterForExchangePrefix}{topicName}"; + using (var controlToken = StateControl.Enter()) { - if (IsDisposedOrDisposing) - { - return false; - } + RejectIfDisposed(); - try + using (var channel = SharedConnection.CreateModel()) { - var topicName = path.ToString(); - var exchangeName = $"{EntityPathExchangePrefix}{DelimitingCharacterForExchangePrefix}{topicName}"; - Channel.ExchangeDelete(exchangeName); - return true; - } - catch - { - return false; + channel.ConfirmSelect(); + channel.ExchangeDelete(exchangeName); + channel.WaitForConfirmsOrDie(); } } + + return true; } /// @@ -1255,7 +1252,6 @@ protected override void Dispose(Boolean disposing) DestroyAllConnections(); } - Channel?.Dispose(); SharedConnection?.Dispose(); } } @@ -1338,14 +1334,33 @@ protected override void Dispose(Boolean disposing) [DebuggerHidden] private void DeclareAndBindQueue(IMessagingEntityPath path) { - var queueName = path.RejectIf().IsNull(nameof(path)).ToString(); + var queueName = path.RejectIf().IsNull(nameof(path)).TargetArgument.ToString(); var exchangeName = $"{EntityPathExchangePrefix}{DelimitingCharacterForExchangePrefix}{queueName}"; try { - Channel.ExchangeDeclare(exchangeName, ExchangeType.Direct, true, false); - Channel.QueueDeclare(queueName, true, false, false); - Channel.QueueBind(queueName, exchangeName, queueName); + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + var channel = SharedConnection.CreateModel(); + + try + { + channel.ExchangeDeclare(exchangeName, ExchangeType.Direct, true, false); + channel.QueueDeclare(queueName, true, false, false); + channel.QueueBind(queueName, exchangeName, queueName); + Task.Factory.StartNew(() => + { + Thread.Sleep(ChannelKeepAliveDuration); + channel.Dispose(); + }); + } + catch + { + channel.Dispose(); + throw; + } + } } catch (Exception exception) { @@ -1376,14 +1391,33 @@ private void DeclareAndBindQueue(IMessagingEntityPath path) [DebuggerHidden] private void DeclareAndBindSubscription(IMessagingEntityPath path, String subscriptionName) { - var topicName = path.RejectIf().IsNull(nameof(path)).ToString(); + var topicName = path.RejectIf().IsNull(nameof(path)).TargetArgument.ToString(); var queueName = subscriptionName.RejectIf().IsNullOrEmpty(nameof(path)); var exchangeName = $"{EntityPathExchangePrefix}{DelimitingCharacterForExchangePrefix}{topicName}"; try { - Channel.QueueDeclare(queueName, true, true, true); - Channel.QueueBind(queueName, exchangeName, queueName); + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + var channel = SharedConnection.CreateModel(); + + try + { + channel.QueueDeclare(queueName, true, true, true); + channel.QueueBind(queueName, exchangeName, topicName); + Task.Factory.StartNew(() => + { + Thread.Sleep(ChannelKeepAliveDuration); + channel.Dispose(); + }); + } + catch + { + channel.Dispose(); + throw; + } + } } catch (Exception exception) { @@ -1406,12 +1440,31 @@ private void DeclareAndBindSubscription(IMessagingEntityPath path, String subscr [DebuggerHidden] private void DeclareTopic(IMessagingEntityPath path) { - var topicName = path.RejectIf().IsNull(nameof(path)).ToString(); + var topicName = path.RejectIf().IsNull(nameof(path)).TargetArgument.ToString(); var exchangeName = $"{EntityPathExchangePrefix}{DelimitingCharacterForExchangePrefix}{topicName}"; try { - Channel.ExchangeDeclare(exchangeName, ExchangeType.Fanout, true, false); + using (var controlToken = StateControl.Enter()) + { + RejectIfDisposed(); + var channel = SharedConnection.CreateModel(); + + try + { + channel.ExchangeDeclare(exchangeName, ExchangeType.Fanout, true, false); + Task.Factory.StartNew(() => + { + Thread.Sleep(ChannelKeepAliveDuration); + channel.Dispose(); + }); + } + catch + { + channel.Dispose(); + throw; + } + } } catch (Exception exception) { @@ -1465,6 +1518,12 @@ public SerializationFormat MessageBodySerializationFormat get; } + /// + /// Represents a value indicating whether or not automatic acknowledgment is enabled. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal const Boolean AutoAcknowledgeIsEnabled = true; + /// /// Represents the delimiting character that follows the exchange prefix token. /// @@ -1496,10 +1555,10 @@ public SerializationFormat MessageBodySerializationFormat internal const String SubscriptionNamePrefix = "Sub"; /// - /// Represents the AMQP model that is used to manage the transport. + /// Represents the shared connection to the target RabbitMQ instance through which all other connections communicate. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - internal readonly IRabbitMqChannel Channel; + internal readonly IRabbitMqConnection SharedConnection; /// /// Represents the valid URI scheme for AMQP connections. @@ -1532,16 +1591,22 @@ public SerializationFormat MessageBodySerializationFormat private const String DefaultConnectionUserName = "guest"; /// - /// Represents a collection of active connections to the current , which are keyed by - /// identifier. + /// Represents the length of time to keep channels alive after issuing non-publish calls. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly ConcurrentDictionary ConnectionDictionary = new ConcurrentDictionary(); + private static readonly TimeSpan ChannelKeepAliveDuration = TimeSpan.FromMilliseconds(21); /// - /// Represents the shared connection to the target RabbitMQ instance through which all other connections communicate. + /// Represents the polling frequency used by RabbitMQ connections. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private static readonly TimeSpan HeartbeatFrequency = TimeSpan.FromSeconds(5); + + /// + /// Represents a collection of active connections to the current , which are keyed by + /// identifier. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly IRabbitMqConnection SharedConnection; + private readonly ConcurrentDictionary ConnectionDictionary = new ConcurrentDictionary(); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.RabbitMq/TransportPrimitives/RabbitMqMessageTransportConnection.cs b/src/RapidField.SolidInstruments.Messaging.RabbitMq/TransportPrimitives/RabbitMqMessageTransportConnection.cs index ba1e4fa8..1f971fae 100644 --- a/src/RapidField.SolidInstruments.Messaging.RabbitMq/TransportPrimitives/RabbitMqMessageTransportConnection.cs +++ b/src/RapidField.SolidInstruments.Messaging.RabbitMq/TransportPrimitives/RabbitMqMessageTransportConnection.cs @@ -37,6 +37,7 @@ internal sealed class RabbitMqMessageTransportConnection : Instrument, IMessageT internal RabbitMqMessageTransportConnection(RabbitMqMessageTransport transport) : base() { + Channels = new List(); Consumers = new List(); Identifier = Guid.NewGuid(); State = MessageTransportConnectionState.Open; @@ -57,9 +58,19 @@ public void Close() try { - if (TransportReference.Connections.Any(connection => connection.Identifier == Identifier)) + try { - TransportReference.CloseConnection(this); + foreach (var channel in Channels) + { + channel.Dispose(); + } + } + finally + { + if (TransportReference.Connections.Any(connection => connection.Identifier == Identifier)) + { + TransportReference.CloseConnection(this); + } } } catch (Exception exception) @@ -90,54 +101,72 @@ public void Close() /// public void RegisterQueueHandler(IMessagingEntityPath queuePath, Action handleMessageAction) { - RejectIfDisposed(); - - if (Transport.QueueExists(queuePath)) + using (var controlToken = StateControl.Enter()) { - _ = handleMessageAction.RejectIf().IsNull(nameof(handleMessageAction)); - var queueName = queuePath.ToString(); - var consumer = new RabbitMqConsumer(Channel); - consumer.Received += (model, eventArguments) => + RejectIfDisposed(); + + if (Transport.QueueExists(queuePath)) { + _ = handleMessageAction.RejectIf().IsNull(nameof(handleMessageAction)); + var queueName = queuePath.ToString(); + var channel = TransportReference.SharedConnection.CreateModel(); + channel.ConfirmSelect(); + try { - var messageBytes = eventArguments.Body.ToArray(); - var serializer = new DynamicSerializer(Transport.MessageBodySerializationFormat); - var message = serializer.Deserialize(messageBytes); + var consumer = new RabbitMqConsumer(channel); + consumer.Received += (model, eventArguments) => + { + try + { + var messageBytes = eventArguments.Body.ToArray(); + var serializer = new DynamicSerializer(Transport.MessageBodySerializationFormat); + var message = serializer.Deserialize(messageBytes); + message.LockToken.DeliveryTag = eventArguments.DeliveryTag; + + try + { + handleMessageAction(message); + } + catch (Exception exception) + { + throw new MessageListeningException($"An exception was raised while processing a message from queue \"{queueName}\". Message identifier: {message?.Identifier.ToSerializedString() ?? "unknown"}.", exception); + } + } + catch (MessageListeningException) + { + throw; + } + catch (SerializationException exception) + { + throw new MessageListeningException($"Failed to deserialize a message from queue \"{queueName}\". Expected format: {Transport.MessageBodySerializationFormat}.", exception); + } + }; try { - handleMessageAction(message); + channel.BasicConsume(queueName, RabbitMqMessageTransport.AutoAcknowledgeIsEnabled, consumer); + channel.WaitForConfirmsOrDie(); + Consumers.Add(consumer); } catch (Exception exception) { - throw new MessageListeningException($"An exception was raised while processing a message from queue \"{queueName}\". Message identifier: {message?.Identifier.ToSerializedString() ?? "unknown"}.", exception); + throw new MessageListeningException($"An exception was raised while attempting to register a consumer for queue \"{queueName}\".", exception); } + + Channels.Add(channel); } - catch (MessageListeningException) + catch { + channel.Dispose(); throw; } - catch (SerializationException exception) - { - throw new MessageListeningException($"Failed to deserialize a message from queue \"{queueName}\". Expected format: {Transport.MessageBodySerializationFormat}.", exception); - } - }; - try - { - Channel.BasicConsume(queueName, false, consumer); - Consumers.Add(consumer); - } - catch (Exception exception) - { - throw new MessageListeningException($"An exception was raised while attempting to register a consumer for queue \"{queueName}\".", exception); + return; } - return; + throw new InvalidOperationException($"Failed to register queue handler. The specified queue, \"{queuePath}\", does not exist."); } - - throw new InvalidOperationException($"Failed to register queue handler. The specified queue, \"{queuePath}\", does not exist."); } /// @@ -167,54 +196,72 @@ public void RegisterQueueHandler(IMessagingEntityPath queuePath, Action public void RegisterSubscriptionHandler(IMessagingEntityPath topicPath, String subscriptionName, Action handleMessageAction) { - RejectIfDisposed(); - - if (Transport.SubscriptionExists(topicPath, subscriptionName)) + using (var controlToken = StateControl.Enter()) { - _ = handleMessageAction.RejectIf().IsNull(nameof(handleMessageAction)); - var topicName = topicPath.ToString(); - var consumer = new RabbitMqConsumer(Channel); - consumer.Received += (model, eventArguments) => + RejectIfDisposed(); + + if (Transport.SubscriptionExists(topicPath, subscriptionName)) { + _ = handleMessageAction.RejectIf().IsNull(nameof(handleMessageAction)); + var topicName = topicPath.ToString(); + var channel = TransportReference.SharedConnection.CreateModel(); + channel.ConfirmSelect(); + try { - var messageBytes = eventArguments.Body.ToArray(); - var serializer = new DynamicSerializer(Transport.MessageBodySerializationFormat); - var message = serializer.Deserialize(messageBytes); + var consumer = new RabbitMqConsumer(channel); + consumer.Received += (model, eventArguments) => + { + try + { + var messageBytes = eventArguments.Body.ToArray(); + var serializer = new DynamicSerializer(Transport.MessageBodySerializationFormat); + var message = serializer.Deserialize(messageBytes); + message.LockToken.DeliveryTag = eventArguments.DeliveryTag; + + try + { + handleMessageAction(message); + } + catch (Exception exception) + { + throw new MessageListeningException($"An exception was raised while processing a message from topic \"{topicName}\" for subscription \"{subscriptionName}\". Message identifier: {message?.Identifier.ToSerializedString() ?? "unknown"}.", exception); + } + } + catch (MessageListeningException) + { + throw; + } + catch (SerializationException exception) + { + throw new MessageListeningException($"Failed to deserialize a message from topic \"{topicName}\" for subscription \"{subscriptionName}\". Expected format: {Transport.MessageBodySerializationFormat}.", exception); + } + }; try { - handleMessageAction(message); + channel.BasicConsume(subscriptionName, RabbitMqMessageTransport.AutoAcknowledgeIsEnabled, consumer); + channel.WaitForConfirmsOrDie(); + Consumers.Add(consumer); } catch (Exception exception) { - throw new MessageListeningException($"An exception was raised while processing a message from topic \"{topicName}\" for subscription \"{subscriptionName}\". Message identifier: {message?.Identifier.ToSerializedString() ?? "unknown"}.", exception); + throw new MessageListeningException($"An exception was raised while attempting to register a consumer for topic \"{topicName}\" with subscription name \"{subscriptionName}\".", exception); } + + Channels.Add(channel); } - catch (MessageListeningException) + catch { + channel.Dispose(); throw; } - catch (SerializationException exception) - { - throw new MessageListeningException($"Failed to deserialize a message from topic \"{topicName}\" for subscription \"{subscriptionName}\". Expected format: {Transport.MessageBodySerializationFormat}.", exception); - } - }; - try - { - Channel.BasicConsume(subscriptionName, false, consumer); - Consumers.Add(consumer); - } - catch (Exception exception) - { - throw new MessageListeningException($"An exception was raised while attempting to register a consumer for topic \"{topicName}\" with subscription name \"{subscriptionName}\".", exception); + return; } - return; + throw new InvalidOperationException($"Failed to register subscription handler. The specified subscription, \"{subscriptionName}\", does not exist."); } - - throw new InvalidOperationException($"Failed to register subscription handler. The specified subscription, \"{subscriptionName}\", does not exist."); } /// @@ -267,22 +314,11 @@ public MessageTransportConnectionState State public IMessageTransport Transport => State == MessageTransportConnectionState.Open ? TransportReference : throw new MessageTransportConnectionClosedException($"Connection {Identifier.ToSerializedString()} is closed."); /// - /// Gets the AMQP model that is used to manage the transport. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private IRabbitMqChannel Channel => TransportReference?.Channel; - - /// - /// Represents the maximum number of messages to dequeue from each entity during a single polling permutation. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const Int32 MessageReceiptBatchSize = 34; - - /// - /// Represents the interval, in milliseconds, at which is polled for receive operations. + /// Represents a collection of channels that are registered with the current + /// . /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const Int32 PollingIntervalInMilliseconds = 987; + private readonly IList Channels; /// /// Represents a collection of consumers that are registered with the current diff --git a/src/RapidField.SolidInstruments.Messaging/IMessageProcessingInformation.cs b/src/RapidField.SolidInstruments.Messaging/IMessageProcessingInformation.cs index 9d4068bc..d2722aca 100644 --- a/src/RapidField.SolidInstruments.Messaging/IMessageProcessingInformation.cs +++ b/src/RapidField.SolidInstruments.Messaging/IMessageProcessingInformation.cs @@ -25,7 +25,7 @@ public Int32 AttemptCount /// Gets an ordered collection of processing attempt results for the associated message, or an empty collection if /// processing has not yet been attempted. /// - public ICollection AttemptResults + public List AttemptResults { get; } diff --git a/src/RapidField.SolidInstruments.Messaging/MessageProcessingInformation.cs b/src/RapidField.SolidInstruments.Messaging/MessageProcessingInformation.cs index 0ee1b49b..c316eddc 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessageProcessingInformation.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessageProcessingInformation.cs @@ -65,7 +65,7 @@ public MessageProcessingInformation(MessageListeningFailurePolicy failurePolicy) /// processing has not yet been attempted. /// [DataMember] - public ICollection AttemptResults + public List AttemptResults { get; set; diff --git a/src/RapidField.SolidInstruments.Messaging/MessagingClientFactory.cs b/src/RapidField.SolidInstruments.Messaging/MessagingClientFactory.cs index 680b8083..6fada12c 100644 --- a/src/RapidField.SolidInstruments.Messaging/MessagingClientFactory.cs +++ b/src/RapidField.SolidInstruments.Messaging/MessagingClientFactory.cs @@ -186,8 +186,8 @@ public String GetSubscriptionName(String receiverIdentifier, IMessagin where TMessage : class { var prefix = SubscriptionNamePrefix.IsNullOrEmpty() ? String.Empty : $"{SubscriptionNamePrefix}{MessagingEntityPath.DelimitingCharacterForPrefix}"; - var suffix = $"{MessagingEntityPath.DelimitingCharacterForLabelToken}{new ZBase32Encoding().GetString(entityPath.RejectIf().IsNull(nameof(entityPath)).GetHashCode().ToByteArray())}"; - return $"{prefix}{receiverIdentifier.RejectIf().IsNullOrEmpty(nameof(receiverIdentifier))}{suffix}"; + var suffix = $"{MessagingEntityPath.DelimitingCharacterForLabelToken}{new ZBase32Encoding().GetString(entityPath.RejectIf().IsNull(nameof(entityPath)).TargetArgument.GetHashCode().ToByteArray())}"; + return $"{prefix}{receiverIdentifier.RejectIf().IsNullOrEmpty(nameof(receiverIdentifier)).TargetArgument}{suffix}"; } /// @@ -270,7 +270,7 @@ public TReceiver GetTopicReceiver(String receiverIdentifier) /// The object is disposed. /// public TReceiver GetTopicReceiver(String receiverIdentifier, IEnumerable pathLabels) - where TMessage : class => GetMessageReceiver(MessagingEntityType.Topic, receiverIdentifier.RejectIf().IsNullOrEmpty(nameof(receiverIdentifier)), pathLabels); + where TMessage : class => GetMessageReceiver(MessagingEntityType.Topic, receiverIdentifier.RejectIf().IsNullOrEmpty(nameof(receiverIdentifier)).TargetArgument, pathLabels); /// /// Gets a shared, managed, implementation-specific message sender for a type-defined topic. diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageLockToken.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageLockToken.cs index c5e744cd..3da7c53d 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageLockToken.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageLockToken.cs @@ -304,11 +304,15 @@ public Byte[] ToByteArray() public sealed override String ToString() => $"{{ {nameof(Identifier)}: {Identifier.ToSerializedString()}, {nameof(ExpirationDateTime)}: {ExpirationDateTime.ToSerializedString()} }}"; /// - /// Represents a transport-issued tracking identifier, or if the implementation does not utilize - /// delivery tags. + /// Gets or sets a transport-issued tracking identifier, or if the implementation does not + /// utilize delivery tags. /// [DataMember] - internal readonly UInt64 DeliveryTag; + internal UInt64 DeliveryTag + { + get; + set; + } /// /// Represents the date and time of expiration for the lock, after which the message will become available for processing. diff --git a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransport.cs b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransport.cs index d08bab0d..b0308efc 100644 --- a/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransport.cs +++ b/src/RapidField.SolidInstruments.Messaging/TransportPrimitives/MessageTransport.cs @@ -356,7 +356,7 @@ public Task CreateSubscriptionAsync(IMessagingEntityPath path, String subscripti { RejectIfDisposed(); - if (TryCreateSubscription(path.RejectIf().IsNull(nameof(path)).TargetArgument, subscriptionName.RejectIf().IsNullOrEmpty(nameof(subscriptionName)))) + if (TryCreateSubscription(path.RejectIf().IsNull(nameof(path)).TargetArgument, subscriptionName.RejectIf().IsNullOrEmpty(nameof(subscriptionName)).TargetArgument)) { return; } @@ -512,7 +512,7 @@ public Task DestroySubscriptionAsync(IMessagingEntityPath path, String subscript { RejectIfDisposed(); - if (TryDestroySubscription(path.RejectIf().IsNull(nameof(path)).TargetArgument, subscriptionName.RejectIf().IsNullOrEmpty(nameof(subscriptionName)))) + if (TryDestroySubscription(path.RejectIf().IsNull(nameof(path)).TargetArgument, subscriptionName.RejectIf().IsNullOrEmpty(nameof(subscriptionName)).TargetArgument)) { return; } diff --git a/src/RapidField.SolidInstruments.ObjectComposition/RapidField.SolidInstruments.ObjectComposition.csproj b/src/RapidField.SolidInstruments.ObjectComposition/RapidField.SolidInstruments.ObjectComposition.csproj index 77bbba0f..37f11988 100644 --- a/src/RapidField.SolidInstruments.ObjectComposition/RapidField.SolidInstruments.ObjectComposition.csproj +++ b/src/RapidField.SolidInstruments.ObjectComposition/RapidField.SolidInstruments.ObjectComposition.csproj @@ -37,7 +37,7 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - + diff --git a/test/RapidField.SolidInstruments.Core.UnitTests/RapidField.SolidInstruments.Core.UnitTests.csproj b/test/RapidField.SolidInstruments.Core.UnitTests/RapidField.SolidInstruments.Core.UnitTests.csproj index b1fe647f..6a514d3f 100644 --- a/test/RapidField.SolidInstruments.Core.UnitTests/RapidField.SolidInstruments.Core.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.Core.UnitTests/RapidField.SolidInstruments.Core.UnitTests.csproj @@ -19,7 +19,7 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - + diff --git a/test/RapidField.SolidInstruments.InversionOfControl.Autofac.UnitTests/RapidField.SolidInstruments.InversionOfControl.Autofac.UnitTests.csproj b/test/RapidField.SolidInstruments.InversionOfControl.Autofac.UnitTests/RapidField.SolidInstruments.InversionOfControl.Autofac.UnitTests.csproj index ec57bf25..17247686 100644 --- a/test/RapidField.SolidInstruments.InversionOfControl.Autofac.UnitTests/RapidField.SolidInstruments.InversionOfControl.Autofac.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.InversionOfControl.Autofac.UnitTests/RapidField.SolidInstruments.InversionOfControl.Autofac.UnitTests.csproj @@ -19,7 +19,7 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - + diff --git a/test/RapidField.SolidInstruments.InversionOfControl.DotNetNative.UnitTests/RapidField.SolidInstruments.InversionOfControl.DotNetNative.UnitTests.csproj b/test/RapidField.SolidInstruments.InversionOfControl.DotNetNative.UnitTests/RapidField.SolidInstruments.InversionOfControl.DotNetNative.UnitTests.csproj index 2619de6d..b737a38b 100644 --- a/test/RapidField.SolidInstruments.InversionOfControl.DotNetNative.UnitTests/RapidField.SolidInstruments.InversionOfControl.DotNetNative.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.InversionOfControl.DotNetNative.UnitTests/RapidField.SolidInstruments.InversionOfControl.DotNetNative.UnitTests.csproj @@ -19,7 +19,7 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - + diff --git a/test/RapidField.SolidInstruments.InversionOfControl.UnitTests/RapidField.SolidInstruments.InversionOfControl.UnitTests.csproj b/test/RapidField.SolidInstruments.InversionOfControl.UnitTests/RapidField.SolidInstruments.InversionOfControl.UnitTests.csproj index c06211d7..c34c943c 100644 --- a/test/RapidField.SolidInstruments.InversionOfControl.UnitTests/RapidField.SolidInstruments.InversionOfControl.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.InversionOfControl.UnitTests/RapidField.SolidInstruments.InversionOfControl.UnitTests.csproj @@ -19,7 +19,7 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - + diff --git a/test/RapidField.SolidInstruments.ObjectComposition.UnitTests/RapidField.SolidInstruments.ObjectComposition.UnitTests.csproj b/test/RapidField.SolidInstruments.ObjectComposition.UnitTests/RapidField.SolidInstruments.ObjectComposition.UnitTests.csproj index 479957ee..eb1475ae 100644 --- a/test/RapidField.SolidInstruments.ObjectComposition.UnitTests/RapidField.SolidInstruments.ObjectComposition.UnitTests.csproj +++ b/test/RapidField.SolidInstruments.ObjectComposition.UnitTests/RapidField.SolidInstruments.ObjectComposition.UnitTests.csproj @@ -19,7 +19,7 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in - + From 246c0dd01f4948ed503feaf860b8a32a0e911d66 Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Sat, 25 Jul 2020 13:13:32 -0500 Subject: [PATCH 50/55] #0007 Remove an unnecessary comment. --- .../appsettings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/RapidField.SolidInstruments.Example.ServiceApplication/appsettings.json b/example/RapidField.SolidInstruments.Example.ServiceApplication/appsettings.json index 0327bb4d..29e165eb 100644 --- a/example/RapidField.SolidInstruments.Example.ServiceApplication/appsettings.json +++ b/example/RapidField.SolidInstruments.Example.ServiceApplication/appsettings.json @@ -4,7 +4,7 @@ { "ConnectionStrings": { - "SolidInstrumentsServiceBusDev": "amqp://guest:guest@localhost:5672" // IMPORTANT Intentionally blank. Replace when testing. Do not commit. + "SolidInstrumentsServiceBusDev": "amqp://guest:guest@localhost:5672" }, "Logging": { "IncludeScopes": false, From 61190df0ccd29461429572e93de379868571d5a9 Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Sat, 25 Jul 2020 13:36:58 -0500 Subject: [PATCH 51/55] #7 Update roadmap. --- ROADMAP.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 0beb1167..6456532a 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -9,15 +9,8 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in The **Solid Instruments** product backlog is fluid and persistently subject to change. The team, however, makes an effort to maintain a projected two-year roadmap, which is made available here. -## 2019 - -[**Quarter 4**](https://github.com/RapidField/solid-instruments/issues?utf8=%E2%9C%93&q=is:issue+label:WindowForDelivery-2019-Q4) - ## 2020 -[**Quarter 1**](https://github.com/RapidField/solid-instruments/issues?utf8=%E2%9C%93&q=is:issue+label:WindowForDelivery-2020-Q1) | -[**Quarter 2**](https://github.com/RapidField/solid-instruments/issues?utf8=%E2%9C%93&q=is:issue+label:WindowForDelivery-2020-Q2) | -[**Quarter 3**](https://github.com/RapidField/solid-instruments/issues?utf8=%E2%9C%93&q=is:issue+label:WindowForDelivery-2020-Q3) | [**Quarter 4**](https://github.com/RapidField/solid-instruments/issues?utf8=%E2%9C%93&q=is:issue+label:WindowForDelivery-2020-Q4) ## 2021 From 8c9f1a17eeac7f203b1729bda65c0d82d744f46f Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Sun, 26 Jul 2020 01:45:17 -0500 Subject: [PATCH 52/55] #7 Add module types. --- RapidField.SolidInstruments.sln | 32 ++++++++- cicd/modules/BuildAndDeployment.psm1 | 8 +-- ...dInstruments.Command.Autofac.Extensions.md | 31 +++++++++ ...dField.SolidInstruments.Command.Autofac.md | 39 +++++++++++ ...ruments.Command.DotNetNative.Extensions.md | 31 +++++++++ ...d.SolidInstruments.Command.DotNetNative.md | 39 +++++++++++ .../RapidField.SolidInstruments.Command.md | 16 ++++- ...RapidField.SolidInstruments.Core.Domain.md | 31 +++++++++ ...raphy.Asymmetric.DigitalSignature.Ecdsa.md | 31 +++++++++ ...ryptography.Asymmetric.DigitalSignature.md | 39 +++++++++++ ...ryptography.Asymmetric.KeyExchange.Ecdh.md | 31 +++++++++ ...nts.Cryptography.Asymmetric.KeyExchange.md | 39 +++++++++++ ...olidInstruments.Cryptography.Asymmetric.md | 45 ++++++++++++ ...d.SolidInstruments.Cryptography.Secrets.md | 31 +++++++++ ...apidField.SolidInstruments.Cryptography.md | 12 ++++ ...struments.DataAccess.Autofac.Extensions.md | 31 +++++++++ ...eld.SolidInstruments.DataAccess.Autofac.md | 39 +++++++++++ ...ents.DataAccess.DotNetNative.Extensions.md | 31 +++++++++ ...olidInstruments.DataAccess.DotNetNative.md | 39 +++++++++++ .../RapidField.SolidInstruments.DataAccess.md | 22 +++++- ...ments.EventAuthoring.Autofac.Extensions.md | 31 +++++++++ ...SolidInstruments.EventAuthoring.Autofac.md | 39 +++++++++++ ....EventAuthoring.DotNetNative.Extensions.md | 31 +++++++++ ...Instruments.EventAuthoring.DotNetNative.md | 39 +++++++++++ ...idField.SolidInstruments.EventAuthoring.md | 12 ++++ ...s.InversionOfControl.Autofac.Extensions.md | 2 +- ...dInstruments.InversionOfControl.Autofac.md | 4 +- ...ersionOfControl.DotNetNative.Extensions.md | 2 +- ...ruments.InversionOfControl.DotNetNative.md | 2 +- ...eld.SolidInstruments.InversionOfControl.md | 12 ++++ ...uments.Messaging.Autofac.Asb.Extensions.md | 31 +++++++++ ....SolidInstruments.Messaging.Autofac.Asb.md | 39 +++++++++++ ...uments.Messaging.Autofac.Rmq.Extensions.md | 31 +++++++++ ....SolidInstruments.Messaging.Autofac.Rmq.md | 39 +++++++++++ ...ield.SolidInstruments.Messaging.Autofac.md | 51 ++++++++++++++ ...idInstruments.Messaging.CommandMessages.md | 31 +++++++++ ...s.Messaging.DotNetNative.Asb.Extensions.md | 31 +++++++++ ...dInstruments.Messaging.DotNetNative.Asb.md | 39 +++++++++++ ...s.Messaging.DotNetNative.Rmq.Extensions.md | 31 +++++++++ ...dInstruments.Messaging.DotNetNative.Rmq.md | 39 +++++++++++ ...SolidInstruments.Messaging.DotNetNative.md | 51 ++++++++++++++ .../RapidField.SolidInstruments.Messaging.md | 18 +++++ en-US_User.dic | 1 + .../ExampleContext.cs | 8 +-- .../ExampleDomainDependencyModule.cs | 6 +- .../ApplicationDependencyPackage.cs | 3 +- .../appsettings.json | 3 +- .../Startup.cs | 10 ++- .../AutofacCommandHandlerModule.cs | 32 +++++++++ .../DotNetNativeCommandHandlerModule.cs | 22 ++---- .../ICommandHandlerModule.cs | 26 +++++++ .../AutofacDataAccessCommandHandlerModule.cs | 32 +++++++++ ...NetNativeDataAccessCommandHandlerModule.cs | 32 +++++++++ .../ConfiguredContext.cs | 12 ++-- .../IDataAccessCommandHandlerModule.cs | 26 +++++++ .../AutofacEventHandlerModule.cs | 32 +++++++++ .../DotNetNativeEventHandlerModule.cs | 32 +++++++++ .../IEventHandlerModule.cs | 26 +++++++ .../IDependencyModule.cs | 9 ++- .../AutofacAzureServiceBusModule.cs | 61 +++++++++++++++++ .../AutofacRabbitMqModule.cs | 61 +++++++++++++++++ .../AutofacMessageHandlerModule.cs | 32 +++++++++ .../AutofacMessageListenerModule.cs | 31 +++++++++ .../AutofacMessageTransmitterModule.cs | 31 +++++++++ .../AutofacTransportDependencyModule.cs | 68 +++++++++++++++++++ .../DotNetNativeAzureServiceBusModule.cs | 61 +++++++++++++++++ .../DotNetNativeRabbitMqModule.cs | 61 +++++++++++++++++ .../DotNetNativeMessageHandlerModule.cs | 32 +++++++++ .../DotNetNativeMessageListenerModule.cs | 31 +++++++++ .../DotNetNativeMessageTransmitterModule.cs | 31 +++++++++ .../DotNetNativeTransportDependencyModule.cs | 68 +++++++++++++++++++ .../IMessageHandlerModule.cs | 26 +++++++ .../IMessageListenerModule.cs | 24 +++++++ .../IMessageTransmitterModule.cs | 24 +++++++ .../ITransportDependencyModule.cs | 34 ++++++++++ 75 files changed, 2161 insertions(+), 49 deletions(-) create mode 100644 doc/namespaces/RapidField.SolidInstruments.Command.Autofac.Extensions.md create mode 100644 doc/namespaces/RapidField.SolidInstruments.Command.Autofac.md create mode 100644 doc/namespaces/RapidField.SolidInstruments.Command.DotNetNative.Extensions.md create mode 100644 doc/namespaces/RapidField.SolidInstruments.Command.DotNetNative.md create mode 100644 doc/namespaces/RapidField.SolidInstruments.Core.Domain.md create mode 100644 doc/namespaces/RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature.Ecdsa.md create mode 100644 doc/namespaces/RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature.md create mode 100644 doc/namespaces/RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange.Ecdh.md create mode 100644 doc/namespaces/RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange.md create mode 100644 doc/namespaces/RapidField.SolidInstruments.Cryptography.Asymmetric.md create mode 100644 doc/namespaces/RapidField.SolidInstruments.Cryptography.Secrets.md create mode 100644 doc/namespaces/RapidField.SolidInstruments.DataAccess.Autofac.Extensions.md create mode 100644 doc/namespaces/RapidField.SolidInstruments.DataAccess.Autofac.md create mode 100644 doc/namespaces/RapidField.SolidInstruments.DataAccess.DotNetNative.Extensions.md create mode 100644 doc/namespaces/RapidField.SolidInstruments.DataAccess.DotNetNative.md create mode 100644 doc/namespaces/RapidField.SolidInstruments.EventAuthoring.Autofac.Extensions.md create mode 100644 doc/namespaces/RapidField.SolidInstruments.EventAuthoring.Autofac.md create mode 100644 doc/namespaces/RapidField.SolidInstruments.EventAuthoring.DotNetNative.Extensions.md create mode 100644 doc/namespaces/RapidField.SolidInstruments.EventAuthoring.DotNetNative.md create mode 100644 doc/namespaces/RapidField.SolidInstruments.Messaging.Autofac.Asb.Extensions.md create mode 100644 doc/namespaces/RapidField.SolidInstruments.Messaging.Autofac.Asb.md create mode 100644 doc/namespaces/RapidField.SolidInstruments.Messaging.Autofac.Rmq.Extensions.md create mode 100644 doc/namespaces/RapidField.SolidInstruments.Messaging.Autofac.Rmq.md create mode 100644 doc/namespaces/RapidField.SolidInstruments.Messaging.Autofac.md create mode 100644 doc/namespaces/RapidField.SolidInstruments.Messaging.CommandMessages.md create mode 100644 doc/namespaces/RapidField.SolidInstruments.Messaging.DotNetNative.Asb.Extensions.md create mode 100644 doc/namespaces/RapidField.SolidInstruments.Messaging.DotNetNative.Asb.md create mode 100644 doc/namespaces/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq.Extensions.md create mode 100644 doc/namespaces/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq.md create mode 100644 doc/namespaces/RapidField.SolidInstruments.Messaging.DotNetNative.md create mode 100644 src/RapidField.SolidInstruments.Command.Autofac/AutofacCommandHandlerModule.cs rename example/RapidField.SolidInstruments.Example.ServiceApplication/ApplicationDependencyModule.cs => src/RapidField.SolidInstruments.Command.DotNetNative/DotNetNativeCommandHandlerModule.cs (50%) create mode 100644 src/RapidField.SolidInstruments.Command/ICommandHandlerModule.cs create mode 100644 src/RapidField.SolidInstruments.DataAccess.Autofac/AutofacDataAccessCommandHandlerModule.cs create mode 100644 src/RapidField.SolidInstruments.DataAccess.DotNetNative/DotNetNativeDataAccessCommandHandlerModule.cs create mode 100644 src/RapidField.SolidInstruments.DataAccess/IDataAccessCommandHandlerModule.cs create mode 100644 src/RapidField.SolidInstruments.EventAuthoring.Autofac/AutofacEventHandlerModule.cs create mode 100644 src/RapidField.SolidInstruments.EventAuthoring.DotNetNative/DotNetNativeEventHandlerModule.cs create mode 100644 src/RapidField.SolidInstruments.EventAuthoring/IEventHandlerModule.cs create mode 100644 src/RapidField.SolidInstruments.Messaging.Autofac.Asb/AutofacAzureServiceBusModule.cs create mode 100644 src/RapidField.SolidInstruments.Messaging.Autofac.Rmq/AutofacRabbitMqModule.cs create mode 100644 src/RapidField.SolidInstruments.Messaging.Autofac/AutofacMessageHandlerModule.cs create mode 100644 src/RapidField.SolidInstruments.Messaging.Autofac/AutofacMessageListenerModule.cs create mode 100644 src/RapidField.SolidInstruments.Messaging.Autofac/AutofacMessageTransmitterModule.cs create mode 100644 src/RapidField.SolidInstruments.Messaging.Autofac/AutofacTransportDependencyModule.cs create mode 100644 src/RapidField.SolidInstruments.Messaging.DotNetNative.Asb/DotNetNativeAzureServiceBusModule.cs create mode 100644 src/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq/DotNetNativeRabbitMqModule.cs create mode 100644 src/RapidField.SolidInstruments.Messaging.DotNetNative/DotNetNativeMessageHandlerModule.cs create mode 100644 src/RapidField.SolidInstruments.Messaging.DotNetNative/DotNetNativeMessageListenerModule.cs create mode 100644 src/RapidField.SolidInstruments.Messaging.DotNetNative/DotNetNativeMessageTransmitterModule.cs create mode 100644 src/RapidField.SolidInstruments.Messaging.DotNetNative/DotNetNativeTransportDependencyModule.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/IMessageHandlerModule.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/IMessageListenerModule.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/IMessageTransmitterModule.cs create mode 100644 src/RapidField.SolidInstruments.Messaging/ITransportDependencyModule.cs diff --git a/RapidField.SolidInstruments.sln b/RapidField.SolidInstruments.sln index 8b2202be..da04f7c8 100644 --- a/RapidField.SolidInstruments.sln +++ b/RapidField.SolidInstruments.sln @@ -195,17 +195,36 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "namespaces", "namespaces", ProjectSection(SolutionItems) = preProject doc\namespaces\RapidField.SolidInstruments.Collections.Extensions.md = doc\namespaces\RapidField.SolidInstruments.Collections.Extensions.md doc\namespaces\RapidField.SolidInstruments.Collections.md = doc\namespaces\RapidField.SolidInstruments.Collections.md + doc\namespaces\RapidField.SolidInstruments.Command.Autofac.Extensions.md = doc\namespaces\RapidField.SolidInstruments.Command.Autofac.Extensions.md + doc\namespaces\RapidField.SolidInstruments.Command.Autofac.md = doc\namespaces\RapidField.SolidInstruments.Command.Autofac.md + doc\namespaces\RapidField.SolidInstruments.Command.DotNetNative.Extensions.md = doc\namespaces\RapidField.SolidInstruments.Command.DotNetNative.Extensions.md + doc\namespaces\RapidField.SolidInstruments.Command.DotNetNative.md = doc\namespaces\RapidField.SolidInstruments.Command.DotNetNative.md doc\namespaces\RapidField.SolidInstruments.Command.md = doc\namespaces\RapidField.SolidInstruments.Command.md doc\namespaces\RapidField.SolidInstruments.Core.ArgumentValidation.md = doc\namespaces\RapidField.SolidInstruments.Core.ArgumentValidation.md doc\namespaces\RapidField.SolidInstruments.Core.Concurrency.md = doc\namespaces\RapidField.SolidInstruments.Core.Concurrency.md + doc\namespaces\RapidField.SolidInstruments.Core.Domain.md = doc\namespaces\RapidField.SolidInstruments.Core.Domain.md doc\namespaces\RapidField.SolidInstruments.Core.Extensions.md = doc\namespaces\RapidField.SolidInstruments.Core.Extensions.md doc\namespaces\RapidField.SolidInstruments.Core.md = doc\namespaces\RapidField.SolidInstruments.Core.md + doc\namespaces\RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature.Ecdsa.md = doc\namespaces\RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature.Ecdsa.md + doc\namespaces\RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature.md = doc\namespaces\RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature.md + doc\namespaces\RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange.Ecdh.md = doc\namespaces\RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange.Ecdh.md + doc\namespaces\RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange.md = doc\namespaces\RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange.md + doc\namespaces\RapidField.SolidInstruments.Cryptography.Asymmetric.md = doc\namespaces\RapidField.SolidInstruments.Cryptography.Asymmetric.md doc\namespaces\RapidField.SolidInstruments.Cryptography.Extensions.md = doc\namespaces\RapidField.SolidInstruments.Cryptography.Extensions.md doc\namespaces\RapidField.SolidInstruments.Cryptography.Hashing.md = doc\namespaces\RapidField.SolidInstruments.Cryptography.Hashing.md doc\namespaces\RapidField.SolidInstruments.Cryptography.md = doc\namespaces\RapidField.SolidInstruments.Cryptography.md + doc\namespaces\RapidField.SolidInstruments.Cryptography.Secrets.md = doc\namespaces\RapidField.SolidInstruments.Cryptography.Secrets.md doc\namespaces\RapidField.SolidInstruments.Cryptography.Symmetric.md = doc\namespaces\RapidField.SolidInstruments.Cryptography.Symmetric.md + doc\namespaces\RapidField.SolidInstruments.DataAccess.Autofac.Extensions.md = doc\namespaces\RapidField.SolidInstruments.DataAccess.Autofac.Extensions.md + doc\namespaces\RapidField.SolidInstruments.DataAccess.Autofac.md = doc\namespaces\RapidField.SolidInstruments.DataAccess.Autofac.md + doc\namespaces\RapidField.SolidInstruments.DataAccess.DotNetNative.Extensions.md = doc\namespaces\RapidField.SolidInstruments.DataAccess.DotNetNative.Extensions.md + doc\namespaces\RapidField.SolidInstruments.DataAccess.DotNetNative.md = doc\namespaces\RapidField.SolidInstruments.DataAccess.DotNetNative.md doc\namespaces\RapidField.SolidInstruments.DataAccess.EntityFramework.md = doc\namespaces\RapidField.SolidInstruments.DataAccess.EntityFramework.md doc\namespaces\RapidField.SolidInstruments.DataAccess.md = doc\namespaces\RapidField.SolidInstruments.DataAccess.md + doc\namespaces\RapidField.SolidInstruments.EventAuthoring.Autofac.Extensions.md = doc\namespaces\RapidField.SolidInstruments.EventAuthoring.Autofac.Extensions.md + doc\namespaces\RapidField.SolidInstruments.EventAuthoring.Autofac.md = doc\namespaces\RapidField.SolidInstruments.EventAuthoring.Autofac.md + doc\namespaces\RapidField.SolidInstruments.EventAuthoring.DotNetNative.Extensions.md = doc\namespaces\RapidField.SolidInstruments.EventAuthoring.DotNetNative.Extensions.md + doc\namespaces\RapidField.SolidInstruments.EventAuthoring.DotNetNative.md = doc\namespaces\RapidField.SolidInstruments.EventAuthoring.DotNetNative.md doc\namespaces\RapidField.SolidInstruments.EventAuthoring.Extensions.md = doc\namespaces\RapidField.SolidInstruments.EventAuthoring.Extensions.md doc\namespaces\RapidField.SolidInstruments.EventAuthoring.md = doc\namespaces\RapidField.SolidInstruments.EventAuthoring.md doc\namespaces\RapidField.SolidInstruments.InversionOfControl.Autofac.Extensions.md = doc\namespaces\RapidField.SolidInstruments.InversionOfControl.Autofac.Extensions.md @@ -220,7 +239,18 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "namespaces", "namespaces", doc\namespaces\RapidField.SolidInstruments.Mathematics.Physics.md = doc\namespaces\RapidField.SolidInstruments.Mathematics.Physics.md doc\namespaces\RapidField.SolidInstruments.Mathematics.Sequences.md = doc\namespaces\RapidField.SolidInstruments.Mathematics.Sequences.md doc\namespaces\RapidField.SolidInstruments.Mathematics.Statistics.md = doc\namespaces\RapidField.SolidInstruments.Mathematics.Statistics.md + doc\namespaces\RapidField.SolidInstruments.Messaging.Autofac.Asb.Extensions.md = doc\namespaces\RapidField.SolidInstruments.Messaging.Autofac.Asb.Extensions.md + doc\namespaces\RapidField.SolidInstruments.Messaging.Autofac.Asb.md = doc\namespaces\RapidField.SolidInstruments.Messaging.Autofac.Asb.md + doc\namespaces\RapidField.SolidInstruments.Messaging.Autofac.md = doc\namespaces\RapidField.SolidInstruments.Messaging.Autofac.md + doc\namespaces\RapidField.SolidInstruments.Messaging.Autofac.Rmq.Extensions.md = doc\namespaces\RapidField.SolidInstruments.Messaging.Autofac.Rmq.Extensions.md + doc\namespaces\RapidField.SolidInstruments.Messaging.Autofac.Rmq.md = doc\namespaces\RapidField.SolidInstruments.Messaging.Autofac.Rmq.md doc\namespaces\RapidField.SolidInstruments.Messaging.AzureServiceBus.md = doc\namespaces\RapidField.SolidInstruments.Messaging.AzureServiceBus.md + doc\namespaces\RapidField.SolidInstruments.Messaging.CommandMessages.md = doc\namespaces\RapidField.SolidInstruments.Messaging.CommandMessages.md + doc\namespaces\RapidField.SolidInstruments.Messaging.DotNetNative.Asb.Extensions.md = doc\namespaces\RapidField.SolidInstruments.Messaging.DotNetNative.Asb.Extensions.md + doc\namespaces\RapidField.SolidInstruments.Messaging.DotNetNative.Asb.md = doc\namespaces\RapidField.SolidInstruments.Messaging.DotNetNative.Asb.md + doc\namespaces\RapidField.SolidInstruments.Messaging.DotNetNative.md = doc\namespaces\RapidField.SolidInstruments.Messaging.DotNetNative.md + doc\namespaces\RapidField.SolidInstruments.Messaging.DotNetNative.Rmq.Extensions.md = doc\namespaces\RapidField.SolidInstruments.Messaging.DotNetNative.Rmq.Extensions.md + doc\namespaces\RapidField.SolidInstruments.Messaging.DotNetNative.Rmq.md = doc\namespaces\RapidField.SolidInstruments.Messaging.DotNetNative.Rmq.md doc\namespaces\RapidField.SolidInstruments.Messaging.EventMessages.md = doc\namespaces\RapidField.SolidInstruments.Messaging.EventMessages.md doc\namespaces\RapidField.SolidInstruments.Messaging.InMemory.md = doc\namespaces\RapidField.SolidInstruments.Messaging.InMemory.md doc\namespaces\RapidField.SolidInstruments.Messaging.md = doc\namespaces\RapidField.SolidInstruments.Messaging.md @@ -365,7 +395,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RapidField.SolidInstruments EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RapidField.SolidInstruments.Messaging.DotNetNative.Asb", "src\RapidField.SolidInstruments.Messaging.DotNetNative.Asb\RapidField.SolidInstruments.Messaging.DotNetNative.Asb.csproj", "{0A130B10-C3DA-48BB-9D2D-8BB1130D299D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RapidField.SolidInstruments.Messaging.DotNetNative.Rmq", "src\RapidField.SolidInstruments.Messaging.DotNetNative.Rmq\RapidField.SolidInstruments.Messaging.DotNetNative.Rmq.csproj", "{8FFB8E4C-5858-4D18-B8DA-D669EED643AB}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RapidField.SolidInstruments.Messaging.DotNetNative.Rmq", "src\RapidField.SolidInstruments.Messaging.DotNetNative.Rmq\RapidField.SolidInstruments.Messaging.DotNetNative.Rmq.csproj", "{8FFB8E4C-5858-4D18-B8DA-D669EED643AB}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/cicd/modules/BuildAndDeployment.psm1 b/cicd/modules/BuildAndDeployment.psm1 index c0486121..b8f0465f 100644 --- a/cicd/modules/BuildAndDeployment.psm1 +++ b/cicd/modules/BuildAndDeployment.psm1 @@ -117,7 +117,7 @@ Function Build $BuildVersionWithoutMetadata = GetBuildVersion; ComposeStart "Building $FilePathForSolutionFile using $SolutionConfiguration configuration."; ComposeNormal "Build version: $BuildVersionWithoutMetadata"; - ExecuteProcess -Path "$CommandNameForDotNetCli" -Arguments "build $FilePathForSolutionFile --configuration $SolutionConfiguration --no-restore --verbosity minimal /p:BuildVersion=$BuildVersionWithoutMetadata"; + ExecuteProcess -Path "$CommandNameForDotNetCli" -Arguments "build $FilePathForSolutionFile --configuration $SolutionConfiguration --nologo --no-restore --verbosity minimal /p:BuildVersion=$BuildVersionWithoutMetadata"; $BuildArtifactsDirectoryPath = Join-Path -Path "$DirectoryPathForArtifacts" -ChildPath "$SolutionConfiguration"; If (-not (Test-Path "$BuildArtifactsDirectoryPath")) @@ -219,7 +219,7 @@ Function Clean ) ComposeStart "Cleaning $FilePathForSolutionFile using $SolutionConfiguration configuration."; - ExecuteProcess -Path "$CommandNameForDotNetCli" -Arguments "clean $FilePathForSolutionFile --configuration $SolutionConfiguration --verbosity minimal"; + ExecuteProcess -Path "$CommandNameForDotNetCli" -Arguments "clean $FilePathForSolutionFile --nologo --configuration $SolutionConfiguration --verbosity minimal"; ComposeStart "Destroying build artifacts."; Get-ChildItem -Path "$DirectoryPathForSource" -Directory | ForEach-Object ` @@ -578,7 +578,7 @@ Function StartExampleWebApplication ComposeStart "Starting the example web application using $SolutionConfiguration configuration."; $ProjectFilePath = Join-Path -Path "$DirectoryPathForExample" -ChildPath "$ExampleWebApplicationNamespace\$ExampleWebApplicationNamespace.csproj"; ComposeNormal "Using project path: $ProjectFilePath"; - Start-Process -ArgumentList "run --project ""$ProjectFilePath"" --configuration $SolutionConfiguration" -FilePath "dotnet" -WindowStyle Minimized; + Start-Process -ArgumentList "run --nologo --project ""$ProjectFilePath"" --configuration $SolutionConfiguration" -FilePath "dotnet" -WindowStyle Minimized; ComposeFinish "Finished starting the application."; } @@ -627,7 +627,7 @@ Function Test { $TestDirectoryPath = $_.FullName; ComposeStart "Running tests for $TestDirectoryPath using $SolutionConfiguration configuration."; - OpenCover.Console.exe -excludebyattribute:*.Debugger* -log:Error -mergeoutput -oldstyle -output:"$FilePathForCoverageReport" -register:user -skipautoprops -target:"dotnet.exe" -targetargs:"test $TestDirectoryPath --configuration $SolutionConfiguration --no-build --no-restore --verbosity minimal"; + OpenCover.Console.exe -excludebyattribute:*.Debugger* -log:Error -mergeoutput -oldstyle -output:"$FilePathForCoverageReport" -register:user -skipautoprops -target:"dotnet.exe" -targetargs:"test $TestDirectoryPath --configuration $SolutionConfiguration --no-build --nologo --no-restore --verbosity minimal"; If ($LASTEXITCODE -ne 0) { diff --git a/doc/namespaces/RapidField.SolidInstruments.Command.Autofac.Extensions.md b/doc/namespaces/RapidField.SolidInstruments.Command.Autofac.Extensions.md new file mode 100644 index 00000000..40d55792 --- /dev/null +++ b/doc/namespaces/RapidField.SolidInstruments.Command.Autofac.Extensions.md @@ -0,0 +1,31 @@ +--- +uid: RapidField.SolidInstruments.Command.Autofac.Extensions +summary: *content +--- + + + +Exposes extensions that support the [**Autofac**](https://autofac.org/) integration for the **Solid Instruments** implementations of the command and mediator patterns. + +
+ +![Command label](../images/Label.Command.300w.png) +- - - + +### Installation + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.Command.Autofac +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.Command.Autofac +``` \ No newline at end of file diff --git a/doc/namespaces/RapidField.SolidInstruments.Command.Autofac.md b/doc/namespaces/RapidField.SolidInstruments.Command.Autofac.md new file mode 100644 index 00000000..a3832107 --- /dev/null +++ b/doc/namespaces/RapidField.SolidInstruments.Command.Autofac.md @@ -0,0 +1,39 @@ +--- +uid: RapidField.SolidInstruments.Command.Autofac +summary: *content +--- + + + +Exposes the [**Autofac**](https://autofac.org/) integration for the **Solid Instruments** implementations of the command and mediator patterns. + +
+ +![Command label](../images/Label.Command.300w.png) +- - - + +### Installation + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.Command.Autofac +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.Command.Autofac +``` + +### Namespaces + +#### [RapidField.SolidInstruments.Command.Autofac.Extensions](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Command.Autofac.Extensions.html) + +
+Exposes extensions that support the Autofac integration for the Solid Instruments implementations of the command and mediator patterns. +
\ No newline at end of file diff --git a/doc/namespaces/RapidField.SolidInstruments.Command.DotNetNative.Extensions.md b/doc/namespaces/RapidField.SolidInstruments.Command.DotNetNative.Extensions.md new file mode 100644 index 00000000..1ed9b418 --- /dev/null +++ b/doc/namespaces/RapidField.SolidInstruments.Command.DotNetNative.Extensions.md @@ -0,0 +1,31 @@ +--- +uid: RapidField.SolidInstruments.Command.DotNetNative.Extensions +summary: *content +--- + + + +Exposes extensions that support the native .NET IoC integration for the **Solid Instruments** implementations of the command and mediator patterns. + +
+ +![Command label](../images/Label.Command.300w.png) +- - - + +### Installation + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.Command.DotNetNative +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.Command.DotNetNative +``` \ No newline at end of file diff --git a/doc/namespaces/RapidField.SolidInstruments.Command.DotNetNative.md b/doc/namespaces/RapidField.SolidInstruments.Command.DotNetNative.md new file mode 100644 index 00000000..2191125f --- /dev/null +++ b/doc/namespaces/RapidField.SolidInstruments.Command.DotNetNative.md @@ -0,0 +1,39 @@ +--- +uid: RapidField.SolidInstruments.Command.DotNetNative +summary: *content +--- + + + +Exposes the native .NET IoC integration for the **Solid Instruments** implementations of the command and mediator patterns. + +
+ +![Command label](../images/Label.Command.300w.png) +- - - + +### Installation + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.Command.DotNetNative +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.Command.DotNetNative +``` + +### Namespaces + +#### [RapidField.SolidInstruments.Command.DotNetNative.Extensions](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Command.DotNetNative.Extensions.html) + +
+Exposes extensions that support the native .NET IoC integration for the Solid Instruments implementations of the command and mediator patterns. +
\ No newline at end of file diff --git a/doc/namespaces/RapidField.SolidInstruments.Command.md b/doc/namespaces/RapidField.SolidInstruments.Command.md index 4df49a6a..79aa29af 100644 --- a/doc/namespaces/RapidField.SolidInstruments.Command.md +++ b/doc/namespaces/RapidField.SolidInstruments.Command.md @@ -94,4 +94,18 @@ public class Subtractor } ``` -
\ No newline at end of file +
+ +### Namespaces + +#### [RapidField.SolidInstruments.Command.Autofac](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Command.Autofac.html) + +
+Exposes the Autofac integration for the Solid Instruments implementations of the command and mediator patterns. +
+ +#### [RapidField.SolidInstruments.Command.DotNetNative](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Command.DotNetNative.html) + +
+Exposes the native .NET IoC integration for the Solid Instruments implementations of the command and mediator patterns. +
\ No newline at end of file diff --git a/doc/namespaces/RapidField.SolidInstruments.Core.Domain.md b/doc/namespaces/RapidField.SolidInstruments.Core.Domain.md new file mode 100644 index 00000000..c29c4404 --- /dev/null +++ b/doc/namespaces/RapidField.SolidInstruments.Core.Domain.md @@ -0,0 +1,31 @@ +--- +uid: RapidField.SolidInstruments.Core.Domain +summary: *content +--- + + + +Defines a repeatable pattern for modeling domain constructs. + +
+ +![Core label](../images/Label.Core.300w.png) +- - - + +### Installation + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.Core +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.Core +``` \ No newline at end of file diff --git a/doc/namespaces/RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature.Ecdsa.md b/doc/namespaces/RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature.Ecdsa.md new file mode 100644 index 00000000..cdbdf166 --- /dev/null +++ b/doc/namespaces/RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature.Ecdsa.md @@ -0,0 +1,31 @@ +--- +uid: RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature.Ecdsa +summary: *content +--- + + + +Provides abstractions for elliptic curve digital signature algorithms. + +
+ +![Cryptography label](../images/Label.Cryptography.300w.png) +- - - + +### Installation + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.Cryptography +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.Cryptography +``` \ No newline at end of file diff --git a/doc/namespaces/RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature.md b/doc/namespaces/RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature.md new file mode 100644 index 00000000..101d4e69 --- /dev/null +++ b/doc/namespaces/RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature.md @@ -0,0 +1,39 @@ +--- +uid: RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature +summary: *content +--- + + + +Provides abstractions for asymmetric-key digital signature algorithms. + +
+ +![Cryptography label](../images/Label.Cryptography.300w.png) +- - - + +### Installation + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.Cryptography +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.Cryptography +``` + +### Namespaces + +#### [RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature.Ecdsa](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature.Ecdsa.html) + +
+Provides abstractions for elliptic curve digital signature algorithms. +
\ No newline at end of file diff --git a/doc/namespaces/RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange.Ecdh.md b/doc/namespaces/RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange.Ecdh.md new file mode 100644 index 00000000..ad1fa465 --- /dev/null +++ b/doc/namespaces/RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange.Ecdh.md @@ -0,0 +1,31 @@ +--- +uid: RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange.Ecdh +summary: *content +--- + + + +Provides abstractions for elliptic curve Diffie-Hellman key exchange algorithms. + +
+ +![Cryptography label](../images/Label.Cryptography.300w.png) +- - - + +### Installation + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.Cryptography +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.Cryptography +``` \ No newline at end of file diff --git a/doc/namespaces/RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange.md b/doc/namespaces/RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange.md new file mode 100644 index 00000000..5739546a --- /dev/null +++ b/doc/namespaces/RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange.md @@ -0,0 +1,39 @@ +--- +uid: RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange +summary: *content +--- + + + +Provides abstractions for asymmetric key exchange algorithms. + +
+ +![Cryptography label](../images/Label.Cryptography.300w.png) +- - - + +### Installation + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.Cryptography +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.Cryptography +``` + +### Namespaces + +#### [RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange.Ecdh](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange.Ecdh.html) + +
+Provides abstractions for elliptic curve Diffie-Hellman key exchange algorithms. +
\ No newline at end of file diff --git a/doc/namespaces/RapidField.SolidInstruments.Cryptography.Asymmetric.md b/doc/namespaces/RapidField.SolidInstruments.Cryptography.Asymmetric.md new file mode 100644 index 00000000..f4fcabd3 --- /dev/null +++ b/doc/namespaces/RapidField.SolidInstruments.Cryptography.Asymmetric.md @@ -0,0 +1,45 @@ +--- +uid: RapidField.SolidInstruments.Cryptography.Asymmetric +summary: *content +--- + + + +Provides abstractions for common asymmetric-key encryption algorithms. + +
+ +![Cryptography label](../images/Label.Cryptography.300w.png) +- - - + +### Installation + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.Cryptography +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.Cryptography +``` + +### Namespaces + +#### [RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Cryptography.Asymmetric.DigitalSignature.html) + +
+Provides abstractions for asymmetric-key digital signature algorithms. +
+ +#### [RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Cryptography.Asymmetric.KeyExchange.html) + +
+Provides abstractions for asymmetric key exchange algorithms. +
\ No newline at end of file diff --git a/doc/namespaces/RapidField.SolidInstruments.Cryptography.Secrets.md b/doc/namespaces/RapidField.SolidInstruments.Cryptography.Secrets.md new file mode 100644 index 00000000..1389f1e0 --- /dev/null +++ b/doc/namespaces/RapidField.SolidInstruments.Cryptography.Secrets.md @@ -0,0 +1,31 @@ +--- +uid: RapidField.SolidInstruments.Cryptography.Secrets +summary: *content +--- + + + +Provides abstractions for securely managing secrets. + +
+ +![Cryptography label](../images/Label.Cryptography.300w.png) +- - - + +### Installation + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.Cryptography +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.Cryptography +``` \ No newline at end of file diff --git a/doc/namespaces/RapidField.SolidInstruments.Cryptography.md b/doc/namespaces/RapidField.SolidInstruments.Cryptography.md index d191e2eb..79afe2d6 100644 --- a/doc/namespaces/RapidField.SolidInstruments.Cryptography.md +++ b/doc/namespaces/RapidField.SolidInstruments.Cryptography.md @@ -55,6 +55,12 @@ using (var secureMemory = new SecureMemory(1024)) ### Namespaces +#### [RapidField.SolidInstruments.Cryptography.Asymmetric](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Cryptography.Asymmetric.html) + +
+Provides abstractions for common asymmetric-key encryption algorithms. +
+ #### [RapidField.SolidInstruments.Cryptography.Extensions](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Cryptography.Extensions.html)
@@ -67,6 +73,12 @@ Exposes extensions that support advanced security mechanics. Provides abstractions for common hashing functions.
+#### [RapidField.SolidInstruments.Cryptography.Secrets](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Cryptography.Secrets.html) + +
+Provides abstractions for securely managing secrets. +
+ #### [RapidField.SolidInstruments.Cryptography.Symmetric](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Cryptography.Symmetric.html)
diff --git a/doc/namespaces/RapidField.SolidInstruments.DataAccess.Autofac.Extensions.md b/doc/namespaces/RapidField.SolidInstruments.DataAccess.Autofac.Extensions.md new file mode 100644 index 00000000..352d801d --- /dev/null +++ b/doc/namespaces/RapidField.SolidInstruments.DataAccess.Autofac.Extensions.md @@ -0,0 +1,31 @@ +--- +uid: RapidField.SolidInstruments.DataAccess.Autofac.Extensions +summary: *content +--- + + + +Exposes extensions that support the [**Autofac**](https://autofac.org/) integration for the **Solid Instruments** data access abstractions. + +
+ +![Data Access label](../images/Label.DataAccess.300w.png) +- - - + +### Installation + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.DataAccess.Autofac +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.DataAccess.Autofac +``` \ No newline at end of file diff --git a/doc/namespaces/RapidField.SolidInstruments.DataAccess.Autofac.md b/doc/namespaces/RapidField.SolidInstruments.DataAccess.Autofac.md new file mode 100644 index 00000000..448a6b9b --- /dev/null +++ b/doc/namespaces/RapidField.SolidInstruments.DataAccess.Autofac.md @@ -0,0 +1,39 @@ +--- +uid: RapidField.SolidInstruments.DataAccess.Autofac +summary: *content +--- + + + +Exposes the [**Autofac**](https://autofac.org/) integration for the **Solid Instruments** data access abstractions. + +
+ +![Data Access label](../images/Label.DataAccess.300w.png) +- - - + +### Installation + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.DataAccess.Autofac +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.DataAccess.Autofac +``` + +### Namespaces + +#### [RapidField.SolidInstruments.DataAccess.Autofac.Extensions](https://www.solidinstruments.com/api/RapidField.SolidInstruments.DataAccess.Autofac.Extensions.html) + +
+Exposes extensions that support the Autofac integration for the Solid Instruments data access abstractions. +
\ No newline at end of file diff --git a/doc/namespaces/RapidField.SolidInstruments.DataAccess.DotNetNative.Extensions.md b/doc/namespaces/RapidField.SolidInstruments.DataAccess.DotNetNative.Extensions.md new file mode 100644 index 00000000..4482b542 --- /dev/null +++ b/doc/namespaces/RapidField.SolidInstruments.DataAccess.DotNetNative.Extensions.md @@ -0,0 +1,31 @@ +--- +uid: RapidField.SolidInstruments.DataAccess.DotNetNative.Extensions +summary: *content +--- + + + +Exposes extensions that support the native .NET IoC integration for the **Solid Instruments** data access abstractions. + +
+ +![Data Access label](../images/Label.DataAccess.300w.png) +- - - + +### Installation + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.DataAccess.DotNetNative +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.DataAccess.DotNetNative +``` \ No newline at end of file diff --git a/doc/namespaces/RapidField.SolidInstruments.DataAccess.DotNetNative.md b/doc/namespaces/RapidField.SolidInstruments.DataAccess.DotNetNative.md new file mode 100644 index 00000000..3cda3ee0 --- /dev/null +++ b/doc/namespaces/RapidField.SolidInstruments.DataAccess.DotNetNative.md @@ -0,0 +1,39 @@ +--- +uid: RapidField.SolidInstruments.DataAccess.DotNetNative +summary: *content +--- + + + +Exposes the native .NET IoC integration for the **Solid Instruments** data access abstractions. + +
+ +![DataAccess label](../images/Label.DataAccess.300w.png) +- - - + +### Installation + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.DataAccess.DotNetNative +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.DataAccess.DotNetNative +``` + +### Namespaces + +#### [RapidField.SolidInstruments.DataAccess.DotNetNative.Extensions](https://www.solidinstruments.com/api/RapidField.SolidInstruments.DataAccess.DotNetNative.Extensions.html) + +
+Exposes extensions that support the native .NET IoC integration for the Solid Instruments data access abstractions. +
\ No newline at end of file diff --git a/doc/namespaces/RapidField.SolidInstruments.DataAccess.md b/doc/namespaces/RapidField.SolidInstruments.DataAccess.md index 29f39003..4e3a31a7 100644 --- a/doc/namespaces/RapidField.SolidInstruments.DataAccess.md +++ b/doc/namespaces/RapidField.SolidInstruments.DataAccess.md @@ -28,4 +28,24 @@ dotnet add package RapidField.SolidInstruments.DataAccess ```shell Install-Package RapidField.SolidInstruments.DataAccess -``` \ No newline at end of file +``` + +### Namespaces + +#### [RapidField.SolidInstruments.DataAccess.Autofac](https://www.solidinstruments.com/api/RapidField.SolidInstruments.DataAccess.Autofac.html) + +
+Exposes the Autofac integration for the Solid Instruments data access abstractions. +
+ +#### [RapidField.SolidInstruments.DataAccess.DotNetNative](https://www.solidinstruments.com/api/RapidField.SolidInstruments.DataAccess.DotNetNative.html) + +
+Exposes the native .NET IoC integration for the Solid Instruments data access abstractions. +
+ +#### [RapidField.SolidInstruments.DataAccess.EntityFramework](https://www.solidinstruments.com/api/RapidField.SolidInstruments.DataAccess.EntityFramework.html) + +
+Provides Entity Framework implementations of the **Solid Instruments** data access abstractions. +
\ No newline at end of file diff --git a/doc/namespaces/RapidField.SolidInstruments.EventAuthoring.Autofac.Extensions.md b/doc/namespaces/RapidField.SolidInstruments.EventAuthoring.Autofac.Extensions.md new file mode 100644 index 00000000..e2c7b6ee --- /dev/null +++ b/doc/namespaces/RapidField.SolidInstruments.EventAuthoring.Autofac.Extensions.md @@ -0,0 +1,31 @@ +--- +uid: RapidField.SolidInstruments.EventAuthoring.Autofac.Extensions +summary: *content +--- + + + +Exposes extensions that support the **Autofac** integration for the **Solid Instruments** event authoring abstractions. + +
+ +![Event Authoring label](../images/Label.EventAuthoring.300w.png) +- - - + +### Installation + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.EventAuthoring.Autofac +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.EventAuthoring.Autofac +``` \ No newline at end of file diff --git a/doc/namespaces/RapidField.SolidInstruments.EventAuthoring.Autofac.md b/doc/namespaces/RapidField.SolidInstruments.EventAuthoring.Autofac.md new file mode 100644 index 00000000..ca3f97a8 --- /dev/null +++ b/doc/namespaces/RapidField.SolidInstruments.EventAuthoring.Autofac.md @@ -0,0 +1,39 @@ +--- +uid: RapidField.SolidInstruments.EventAuthoring.Autofac +summary: *content +--- + + + +Exposes the [**Autofac**](https://autofac.org/) integration for the **Solid Instruments** event authoring abstractions. + +
+ +![Event Authoring label](../images/Label.EventAuthoring.300w.png) +- - - + +### Installation + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.EventAuthoring.Autofac +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.EventAuthoring.Autofac +``` + +### Namespaces + +#### [RapidField.SolidInstruments.EventAuthoring.Autofac.Extensions](https://www.solidinstruments.com/api/RapidField.SolidInstruments.EventAuthoring.Autofac.Extensions.html) + +
+Exposes extensions that support the Autofac integration for the Solid Instruments event authoring abstractions. +
\ No newline at end of file diff --git a/doc/namespaces/RapidField.SolidInstruments.EventAuthoring.DotNetNative.Extensions.md b/doc/namespaces/RapidField.SolidInstruments.EventAuthoring.DotNetNative.Extensions.md new file mode 100644 index 00000000..e6d86732 --- /dev/null +++ b/doc/namespaces/RapidField.SolidInstruments.EventAuthoring.DotNetNative.Extensions.md @@ -0,0 +1,31 @@ +--- +uid: RapidField.SolidInstruments.EventAuthoring.DotNetNative.Extensions +summary: *content +--- + + + +Exposes extensions that support the native .NET IoC integration for the **Solid Instruments** event authoring abstractions. + +
+ +![Event Authoring label](../images/Label.EventAuthoring.300w.png) +- - - + +### Installation + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.EventAuthoring.DotNetNative +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.EventAuthoring.DotNetNative +``` \ No newline at end of file diff --git a/doc/namespaces/RapidField.SolidInstruments.EventAuthoring.DotNetNative.md b/doc/namespaces/RapidField.SolidInstruments.EventAuthoring.DotNetNative.md new file mode 100644 index 00000000..e7317d82 --- /dev/null +++ b/doc/namespaces/RapidField.SolidInstruments.EventAuthoring.DotNetNative.md @@ -0,0 +1,39 @@ +--- +uid: RapidField.SolidInstruments.EventAuthoring.DotNetNative +summary: *content +--- + + + +Exposes the native .NET IoC integration for the **Solid Instruments** event authoring abstractions. + +
+ +![Event Authoring label](../images/Label.EventAuthoring.300w.png) +- - - + +### Installation + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.EventAuthoring.DotNetNative +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.EventAuthoring.DotNetNative +``` + +### Namespaces + +#### [RapidField.SolidInstruments.EventAuthoring.DotNetNative.Extensions](https://www.solidinstruments.com/api/RapidField.SolidInstruments.EventAuthoring.DotNetNative.Extensions.html) + +
+Exposes extensions that support the native .NET IoC integration for the Solid Instruments event authoring abstractions. +
\ No newline at end of file diff --git a/doc/namespaces/RapidField.SolidInstruments.EventAuthoring.md b/doc/namespaces/RapidField.SolidInstruments.EventAuthoring.md index cbabdf5f..39d5006a 100644 --- a/doc/namespaces/RapidField.SolidInstruments.EventAuthoring.md +++ b/doc/namespaces/RapidField.SolidInstruments.EventAuthoring.md @@ -32,6 +32,18 @@ Install-Package RapidField.SolidInstruments.EventAuthoring ### Namespaces +#### [RapidField.SolidInstruments.EventAuthoring.Autofac](https://www.solidinstruments.com/api/RapidField.SolidInstruments.EventAuthoring.Autofac.html) + +
+Exposes the Autofac integration for the Solid Instruments event authoring abstractions. +
+ +#### [RapidField.SolidInstruments.EventAuthoring.DotNetNative](https://www.solidinstruments.com/api/RapidField.SolidInstruments.EventAuthoring.DotNetNative.html) + +
+Exposes the native .NET IoC integration for the Solid Instruments event authoring abstractions. +
+ #### [RapidField.SolidInstruments.EventAuthoring.Extensions](https://www.solidinstruments.com/api/RapidField.SolidInstruments.EventAuthoring.Extensions.html)
diff --git a/doc/namespaces/RapidField.SolidInstruments.InversionOfControl.Autofac.Extensions.md b/doc/namespaces/RapidField.SolidInstruments.InversionOfControl.Autofac.Extensions.md index 344e4bda..33f265a3 100644 --- a/doc/namespaces/RapidField.SolidInstruments.InversionOfControl.Autofac.Extensions.md +++ b/doc/namespaces/RapidField.SolidInstruments.InversionOfControl.Autofac.Extensions.md @@ -7,7 +7,7 @@ summary: *content Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. --> -Exposes extensions that support the **Autofac** implementation of the **Solid Instruments** inversion of control abstraction. +Exposes extensions that support the [**Autofac**](https://autofac.org/) integration for the **Solid Instruments** inversion of control abstractions.
diff --git a/doc/namespaces/RapidField.SolidInstruments.InversionOfControl.Autofac.md b/doc/namespaces/RapidField.SolidInstruments.InversionOfControl.Autofac.md index b402c113..035fb548 100644 --- a/doc/namespaces/RapidField.SolidInstruments.InversionOfControl.Autofac.md +++ b/doc/namespaces/RapidField.SolidInstruments.InversionOfControl.Autofac.md @@ -7,7 +7,7 @@ summary: *content Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. --> -Exposes the Autofac implementation of the **Solid Instruments** inversion of control abstraction. +Exposes the [**Autofac**](https://autofac.org/) implementation of the **Solid Instruments** inversion of control abstractions.
@@ -35,5 +35,5 @@ Install-Package RapidField.SolidInstruments.InversionOfControl.Autofac #### [RapidField.SolidInstruments.InversionOfControl.Autofac.Extensions](https://www.solidinstruments.com/api/RapidField.SolidInstruments.InversionOfControl.Autofac.Extensions.html)
-Exposes extensions that support the Autofac implementation of the Solid Instruments inversion of control abstraction. +Exposes extensions that support the Autofac implementation of the Solid Instruments inversion of control abstractions.
\ No newline at end of file diff --git a/doc/namespaces/RapidField.SolidInstruments.InversionOfControl.DotNetNative.Extensions.md b/doc/namespaces/RapidField.SolidInstruments.InversionOfControl.DotNetNative.Extensions.md index 582c99ed..e63e4d66 100644 --- a/doc/namespaces/RapidField.SolidInstruments.InversionOfControl.DotNetNative.Extensions.md +++ b/doc/namespaces/RapidField.SolidInstruments.InversionOfControl.DotNetNative.Extensions.md @@ -7,7 +7,7 @@ summary: *content Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. --> -Exposes extensions that support the native .NET implementation of the **Solid Instruments** inversion of control abstraction. +Exposes extensions that support the native .NET IoC integration for the **Solid Instruments** inversion of control abstractions.
diff --git a/doc/namespaces/RapidField.SolidInstruments.InversionOfControl.DotNetNative.md b/doc/namespaces/RapidField.SolidInstruments.InversionOfControl.DotNetNative.md index de5cefcd..dac83731 100644 --- a/doc/namespaces/RapidField.SolidInstruments.InversionOfControl.DotNetNative.md +++ b/doc/namespaces/RapidField.SolidInstruments.InversionOfControl.DotNetNative.md @@ -35,5 +35,5 @@ Install-Package RapidField.SolidInstruments.InversionOfControl.DotNetNative #### [RapidField.SolidInstruments.InversionOfControl.DotNetNative.Extensions](https://www.solidinstruments.com/api/RapidField.SolidInstruments.InversionOfControl.DotNetNative.Extensions.html)
-Exposes extensions that support the native .NET implementation of the Solid Instruments inversion of control abstraction. +Exposes extensions that support the native .NET implementation of the Solid Instruments inversion of control abstractions.
\ No newline at end of file diff --git a/doc/namespaces/RapidField.SolidInstruments.InversionOfControl.md b/doc/namespaces/RapidField.SolidInstruments.InversionOfControl.md index 3d19f5cc..edae9842 100644 --- a/doc/namespaces/RapidField.SolidInstruments.InversionOfControl.md +++ b/doc/namespaces/RapidField.SolidInstruments.InversionOfControl.md @@ -32,6 +32,18 @@ Install-Package RapidField.SolidInstruments.InversionOfControl ### Namespaces +#### [RapidField.SolidInstruments.InversionOfControl.Autofac](https://www.solidinstruments.com/api/RapidField.SolidInstruments.InversionOfControl.Autofac.html) + +
+Exposes the Autofac implementation of the Solid Instruments inversion of control abstractions. +
+ +#### [RapidField.SolidInstruments.InversionOfControl.DotNetNative](https://www.solidinstruments.com/api/RapidField.SolidInstruments.InversionOfControl.DotNetNative.html) + +
+Exposes the native .NET implementation of the Solid Instruments inversion of control abstractions. +
+ #### [RapidField.SolidInstruments.InversionOfControl.Extensions](https://www.solidinstruments.com/api/RapidField.SolidInstruments.InversionOfControl.Extensions.html)
diff --git a/doc/namespaces/RapidField.SolidInstruments.Messaging.Autofac.Asb.Extensions.md b/doc/namespaces/RapidField.SolidInstruments.Messaging.Autofac.Asb.Extensions.md new file mode 100644 index 00000000..a23bbca0 --- /dev/null +++ b/doc/namespaces/RapidField.SolidInstruments.Messaging.Autofac.Asb.Extensions.md @@ -0,0 +1,31 @@ +--- +uid: RapidField.SolidInstruments.Messaging.Autofac.Asb.Extensions +summary: *content +--- + + + +Exposes extensions that support the [**Autofac**](https://autofac.org/) integration for the **Solid Instruments** [**Azure Service Bus**](https://docs.microsoft.com/en-us/azure/service-bus-messaging/) messaging abstractions. + +
+ +![Messaging label](../images/Label.Messaging.300w.png) +- - - + +### Installation + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.Messaging.Autofac.Asb +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.Messaging.Autofac.Asb +``` \ No newline at end of file diff --git a/doc/namespaces/RapidField.SolidInstruments.Messaging.Autofac.Asb.md b/doc/namespaces/RapidField.SolidInstruments.Messaging.Autofac.Asb.md new file mode 100644 index 00000000..7a5c7a65 --- /dev/null +++ b/doc/namespaces/RapidField.SolidInstruments.Messaging.Autofac.Asb.md @@ -0,0 +1,39 @@ +--- +uid: RapidField.SolidInstruments.Messaging.Autofac.Asb +summary: *content +--- + + + +Exposes the [**Autofac**](https://autofac.org/) IoC integration for the **Solid Instruments** [**Azure Service Bus**](https://docs.microsoft.com/en-us/azure/service-bus-messaging/) messaging abstractions. + +
+ +![Messaging label](../images/Label.Messaging.300w.png) +- - - + +### Installation + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.Messaging.Autofac.Asb +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.Messaging.Autofac.Asb +``` + +### Namespaces + +#### [RapidField.SolidInstruments.Messaging.Autofac.Asb.Extensions](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Messaging.Autofac.Asb.Extensions.html) + +
+Exposes extensions that support the Autofac IoC integration for the Solid Instruments Azure Service Bus messaging abstractions. +
\ No newline at end of file diff --git a/doc/namespaces/RapidField.SolidInstruments.Messaging.Autofac.Rmq.Extensions.md b/doc/namespaces/RapidField.SolidInstruments.Messaging.Autofac.Rmq.Extensions.md new file mode 100644 index 00000000..300dd476 --- /dev/null +++ b/doc/namespaces/RapidField.SolidInstruments.Messaging.Autofac.Rmq.Extensions.md @@ -0,0 +1,31 @@ +--- +uid: RapidField.SolidInstruments.Messaging.Autofac.Rmq.Extensions +summary: *content +--- + + + +Exposes extensions that support the [**Autofac**](https://autofac.org/) integration for the **Solid Instruments** [**RabbitMQ**](https://www.rabbitmq.com/) messaging abstractions. + +
+ +![Messaging label](../images/Label.Messaging.300w.png) +- - - + +### Installation + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.Messaging.Autofac.Rmq +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.Messaging.Autofac.Rmq +``` \ No newline at end of file diff --git a/doc/namespaces/RapidField.SolidInstruments.Messaging.Autofac.Rmq.md b/doc/namespaces/RapidField.SolidInstruments.Messaging.Autofac.Rmq.md new file mode 100644 index 00000000..f94fff60 --- /dev/null +++ b/doc/namespaces/RapidField.SolidInstruments.Messaging.Autofac.Rmq.md @@ -0,0 +1,39 @@ +--- +uid: RapidField.SolidInstruments.Messaging.Autofac.Rmq +summary: *content +--- + + + +Exposes the [**Autofac**](https://autofac.org/) IoC integration for the **Solid Instruments** [**RabbitMQ**](https://www.rabbitmq.com/) messaging abstractions. + +
+ +![Messaging label](../images/Label.Messaging.300w.png) +- - - + +### Installation + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.Messaging.Autofac.Rmq +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.Messaging.Autofac.Rmq +``` + +### Namespaces + +#### [RapidField.SolidInstruments.Messaging.Autofac.Asb.Extensions](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Messaging.Autofac.Asb.Extensions.html) + +
+Exposes extensions that support the Autofac IoC integration for the Solid Instruments RabbitMQ messaging abstractions. +
\ No newline at end of file diff --git a/doc/namespaces/RapidField.SolidInstruments.Messaging.Autofac.md b/doc/namespaces/RapidField.SolidInstruments.Messaging.Autofac.md new file mode 100644 index 00000000..6b679c85 --- /dev/null +++ b/doc/namespaces/RapidField.SolidInstruments.Messaging.Autofac.md @@ -0,0 +1,51 @@ +--- +uid: RapidField.SolidInstruments.Messaging.Autofac +summary: *content +--- + + + +Exposes the [**Autofac**](https://autofac.org/) integration for the **Solid Instruments** messaging abstractions. + +
+ +![Messaging label](../images/Label.Messaging.300w.png) +- - - + +### Installation + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.Messaging.Autofac +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.Messaging.Autofac +``` + +### Namespaces + +#### [RapidField.SolidInstruments.Messaging.Autofac.Asb](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Messaging.Autofac.Asb.html) + +
+Exposes the Autofac IoC integration for the Solid Instruments Azure Service Bus messaging abstractions. +
+ +#### [RapidField.SolidInstruments.Messaging.Autofac.Extensions](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Messaging.Autofac.Extensions.html) + +
+Exposes extensions that support the Autofac integration for the Solid Instruments messaging abstractions. +
+ +#### [RapidField.SolidInstruments.Messaging.Autofac.Rmq](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Messaging.Autofac.Rmq.html) + +
+Exposes the Autofac IoC integration for the Solid Instruments RabbitMQ messaging abstractions. +
\ No newline at end of file diff --git a/doc/namespaces/RapidField.SolidInstruments.Messaging.CommandMessages.md b/doc/namespaces/RapidField.SolidInstruments.Messaging.CommandMessages.md new file mode 100644 index 00000000..348d8623 --- /dev/null +++ b/doc/namespaces/RapidField.SolidInstruments.Messaging.CommandMessages.md @@ -0,0 +1,31 @@ +--- +uid: RapidField.SolidInstruments.Messaging.CommandMessages +summary: *content +--- + + + +Exposes various command message types. + +
+ +![Messaging label](../images/Label.Messaging.300w.png) +- - - + +### Installation + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.Messaging +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.Messaging +``` \ No newline at end of file diff --git a/doc/namespaces/RapidField.SolidInstruments.Messaging.DotNetNative.Asb.Extensions.md b/doc/namespaces/RapidField.SolidInstruments.Messaging.DotNetNative.Asb.Extensions.md new file mode 100644 index 00000000..cd45b7c0 --- /dev/null +++ b/doc/namespaces/RapidField.SolidInstruments.Messaging.DotNetNative.Asb.Extensions.md @@ -0,0 +1,31 @@ +--- +uid: RapidField.SolidInstruments.Messaging.DotNetNative.Asb.Extensions +summary: *content +--- + + + +Exposes extensions that support the native .NET integration for the **Solid Instruments** [**Azure Service Bus**](https://docs.microsoft.com/en-us/azure/service-bus-messaging/) messaging abstractions. + +
+ +![Messaging label](../images/Label.Messaging.300w.png) +- - - + +### Installation + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.Messaging.DotNetNative.Asb +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.Messaging.DotNetNative.Asb +``` \ No newline at end of file diff --git a/doc/namespaces/RapidField.SolidInstruments.Messaging.DotNetNative.Asb.md b/doc/namespaces/RapidField.SolidInstruments.Messaging.DotNetNative.Asb.md new file mode 100644 index 00000000..54fbbe4d --- /dev/null +++ b/doc/namespaces/RapidField.SolidInstruments.Messaging.DotNetNative.Asb.md @@ -0,0 +1,39 @@ +--- +uid: RapidField.SolidInstruments.Messaging.DotNetNative.Asb +summary: *content +--- + + + +Exposes the native .NET IoC integration for the **Solid Instruments** [**Azure Service Bus**](https://docs.microsoft.com/en-us/azure/service-bus-messaging/) messaging abstractions. + +
+ +![Messaging label](../images/Label.Messaging.300w.png) +- - - + +### Installation + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.Messaging.DotNetNative.Asb +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.Messaging.DotNetNative.Asb +``` + +### Namespaces + +#### [RapidField.SolidInstruments.Messaging.DotNetNative.Asb.Extensions](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Messaging.DotNetNative.Asb.Extensions.html) + +
+Exposes extensions that support the DotNetNative IoC integration for the Solid Instruments Azure Service Bus messaging abstractions. +
\ No newline at end of file diff --git a/doc/namespaces/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq.Extensions.md b/doc/namespaces/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq.Extensions.md new file mode 100644 index 00000000..2bca2316 --- /dev/null +++ b/doc/namespaces/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq.Extensions.md @@ -0,0 +1,31 @@ +--- +uid: RapidField.SolidInstruments.Messaging.DotNetNative.Rmq.Extensions +summary: *content +--- + + + +Exposes extensions that support the native .NET integration for the **Solid Instruments** [**RabbitMQ**](https://www.rabbitmq.com/) messaging abstractions. + +
+ +![Messaging label](../images/Label.Messaging.300w.png) +- - - + +### Installation + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.Messaging.DotNetNative.Rmq +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.Messaging.DotNetNative.Rmq +``` \ No newline at end of file diff --git a/doc/namespaces/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq.md b/doc/namespaces/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq.md new file mode 100644 index 00000000..534d976e --- /dev/null +++ b/doc/namespaces/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq.md @@ -0,0 +1,39 @@ +--- +uid: RapidField.SolidInstruments.Messaging.DotNetNative.Rmq +summary: *content +--- + + + +Exposes the native .NET IoC integration for the **Solid Instruments** [**RabbitMQ**](https://www.rabbitmq.com/) messaging abstractions. + +
+ +![Messaging label](../images/Label.Messaging.300w.png) +- - - + +### Installation + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.Messaging.DotNetNative.Rmq +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.Messaging.DotNetNative.Rmq +``` + +### Namespaces + +#### [RapidField.SolidInstruments.Messaging.DotNetNative.Asb.Extensions](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Messaging.DotNetNative.Asb.Extensions.html) + +
+Exposes extensions that support the DotNetNative IoC integration for the Solid Instruments RabbitMQ messaging abstractions. +
\ No newline at end of file diff --git a/doc/namespaces/RapidField.SolidInstruments.Messaging.DotNetNative.md b/doc/namespaces/RapidField.SolidInstruments.Messaging.DotNetNative.md new file mode 100644 index 00000000..775c9dee --- /dev/null +++ b/doc/namespaces/RapidField.SolidInstruments.Messaging.DotNetNative.md @@ -0,0 +1,51 @@ +--- +uid: RapidField.SolidInstruments.Messaging.DotNetNative +summary: *content +--- + + + +Exposes the native .NET integration for the **Solid Instruments** messaging abstractions. + +
+ +![Messaging label](../images/Label.Messaging.300w.png) +- - - + +### Installation + +This library is available via [**NuGet**](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio). Use one of the commands below to download and install the library and all of its dependencies. + +###### .NET CLI + +```shell +dotnet add package RapidField.SolidInstruments.Messaging.DotNetNative +``` + +###### NuGet Package Manager + +```shell +Install-Package RapidField.SolidInstruments.Messaging.DotNetNative +``` + +### Namespaces + +#### [RapidField.SolidInstruments.Messaging.DotNetNative.Asb](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Messaging.DotNetNative.Asb.html) + +
+Exposes the native .NET IoC integration for the Solid Instruments Azure Service Bus messaging abstractions. +
+ +#### [RapidField.SolidInstruments.Messaging.DotNetNative.Extensions](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Messaging.DotNetNative.Extensions.html) + +
+Exposes extensions that support the native .NET integration for the Solid Instruments messaging abstractions. +
+ +#### [RapidField.SolidInstruments.Messaging.DotNetNative.Rmq](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq.html) + +
+Exposes the native .NET IoC integration for the Solid Instruments RabbitMQ messaging abstractions. +
\ No newline at end of file diff --git a/doc/namespaces/RapidField.SolidInstruments.Messaging.md b/doc/namespaces/RapidField.SolidInstruments.Messaging.md index 9123f8af..93daf899 100644 --- a/doc/namespaces/RapidField.SolidInstruments.Messaging.md +++ b/doc/namespaces/RapidField.SolidInstruments.Messaging.md @@ -32,6 +32,24 @@ Install-Package RapidField.SolidInstruments.Messaging ### Namespaces +#### [RapidField.SolidInstruments.Messaging.Autofac](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Messaging.Autofac.html) + +
+Exposes the Autofac integration for the Solid Instruments messaging abstractions. +
+ +#### [RapidField.SolidInstruments.Messaging.CommandMessages](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Messaging.CommandMessages.html) + +
+Exposes various command message types. +
+ +#### [RapidField.SolidInstruments.Messaging.DotNetNative](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Messaging.DotNetNative.html) + +
+Exposes the native .NET IoC integration for the Solid Instruments messaging abstractions. +
+ #### [RapidField.SolidInstruments.Messaging.EventMessages](https://www.solidinstruments.com/api/RapidField.SolidInstruments.Messaging.EventMessages.html)
diff --git a/en-US_User.dic b/en-US_User.dic index 25f23432..2a643767 100644 --- a/en-US_User.dic +++ b/en-US_User.dic @@ -173,6 +173,7 @@ nm nodejs noexit noindex +nologo norepair npm nuget diff --git a/example/RapidField.SolidInstruments.Example.DatabaseModel/ExampleContext.cs b/example/RapidField.SolidInstruments.Example.DatabaseModel/ExampleContext.cs index 5a439512..8dc2b210 100644 --- a/example/RapidField.SolidInstruments.Example.DatabaseModel/ExampleContext.cs +++ b/example/RapidField.SolidInstruments.Example.DatabaseModel/ExampleContext.cs @@ -38,7 +38,7 @@ public ExampleContext(IConfiguration applicationConfiguration) /// Configuration information for the application. /// /// - /// The database type of the backing database. The default value is . + /// The database type of the backing database. The default value is . /// /// /// is . @@ -59,7 +59,7 @@ public ExampleContext(IConfiguration applicationConfiguration, ContextDatabaseTy /// Configuration information for the application. /// /// - /// The database type of the backing database. The default value is . + /// The database type of the backing database. The default value is . /// /// /// The name of the backing database, which matches the associated connection string key in @@ -89,7 +89,7 @@ public ExampleContext(IConfiguration applicationConfiguration, ContextDatabaseTy /// Configuration information for the application. /// /// - /// The database type of the backing database. The default value is . + /// The database type of the backing database. The default value is . /// /// /// The query result tracking behavior for the context. The default value is . @@ -113,7 +113,7 @@ public ExampleContext(IConfiguration applicationConfiguration, ContextDatabaseTy /// Configuration information for the application. /// /// - /// The database type of the backing database. The default value is . + /// The database type of the backing database. The default value is . /// /// /// The name of the backing database, which matches the associated connection string key in diff --git a/example/RapidField.SolidInstruments.Example.Domain/ExampleDomainDependencyModule.cs b/example/RapidField.SolidInstruments.Example.Domain/ExampleDomainDependencyModule.cs index c8b4e02e..d7446997 100644 --- a/example/RapidField.SolidInstruments.Example.Domain/ExampleDomainDependencyModule.cs +++ b/example/RapidField.SolidInstruments.Example.Domain/ExampleDomainDependencyModule.cs @@ -45,15 +45,13 @@ public ExampleDomainDependencyModule(IConfiguration applicationConfiguration) /// protected override void Configure(ServiceCollection configurator, IConfiguration applicationConfiguration) { - // Register queue listeners. + // Register event message listeners. configurator.AddMessageListener(); configurator.AddMessageListener(); configurator.AddMessageListener(); - - // Register topic listeners. configurator.AddMessageListener(); - // Register request listeners. + // Register request message listeners. configurator.AddRequestMessageListener(); } } diff --git a/example/RapidField.SolidInstruments.Example.ServiceApplication/ApplicationDependencyPackage.cs b/example/RapidField.SolidInstruments.Example.ServiceApplication/ApplicationDependencyPackage.cs index cb5f90fe..31bd5c9b 100644 --- a/example/RapidField.SolidInstruments.Example.ServiceApplication/ApplicationDependencyPackage.cs +++ b/example/RapidField.SolidInstruments.Example.ServiceApplication/ApplicationDependencyPackage.cs @@ -8,6 +8,7 @@ using RapidField.SolidInstruments.Example.Domain; using RapidField.SolidInstruments.InversionOfControl; using RapidField.SolidInstruments.InversionOfControl.DotNetNative; +using RapidField.SolidInstruments.Messaging.DotNetNative.Rmq; using System.Collections.Generic; namespace RapidField.SolidInstruments.Example.ServiceApplication @@ -37,7 +38,7 @@ public ApplicationDependencyPackage() /// protected override IEnumerable> CreateModules(IConfiguration applicationConfiguration) => new IDependencyModule[] { - new ApplicationDependencyModule(applicationConfiguration), + new DotNetNativeRabbitMqModule(applicationConfiguration, "SolidInstrumentsServiceBusDev"), new ExampleContractsDependencyModule(applicationConfiguration), new ExampleDomainDependencyModule(applicationConfiguration) }; diff --git a/example/RapidField.SolidInstruments.Example.ServiceApplication/appsettings.json b/example/RapidField.SolidInstruments.Example.ServiceApplication/appsettings.json index 29e165eb..e7947f5b 100644 --- a/example/RapidField.SolidInstruments.Example.ServiceApplication/appsettings.json +++ b/example/RapidField.SolidInstruments.Example.ServiceApplication/appsettings.json @@ -4,7 +4,8 @@ { "ConnectionStrings": { - "SolidInstrumentsServiceBusDev": "amqp://guest:guest@localhost:5672" + //"SolidInstrumentsServiceBusDev": "amqp://guest:guest@localhost:5672" // Register and connect to a local RabbitMQ instance. + "SolidInstrumentsServiceBusDev": "InMemory" // Register, host and connect to an in-memory service bus. }, "Logging": { "IncludeScopes": false, diff --git a/example/RapidField.SolidInstruments.Example.WebApplication/Startup.cs b/example/RapidField.SolidInstruments.Example.WebApplication/Startup.cs index 6a498f26..ec4f43c8 100644 --- a/example/RapidField.SolidInstruments.Example.WebApplication/Startup.cs +++ b/example/RapidField.SolidInstruments.Example.WebApplication/Startup.cs @@ -49,10 +49,13 @@ public static void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseHsts(); } - app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); - app.UseEndpoints(endpoints => endpoints.MapRazorPages()); + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + endpoints.MapRazorPages(); + }); } /// @@ -63,8 +66,9 @@ public static void Configure(IApplicationBuilder app, IWebHostEnvironment env) /// public IServiceProvider ConfigureServices(IServiceCollection services) { - services.AddMvc().AddControllersAsServices(); services.AddDependencyPackage(Configuration, out var serviceProvider); + services.AddControllers(); + services.AddRazorPages(); return serviceProvider; } diff --git a/src/RapidField.SolidInstruments.Command.Autofac/AutofacCommandHandlerModule.cs b/src/RapidField.SolidInstruments.Command.Autofac/AutofacCommandHandlerModule.cs new file mode 100644 index 00000000..fba9fb13 --- /dev/null +++ b/src/RapidField.SolidInstruments.Command.Autofac/AutofacCommandHandlerModule.cs @@ -0,0 +1,32 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Autofac; +using Microsoft.Extensions.Configuration; +using RapidField.SolidInstruments.InversionOfControl.Autofac; +using System; + +namespace RapidField.SolidInstruments.Command.Autofac +{ + /// + /// Encapsulates Autofac container configuration for command handlers. + /// + public abstract class AutofacCommandHandlerModule : AutofacDependencyModule, ICommandHandlerModule + { + /// + /// Initializes a new instance of the class. + /// + /// + /// Configuration information for the application. + /// + /// + /// is . + /// + protected AutofacCommandHandlerModule(IConfiguration applicationConfiguration) + : base(applicationConfiguration) + { + return; + } + } +} \ No newline at end of file diff --git a/example/RapidField.SolidInstruments.Example.ServiceApplication/ApplicationDependencyModule.cs b/src/RapidField.SolidInstruments.Command.DotNetNative/DotNetNativeCommandHandlerModule.cs similarity index 50% rename from example/RapidField.SolidInstruments.Example.ServiceApplication/ApplicationDependencyModule.cs rename to src/RapidField.SolidInstruments.Command.DotNetNative/DotNetNativeCommandHandlerModule.cs index d9447bf4..6d869a82 100644 --- a/example/RapidField.SolidInstruments.Example.ServiceApplication/ApplicationDependencyModule.cs +++ b/src/RapidField.SolidInstruments.Command.DotNetNative/DotNetNativeCommandHandlerModule.cs @@ -5,18 +5,17 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using RapidField.SolidInstruments.InversionOfControl.DotNetNative; -using RapidField.SolidInstruments.Messaging.DotNetNative.Rmq.Extensions; using System; -namespace RapidField.SolidInstruments.Example.ServiceApplication +namespace RapidField.SolidInstruments.Command.DotNetNative { /// - /// Encapsulates container configuration for application dependencies. + /// Encapsulates native .NET container configuration for command handlers. /// - public class ApplicationDependencyModule : DotNetNativeDependencyModule + public abstract class DotNetNativeCommandHandlerModule : DotNetNativeDependencyModule, ICommandHandlerModule { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// Configuration information for the application. @@ -24,21 +23,10 @@ public class ApplicationDependencyModule : DotNetNativeDependencyModule /// /// is . /// - public ApplicationDependencyModule(IConfiguration applicationConfiguration) + protected DotNetNativeCommandHandlerModule(IConfiguration applicationConfiguration) : base(applicationConfiguration) { return; } - - /// - /// Configures the module. - /// - /// - /// An object that configures containers. - /// - /// - /// Configuration information for the application. - /// - protected override void Configure(ServiceCollection configurator, IConfiguration applicationConfiguration) => configurator.AddSupportingTypesForRabbitMqMessaging(applicationConfiguration, "SolidInstrumentsServiceBusDev"); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Command/ICommandHandlerModule.cs b/src/RapidField.SolidInstruments.Command/ICommandHandlerModule.cs new file mode 100644 index 00000000..0b3d94fe --- /dev/null +++ b/src/RapidField.SolidInstruments.Command/ICommandHandlerModule.cs @@ -0,0 +1,26 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.InversionOfControl; + +namespace RapidField.SolidInstruments.Command +{ + /// + /// Encapsulates container configuration for command handlers. + /// + /// + /// The type of the object that configures containers. + /// + public interface ICommandHandlerModule : ICommandHandlerModule, IDependencyModule + where TConfigurator : class, new() + { + } + + /// + /// Encapsulates container configuration for command handlers. + /// + public interface ICommandHandlerModule : IDependencyModule + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess.Autofac/AutofacDataAccessCommandHandlerModule.cs b/src/RapidField.SolidInstruments.DataAccess.Autofac/AutofacDataAccessCommandHandlerModule.cs new file mode 100644 index 00000000..a1d0ffc3 --- /dev/null +++ b/src/RapidField.SolidInstruments.DataAccess.Autofac/AutofacDataAccessCommandHandlerModule.cs @@ -0,0 +1,32 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Autofac; +using Microsoft.Extensions.Configuration; +using RapidField.SolidInstruments.Command.Autofac; +using System; + +namespace RapidField.SolidInstruments.DataAccess.Autofac +{ + /// + /// Encapsulates Autofac container configuration for data access command handlers. + /// + public abstract class AutofacDataAccessCommandHandlerModule : AutofacCommandHandlerModule, IDataAccessCommandHandlerModule + { + /// + /// Initializes a new instance of the class. + /// + /// + /// Configuration information for the application. + /// + /// + /// is . + /// + protected AutofacDataAccessCommandHandlerModule(IConfiguration applicationConfiguration) + : base(applicationConfiguration) + { + return; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess.DotNetNative/DotNetNativeDataAccessCommandHandlerModule.cs b/src/RapidField.SolidInstruments.DataAccess.DotNetNative/DotNetNativeDataAccessCommandHandlerModule.cs new file mode 100644 index 00000000..c41821c3 --- /dev/null +++ b/src/RapidField.SolidInstruments.DataAccess.DotNetNative/DotNetNativeDataAccessCommandHandlerModule.cs @@ -0,0 +1,32 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using RapidField.SolidInstruments.DataAccess; +using System; + +namespace RapidField.SolidInstruments.Command.DotNetNative +{ + /// + /// Encapsulates native .NET container configuration for data access command handlers. + /// + public abstract class DotNetNativeDataAccessCommandHandlerModule : DotNetNativeCommandHandlerModule, IDataAccessCommandHandlerModule + { + /// + /// Initializes a new instance of the class. + /// + /// + /// Configuration information for the application. + /// + /// + /// is . + /// + protected DotNetNativeDataAccessCommandHandlerModule(IConfiguration applicationConfiguration) + : base(applicationConfiguration) + { + return; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess.EntityFramework/ConfiguredContext.cs b/src/RapidField.SolidInstruments.DataAccess.EntityFramework/ConfiguredContext.cs index 73307213..d0d6d170 100644 --- a/src/RapidField.SolidInstruments.DataAccess.EntityFramework/ConfiguredContext.cs +++ b/src/RapidField.SolidInstruments.DataAccess.EntityFramework/ConfiguredContext.cs @@ -40,7 +40,7 @@ public ConfiguredContext(IConfiguration applicationConfiguration) /// Configuration information for the application. /// /// - /// The database type of the backing database. The default value is . + /// The database type of the backing database. The default value is . /// /// /// is . @@ -61,7 +61,7 @@ public ConfiguredContext(IConfiguration applicationConfiguration, ContextDatabas /// Configuration information for the application. /// /// - /// The database type of the backing database. The default value is . + /// The database type of the backing database. The default value is . /// /// /// The name of the backing database, which matches the associated connection string key in @@ -90,7 +90,7 @@ public ConfiguredContext(IConfiguration applicationConfiguration, ContextDatabas /// Configuration information for the application. /// /// - /// The database type of the backing database. The default value is . + /// The database type of the backing database. The default value is . /// /// /// The query result tracking behavior for the context. The default value is . @@ -102,7 +102,7 @@ public ConfiguredContext(IConfiguration applicationConfiguration, ContextDatabas /// is equal to . /// public ConfiguredContext(IConfiguration applicationConfiguration, ContextDatabaseType databaseType, QueryTrackingBehavior trackingBehavior) - : this(applicationConfiguration, databaseType, UseConventionalDatabaseNameIndicator, DefaultTrackingBehavior) + : this(applicationConfiguration, databaseType, UseConventionalDatabaseNameIndicator, trackingBehavior) { return; } @@ -114,7 +114,7 @@ public ConfiguredContext(IConfiguration applicationConfiguration, ContextDatabas /// Configuration information for the application. /// /// - /// The database type of the backing database. The default value is . + /// The database type of the backing database. The default value is . /// /// /// The name of the backing database, which matches the associated connection string key in @@ -307,7 +307,7 @@ private String DatabaseName /// Represents the default database type for backing databases. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private const ContextDatabaseType DefaultDatabaseType = ContextDatabaseType.SQLServer; + private const ContextDatabaseType DefaultDatabaseType = ContextDatabaseType.InMemory; /// /// Represents the default query result tracking behavior for contexts. diff --git a/src/RapidField.SolidInstruments.DataAccess/IDataAccessCommandHandlerModule.cs b/src/RapidField.SolidInstruments.DataAccess/IDataAccessCommandHandlerModule.cs new file mode 100644 index 00000000..82d55931 --- /dev/null +++ b/src/RapidField.SolidInstruments.DataAccess/IDataAccessCommandHandlerModule.cs @@ -0,0 +1,26 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; + +namespace RapidField.SolidInstruments.DataAccess +{ + /// + /// Encapsulates container configuration for data access command handlers. + /// + /// + /// The type of the object that configures containers. + /// + public interface IDataAccessCommandHandlerModule : IDataAccessCommandHandlerModule, ICommandHandlerModule + where TConfigurator : class, new() + { + } + + /// + /// Encapsulates container configuration for data access command handlers. + /// + public interface IDataAccessCommandHandlerModule : ICommandHandlerModule + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring.Autofac/AutofacEventHandlerModule.cs b/src/RapidField.SolidInstruments.EventAuthoring.Autofac/AutofacEventHandlerModule.cs new file mode 100644 index 00000000..5457838f --- /dev/null +++ b/src/RapidField.SolidInstruments.EventAuthoring.Autofac/AutofacEventHandlerModule.cs @@ -0,0 +1,32 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Autofac; +using Microsoft.Extensions.Configuration; +using RapidField.SolidInstruments.Command.Autofac; +using System; + +namespace RapidField.SolidInstruments.EventAuthoring.Autofac +{ + /// + /// Encapsulates Autofac container configuration for event handlers. + /// + public abstract class AutofacEventHandlerModule : AutofacCommandHandlerModule, IEventHandlerModule + { + /// + /// Initializes a new instance of the class. + /// + /// + /// Configuration information for the application. + /// + /// + /// is . + /// + protected AutofacEventHandlerModule(IConfiguration applicationConfiguration) + : base(applicationConfiguration) + { + return; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring.DotNetNative/DotNetNativeEventHandlerModule.cs b/src/RapidField.SolidInstruments.EventAuthoring.DotNetNative/DotNetNativeEventHandlerModule.cs new file mode 100644 index 00000000..84b471dc --- /dev/null +++ b/src/RapidField.SolidInstruments.EventAuthoring.DotNetNative/DotNetNativeEventHandlerModule.cs @@ -0,0 +1,32 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using RapidField.SolidInstruments.Command.DotNetNative; +using System; + +namespace RapidField.SolidInstruments.EventAuthoring.DotNetNative +{ + /// + /// Encapsulates native .NET container configuration for event handlers. + /// + public abstract class DotNetNativeEventHandlerModule : DotNetNativeCommandHandlerModule, IEventHandlerModule + { + /// + /// Initializes a new instance of the class. + /// + /// + /// Configuration information for the application. + /// + /// + /// is . + /// + protected DotNetNativeEventHandlerModule(IConfiguration applicationConfiguration) + : base(applicationConfiguration) + { + return; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.EventAuthoring/IEventHandlerModule.cs b/src/RapidField.SolidInstruments.EventAuthoring/IEventHandlerModule.cs new file mode 100644 index 00000000..cb4d47fd --- /dev/null +++ b/src/RapidField.SolidInstruments.EventAuthoring/IEventHandlerModule.cs @@ -0,0 +1,26 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; + +namespace RapidField.SolidInstruments.EventAuthoring +{ + /// + /// Encapsulates container configuration for event handlers. + /// + /// + /// The type of the object that configures containers. + /// + public interface IEventHandlerModule : IEventHandlerModule, ICommandHandlerModule + where TConfigurator : class, new() + { + } + + /// + /// Encapsulates container configuration for event handlers. + /// + public interface IEventHandlerModule : ICommandHandlerModule + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.InversionOfControl/IDependencyModule.cs b/src/RapidField.SolidInstruments.InversionOfControl/IDependencyModule.cs index 0368edac..45601895 100644 --- a/src/RapidField.SolidInstruments.InversionOfControl/IDependencyModule.cs +++ b/src/RapidField.SolidInstruments.InversionOfControl/IDependencyModule.cs @@ -12,7 +12,7 @@ namespace RapidField.SolidInstruments.InversionOfControl /// /// The type of the object that configures containers. /// - public interface IDependencyModule + public interface IDependencyModule : IDependencyModule where TConfigurator : class, new() { /// @@ -29,4 +29,11 @@ public interface IDependencyModule /// public void Configure(TConfigurator configurator); } + + /// + /// Encapsulates container configuration for a group of related dependencies. + /// + public interface IDependencyModule + { + } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.Autofac.Asb/AutofacAzureServiceBusModule.cs b/src/RapidField.SolidInstruments.Messaging.Autofac.Asb/AutofacAzureServiceBusModule.cs new file mode 100644 index 00000000..f2011941 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.Autofac.Asb/AutofacAzureServiceBusModule.cs @@ -0,0 +1,61 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Autofac; +using Microsoft.Extensions.Configuration; +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Messaging.Autofac.Asb.Extensions; +using RapidField.SolidInstruments.Messaging.Autofac.Extensions; +using System; + +namespace RapidField.SolidInstruments.Messaging.Autofac.Asb +{ + /// + /// Encapsulates Autofac container configuration for an Azure Service Bus connection and related transport dependencies. + /// + public class AutofacAzureServiceBusModule : AutofacTransportDependencyModule + { + /// + /// Initializes a new instance of the class. + /// + /// + /// Configuration information for the application. + /// + /// + /// The configuration connection string key name for the service bus connection. + /// + /// + /// is empty. + /// + /// + /// is -or- + /// is . + /// + public AutofacAzureServiceBusModule(IConfiguration applicationConfiguration, String connectionStringConfigurationKeyName) + : base(applicationConfiguration, connectionStringConfigurationKeyName) + { + return; + } + + /// + /// Configures the module. + /// + /// + /// An object that configures containers. + /// + /// + /// Configuration information for the application. + /// + protected override void Configure(ContainerBuilder configurator, IConfiguration applicationConfiguration) + { + if (RegistersInMemoryServiceBusComponents) + { + configurator.RegisterSupportingTypesForInMemoryMessaging(); + return; + } + + configurator.RegisterSupportingTypesForAzureServiceBusMessaging(applicationConfiguration, ConnectionStringConfigurationKeyName); + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.Autofac.Rmq/AutofacRabbitMqModule.cs b/src/RapidField.SolidInstruments.Messaging.Autofac.Rmq/AutofacRabbitMqModule.cs new file mode 100644 index 00000000..ff9fde39 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.Autofac.Rmq/AutofacRabbitMqModule.cs @@ -0,0 +1,61 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Autofac; +using Microsoft.Extensions.Configuration; +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Messaging.Autofac.Extensions; +using RapidField.SolidInstruments.Messaging.Autofac.Rmq.Extensions; +using System; + +namespace RapidField.SolidInstruments.Messaging.Autofac.Rmq +{ + /// + /// Encapsulates Autofac container configuration for a RabbitMQ connection and related transport dependencies. + /// + public class AutofacRabbitMqModule : AutofacTransportDependencyModule + { + /// + /// Initializes a new instance of the class. + /// + /// + /// Configuration information for the application. + /// + /// + /// The configuration connection string key name for the service bus connection. + /// + /// + /// is empty. + /// + /// + /// is -or- + /// is . + /// + public AutofacRabbitMqModule(IConfiguration applicationConfiguration, String connectionStringConfigurationKeyName) + : base(applicationConfiguration, connectionStringConfigurationKeyName) + { + return; + } + + /// + /// Configures the module. + /// + /// + /// An object that configures containers. + /// + /// + /// Configuration information for the application. + /// + protected override void Configure(ContainerBuilder configurator, IConfiguration applicationConfiguration) + { + if (RegistersInMemoryServiceBusComponents) + { + configurator.RegisterSupportingTypesForInMemoryMessaging(); + return; + } + + configurator.RegisterSupportingTypesForRabbitMqMessaging(applicationConfiguration, ConnectionStringConfigurationKeyName); + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.Autofac/AutofacMessageHandlerModule.cs b/src/RapidField.SolidInstruments.Messaging.Autofac/AutofacMessageHandlerModule.cs new file mode 100644 index 00000000..4133d764 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.Autofac/AutofacMessageHandlerModule.cs @@ -0,0 +1,32 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Autofac; +using Microsoft.Extensions.Configuration; +using RapidField.SolidInstruments.Command.Autofac; +using System; + +namespace RapidField.SolidInstruments.Messaging.Autofac +{ + /// + /// Encapsulates Autofac container configuration for message handlers. + /// + public abstract class AutofacMessageHandlerModule : AutofacCommandHandlerModule, IMessageHandlerModule + { + /// + /// Initializes a new instance of the class. + /// + /// + /// Configuration information for the application. + /// + /// + /// is . + /// + protected AutofacMessageHandlerModule(IConfiguration applicationConfiguration) + : base(applicationConfiguration) + { + return; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.Autofac/AutofacMessageListenerModule.cs b/src/RapidField.SolidInstruments.Messaging.Autofac/AutofacMessageListenerModule.cs new file mode 100644 index 00000000..6b18a7d8 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.Autofac/AutofacMessageListenerModule.cs @@ -0,0 +1,31 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Autofac; +using Microsoft.Extensions.Configuration; +using System; + +namespace RapidField.SolidInstruments.Messaging.Autofac +{ + /// + /// Encapsulates Autofac container configuration for message listeners. + /// + public abstract class AutofacMessageListenerModule : AutofacMessageHandlerModule, IMessageListenerModule + { + /// + /// Initializes a new instance of the class. + /// + /// + /// Configuration information for the application. + /// + /// + /// is . + /// + protected AutofacMessageListenerModule(IConfiguration applicationConfiguration) + : base(applicationConfiguration) + { + return; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.Autofac/AutofacMessageTransmitterModule.cs b/src/RapidField.SolidInstruments.Messaging.Autofac/AutofacMessageTransmitterModule.cs new file mode 100644 index 00000000..b58150d9 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.Autofac/AutofacMessageTransmitterModule.cs @@ -0,0 +1,31 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Autofac; +using Microsoft.Extensions.Configuration; +using System; + +namespace RapidField.SolidInstruments.Messaging.Autofac +{ + /// + /// Encapsulates Autofac container configuration for message transmitters. + /// + public abstract class AutofacMessageTransmitterModule : AutofacMessageHandlerModule, IMessageTransmitterModule + { + /// + /// Initializes a new instance of the class. + /// + /// + /// Configuration information for the application. + /// + /// + /// is . + /// + protected AutofacMessageTransmitterModule(IConfiguration applicationConfiguration) + : base(applicationConfiguration) + { + return; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.Autofac/AutofacTransportDependencyModule.cs b/src/RapidField.SolidInstruments.Messaging.Autofac/AutofacTransportDependencyModule.cs new file mode 100644 index 00000000..f2f4b69a --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.Autofac/AutofacTransportDependencyModule.cs @@ -0,0 +1,68 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Autofac; +using Microsoft.Extensions.Configuration; +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.InversionOfControl.Autofac; +using System; + +namespace RapidField.SolidInstruments.Messaging.Autofac +{ + /// + /// Encapsulates Autofac container configuration for a service bus connection and related transport dependencies. + /// + public abstract class AutofacTransportDependencyModule : AutofacDependencyModule, ITransportDependencyModule + { + /// + /// Initializes a new instance of the class. + /// + /// + /// Configuration information for the application. + /// + /// + /// The configuration connection string key name for the service bus connection. If the configured connection string value + /// is equal to "InMemory", the module will register in-memory service bus components. + /// + /// + /// is empty. + /// + /// + /// is -or- + /// is . + /// + protected AutofacTransportDependencyModule(IConfiguration applicationConfiguration, String connectionStringConfigurationKeyName) + : base(applicationConfiguration) + { + ConnectionStringConfigurationKeyName = connectionStringConfigurationKeyName.RejectIf().IsNullOrEmpty(nameof(connectionStringConfigurationKeyName)); + RegistersInMemoryServiceBusComponents = applicationConfiguration.GetConnectionString(ConnectionStringConfigurationKeyName)?.ToLower() == InMemoryConnectionStringValue.ToLower(); + } + + /// + /// Gets the configuration connection string key name for the service bus connection. + /// + public String ConnectionStringConfigurationKeyName + { + get; + } + + /// + /// Gets a value indicating whether or not the module registers in-memory service bus components. + /// + /// + /// This property's value is when the configured connection string has a value equal to + /// . + /// + public Boolean RegistersInMemoryServiceBusComponents + { + get; + } + + /// + /// Represents a connection string value that instructs the module to register in-memory service bus components. + /// + protected internal const String InMemoryConnectionStringValue = "InMemory"; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.DotNetNative.Asb/DotNetNativeAzureServiceBusModule.cs b/src/RapidField.SolidInstruments.Messaging.DotNetNative.Asb/DotNetNativeAzureServiceBusModule.cs new file mode 100644 index 00000000..6c11fa83 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.DotNetNative.Asb/DotNetNativeAzureServiceBusModule.cs @@ -0,0 +1,61 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Messaging.DotNetNative.Asb.Extensions; +using RapidField.SolidInstruments.Messaging.DotNetNative.Extensions; +using System; + +namespace RapidField.SolidInstruments.Messaging.DotNetNative.Asb +{ + /// + /// Encapsulates native .NET container configuration for an Azure Service Bus connection and related transport dependencies. + /// + public class DotNetNativeAzureServiceBusModule : DotNetNativeTransportDependencyModule + { + /// + /// Initializes a new instance of the class. + /// + /// + /// Configuration information for the application. + /// + /// + /// The configuration connection string key name for the service bus connection. + /// + /// + /// is empty. + /// + /// + /// is -or- + /// is . + /// + public DotNetNativeAzureServiceBusModule(IConfiguration applicationConfiguration, String connectionStringConfigurationKeyName) + : base(applicationConfiguration, connectionStringConfigurationKeyName) + { + return; + } + + /// + /// Configures the module. + /// + /// + /// An object that configures containers. + /// + /// + /// Configuration information for the application. + /// + protected override void Configure(ServiceCollection configurator, IConfiguration applicationConfiguration) + { + if (RegistersInMemoryServiceBusComponents) + { + configurator.AddSupportingTypesForInMemoryMessaging(); + return; + } + + configurator.AddSupportingTypesForAzureServiceBusMessaging(applicationConfiguration, ConnectionStringConfigurationKeyName); + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq/DotNetNativeRabbitMqModule.cs b/src/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq/DotNetNativeRabbitMqModule.cs new file mode 100644 index 00000000..1451c3f7 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.DotNetNative.Rmq/DotNetNativeRabbitMqModule.cs @@ -0,0 +1,61 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Messaging.DotNetNative.Extensions; +using RapidField.SolidInstruments.Messaging.DotNetNative.Rmq.Extensions; +using System; + +namespace RapidField.SolidInstruments.Messaging.DotNetNative.Rmq +{ + /// + /// Encapsulates native .NET container configuration for a RabbitMQ connection and related transport dependencies. + /// + public class DotNetNativeRabbitMqModule : DotNetNativeTransportDependencyModule + { + /// + /// Initializes a new instance of the class. + /// + /// + /// Configuration information for the application. + /// + /// + /// The configuration connection string key name for the service bus connection. + /// + /// + /// is empty. + /// + /// + /// is -or- + /// is . + /// + public DotNetNativeRabbitMqModule(IConfiguration applicationConfiguration, String connectionStringConfigurationKeyName) + : base(applicationConfiguration, connectionStringConfigurationKeyName) + { + return; + } + + /// + /// Configures the module. + /// + /// + /// An object that configures containers. + /// + /// + /// Configuration information for the application. + /// + protected override void Configure(ServiceCollection configurator, IConfiguration applicationConfiguration) + { + if (RegistersInMemoryServiceBusComponents) + { + configurator.AddSupportingTypesForInMemoryMessaging(); + return; + } + + configurator.AddSupportingTypesForRabbitMqMessaging(applicationConfiguration, ConnectionStringConfigurationKeyName); + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.DotNetNative/DotNetNativeMessageHandlerModule.cs b/src/RapidField.SolidInstruments.Messaging.DotNetNative/DotNetNativeMessageHandlerModule.cs new file mode 100644 index 00000000..b540871f --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.DotNetNative/DotNetNativeMessageHandlerModule.cs @@ -0,0 +1,32 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using RapidField.SolidInstruments.Command.DotNetNative; +using System; + +namespace RapidField.SolidInstruments.Messaging.DotNetNative +{ + /// + /// Encapsulates native .NET container configuration for message handlers. + /// + public abstract class DotNetNativeMessageHandlerModule : DotNetNativeCommandHandlerModule, IMessageHandlerModule + { + /// + /// Initializes a new instance of the class. + /// + /// + /// Configuration information for the application. + /// + /// + /// is . + /// + protected DotNetNativeMessageHandlerModule(IConfiguration applicationConfiguration) + : base(applicationConfiguration) + { + return; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.DotNetNative/DotNetNativeMessageListenerModule.cs b/src/RapidField.SolidInstruments.Messaging.DotNetNative/DotNetNativeMessageListenerModule.cs new file mode 100644 index 00000000..f0792ce9 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.DotNetNative/DotNetNativeMessageListenerModule.cs @@ -0,0 +1,31 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System; + +namespace RapidField.SolidInstruments.Messaging.DotNetNative +{ + /// + /// Encapsulates native .NET container configuration for message listeners. + /// + public abstract class DotNetNativeMessageListenerModule : DotNetNativeMessageHandlerModule, IMessageListenerModule + { + /// + /// Initializes a new instance of the class. + /// + /// + /// Configuration information for the application. + /// + /// + /// is . + /// + protected DotNetNativeMessageListenerModule(IConfiguration applicationConfiguration) + : base(applicationConfiguration) + { + return; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.DotNetNative/DotNetNativeMessageTransmitterModule.cs b/src/RapidField.SolidInstruments.Messaging.DotNetNative/DotNetNativeMessageTransmitterModule.cs new file mode 100644 index 00000000..0dbfc627 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.DotNetNative/DotNetNativeMessageTransmitterModule.cs @@ -0,0 +1,31 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System; + +namespace RapidField.SolidInstruments.Messaging.DotNetNative +{ + /// + /// Encapsulates native .NET container configuration for message transmitters. + /// + public abstract class DotNetNativeMessageTransmitterModule : DotNetNativeMessageHandlerModule, IMessageTransmitterModule + { + /// + /// Initializes a new instance of the class. + /// + /// + /// Configuration information for the application. + /// + /// + /// is . + /// + protected DotNetNativeMessageTransmitterModule(IConfiguration applicationConfiguration) + : base(applicationConfiguration) + { + return; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging.DotNetNative/DotNetNativeTransportDependencyModule.cs b/src/RapidField.SolidInstruments.Messaging.DotNetNative/DotNetNativeTransportDependencyModule.cs new file mode 100644 index 00000000..5cc215e0 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging.DotNetNative/DotNetNativeTransportDependencyModule.cs @@ -0,0 +1,68 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.InversionOfControl.DotNetNative; +using System; + +namespace RapidField.SolidInstruments.Messaging.DotNetNative +{ + /// + /// Encapsulates native .NET container configuration for a service bus connection and related transport dependencies. + /// + public abstract class DotNetNativeTransportDependencyModule : DotNetNativeDependencyModule, ITransportDependencyModule + { + /// + /// Initializes a new instance of the class. + /// + /// + /// Configuration information for the application. + /// + /// + /// The configuration connection string key name for the service bus connection. If the configured connection string value + /// is equal to "InMemory", the module will register in-memory service bus components. + /// + /// + /// is empty. + /// + /// + /// is -or- + /// is . + /// + protected DotNetNativeTransportDependencyModule(IConfiguration applicationConfiguration, String connectionStringConfigurationKeyName) + : base(applicationConfiguration) + { + ConnectionStringConfigurationKeyName = connectionStringConfigurationKeyName.RejectIf().IsNullOrEmpty(nameof(connectionStringConfigurationKeyName)); + RegistersInMemoryServiceBusComponents = applicationConfiguration.GetConnectionString(ConnectionStringConfigurationKeyName)?.ToLower() == InMemoryConnectionStringValue.ToLower(); + } + + /// + /// Gets the configuration connection string key name for the service bus connection. + /// + public String ConnectionStringConfigurationKeyName + { + get; + } + + /// + /// Gets a value indicating whether or not the module registers in-memory service bus components. + /// + /// + /// This property's value is when the configured connection string has a value equal to + /// . + /// + public Boolean RegistersInMemoryServiceBusComponents + { + get; + } + + /// + /// Represents a connection string value that instructs the module to register in-memory service bus components. + /// + protected internal const String InMemoryConnectionStringValue = "InMemory"; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/IMessageHandlerModule.cs b/src/RapidField.SolidInstruments.Messaging/IMessageHandlerModule.cs new file mode 100644 index 00000000..9c57999f --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/IMessageHandlerModule.cs @@ -0,0 +1,26 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.Command; + +namespace RapidField.SolidInstruments.Messaging +{ + /// + /// Encapsulates container configuration for message handlers. + /// + /// + /// The type of the object that configures containers. + /// + public interface IMessageHandlerModule : IMessageHandlerModule, ICommandHandlerModule + where TConfigurator : class, new() + { + } + + /// + /// Encapsulates container configuration for message handlers. + /// + public interface IMessageHandlerModule : ICommandHandlerModule + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/IMessageListenerModule.cs b/src/RapidField.SolidInstruments.Messaging/IMessageListenerModule.cs new file mode 100644 index 00000000..c879074f --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/IMessageListenerModule.cs @@ -0,0 +1,24 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +namespace RapidField.SolidInstruments.Messaging +{ + /// + /// Encapsulates container configuration for message listeners. + /// + /// + /// The type of the object that configures containers. + /// + public interface IMessageListenerModule : IMessageListenerModule, IMessageHandlerModule + where TConfigurator : class, new() + { + } + + /// + /// Encapsulates container configuration for message listeners. + /// + public interface IMessageListenerModule : IMessageHandlerModule + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/IMessageTransmitterModule.cs b/src/RapidField.SolidInstruments.Messaging/IMessageTransmitterModule.cs new file mode 100644 index 00000000..7a7b1fa9 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/IMessageTransmitterModule.cs @@ -0,0 +1,24 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +namespace RapidField.SolidInstruments.Messaging +{ + /// + /// Encapsulates container configuration for message transmitters. + /// + /// + /// The type of the object that configures containers. + /// + public interface IMessageTransmitterModule : IMessageTransmitterModule, IMessageHandlerModule + where TConfigurator : class, new() + { + } + + /// + /// Encapsulates container configuration for message transmitters. + /// + public interface IMessageTransmitterModule : IMessageHandlerModule + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.Messaging/ITransportDependencyModule.cs b/src/RapidField.SolidInstruments.Messaging/ITransportDependencyModule.cs new file mode 100644 index 00000000..f7bcbdf3 --- /dev/null +++ b/src/RapidField.SolidInstruments.Messaging/ITransportDependencyModule.cs @@ -0,0 +1,34 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.InversionOfControl; +using System; + +namespace RapidField.SolidInstruments.Messaging +{ + /// + /// Encapsulates container configuration for a service bus connection and related transport dependencies. + /// + /// + /// The type of the object that configures containers. + /// + public interface ITransportDependencyModule : ITransportDependencyModule, IDependencyModule + where TConfigurator : class, new() + { + } + + /// + /// Encapsulates container configuration for a service bus connection and related transport dependencies. + /// + public interface ITransportDependencyModule : IDependencyModule + { + /// + /// Gets a value indicating whether or not the module registers in-memory service bus components. + /// + public Boolean RegistersInMemoryServiceBusComponents + { + get; + } + } +} \ No newline at end of file From e29873b900c43d7ef607a61b95eb8b56b861b59f Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Sun, 26 Jul 2020 09:11:08 -0500 Subject: [PATCH 53/55] Extend data access command handler system. --- .../AddFibonacciNumberCommandHandler.cs | 10 +- .../GetFibonacciNumberValuesCommandHandler.cs | 10 +- .../AutofacDataStoreDependencyModule.cs | 81 ++++++++++++++++ .../Extensions/ContainerBuilderExtensions.cs | 12 +++ .../DotNetNativeDataStoreDependencyModule.cs | 81 ++++++++++++++++ .../IServiceCollectionExtensions.cs | 20 ++++ .../ContextDatabaseType.cs | 8 +- .../EntityFrameworkCommandHandler.cs | 24 +++-- .../EntityFrameworkRepository.cs | 23 ++++- .../EntityFrameworkRepositoryFactory.cs | 20 +++- .../EntityFrameworkTransaction.cs | 6 +- .../IEntityFrameworkCommandHandler.cs | 41 +++++++++ .../IEntityFrameworkRepository.cs | 92 +++++++++++++++++++ .../IEntityFrameworkRepositoryFactory.cs | 41 +++++++++ .../IEntityFrameworkTransaction.cs | 26 ++++++ .../DataAccessCommandHandler.cs | 22 ++++- .../IDataStoreDependencyModule.cs | 42 +++++++++ 17 files changed, 532 insertions(+), 27 deletions(-) create mode 100644 src/RapidField.SolidInstruments.DataAccess.Autofac/AutofacDataStoreDependencyModule.cs create mode 100644 src/RapidField.SolidInstruments.DataAccess.DotNetNative/DotNetNativeDataStoreDependencyModule.cs create mode 100644 src/RapidField.SolidInstruments.DataAccess.EntityFramework/IEntityFrameworkCommandHandler.cs create mode 100644 src/RapidField.SolidInstruments.DataAccess.EntityFramework/IEntityFrameworkRepository.cs create mode 100644 src/RapidField.SolidInstruments.DataAccess.EntityFramework/IEntityFrameworkRepositoryFactory.cs create mode 100644 src/RapidField.SolidInstruments.DataAccess.EntityFramework/IEntityFrameworkTransaction.cs create mode 100644 src/RapidField.SolidInstruments.DataAccess/IDataStoreDependencyModule.cs diff --git a/example/RapidField.SolidInstruments.Example.DatabaseModel/CommandHandlers/AddFibonacciNumberCommandHandler.cs b/example/RapidField.SolidInstruments.Example.DatabaseModel/CommandHandlers/AddFibonacciNumberCommandHandler.cs index 2f23a341..3a368921 100644 --- a/example/RapidField.SolidInstruments.Example.DatabaseModel/CommandHandlers/AddFibonacciNumberCommandHandler.cs +++ b/example/RapidField.SolidInstruments.Example.DatabaseModel/CommandHandlers/AddFibonacciNumberCommandHandler.cs @@ -3,6 +3,7 @@ // ================================================================================================================================= using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Concurrency; using RapidField.SolidInstruments.Example.DatabaseModel.Commands; using RapidField.SolidInstruments.Example.DatabaseModel.Entities; using RapidField.SolidInstruments.Example.DatabaseModel.Repositories; @@ -50,10 +51,17 @@ public AddFibonacciNumberCommandHandler(ICommandMediator mediator, ExampleReposi /// /// The command to process. /// + /// + /// A processing intermediary that is used to process sub-commands. Do not process using + /// , as doing so will generally result in infinite-looping. + /// /// /// An object that provides access to data access repositories. /// - protected override void Process(AddFibonacciNumberCommand command, IFactoryProducedInstanceGroup repositories) + /// + /// A token that represents and manages contextual thread safety. + /// + protected override void Process(AddFibonacciNumberCommand command, ICommandMediator mediator, IFactoryProducedInstanceGroup repositories, IConcurrencyControlToken controlToken) { var fibonacciNumberSeries = NumberSeries.Named.Fibonacci; var numberRepository = repositories.Get(); diff --git a/example/RapidField.SolidInstruments.Example.DatabaseModel/CommandHandlers/GetFibonacciNumberValuesCommandHandler.cs b/example/RapidField.SolidInstruments.Example.DatabaseModel/CommandHandlers/GetFibonacciNumberValuesCommandHandler.cs index 0b3692db..3a346909 100644 --- a/example/RapidField.SolidInstruments.Example.DatabaseModel/CommandHandlers/GetFibonacciNumberValuesCommandHandler.cs +++ b/example/RapidField.SolidInstruments.Example.DatabaseModel/CommandHandlers/GetFibonacciNumberValuesCommandHandler.cs @@ -3,6 +3,7 @@ // ================================================================================================================================= using RapidField.SolidInstruments.Command; +using RapidField.SolidInstruments.Core.Concurrency; using RapidField.SolidInstruments.Example.DatabaseModel.Commands; using RapidField.SolidInstruments.Example.DatabaseModel.Entities; using RapidField.SolidInstruments.Example.DatabaseModel.Repositories; @@ -51,13 +52,20 @@ public GetFibonacciNumberValuesCommandHandler(ICommandMediator mediator, Example /// /// The command to process. /// + /// + /// A processing intermediary that is used to process sub-commands. Do not process using + /// , as doing so will generally result in infinite-looping. + /// /// /// An object that provides access to data access repositories. /// + /// + /// A token that represents and manages contextual thread safety. + /// /// /// The result that is emitted when processing the command. /// - protected sealed override IEnumerable Process(GetFibonacciNumberValuesCommand command, IFactoryProducedInstanceGroup repositories) + protected sealed override IEnumerable Process(GetFibonacciNumberValuesCommand command, ICommandMediator mediator, IFactoryProducedInstanceGroup repositories, IConcurrencyControlToken controlToken) { var numberSeriesNumberRepository = repositories.Get(); return numberSeriesNumberRepository.FindWhere(entity => entity.NumberSeries.Identifier == NumberSeries.Named.Fibonacci.Identifier).Select(entity => entity.Number.Value).ToList(); diff --git a/src/RapidField.SolidInstruments.DataAccess.Autofac/AutofacDataStoreDependencyModule.cs b/src/RapidField.SolidInstruments.DataAccess.Autofac/AutofacDataStoreDependencyModule.cs new file mode 100644 index 00000000..d7304104 --- /dev/null +++ b/src/RapidField.SolidInstruments.DataAccess.Autofac/AutofacDataStoreDependencyModule.cs @@ -0,0 +1,81 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Autofac; +using Microsoft.Extensions.Configuration; +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.InversionOfControl.Autofac; +using System; + +namespace RapidField.SolidInstruments.DataAccess.Autofac +{ + /// + /// Encapsulates Autofac container configuration for a data store connection and related data access dependencies. + /// + public abstract class AutofacDataStoreDependencyModule : AutofacDependencyModule, IDataStoreDependencyModule + { + /// + /// Initializes a new instance of the class. + /// + /// + /// Configuration information for the application. + /// + /// + /// The configuration connection string key name for the data store connection. If the configured connection string value is + /// equal to "InMemory", the module will register in-memory database components. + /// + /// + /// The name of the associated data store. + /// + /// + /// is empty -or- is empty. + /// + /// + /// is -or- + /// is -or- + /// is . + /// + protected AutofacDataStoreDependencyModule(IConfiguration applicationConfiguration, String connectionStringConfigurationKeyName, String dataStoreName) + : base(applicationConfiguration) + { + ConnectionStringConfigurationKeyName = connectionStringConfigurationKeyName.RejectIf().IsNullOrEmpty(nameof(connectionStringConfigurationKeyName)); + DataStoreName = dataStoreName.RejectIf().IsNullOrEmpty(nameof(dataStoreName)); + RegistersInMemoryDatabaseComponents = applicationConfiguration.GetConnectionString(ConnectionStringConfigurationKeyName)?.ToLower() == InMemoryConnectionStringValue.ToLower(); + } + + /// + /// Gets the configuration connection string key name for the data store connection. + /// + public String ConnectionStringConfigurationKeyName + { + get; + } + + /// + /// Gets the name of the associated data store. + /// + public String DataStoreName + { + get; + } + + /// + /// Gets a value indicating whether or not the module registers in-memory database components. + /// + /// + /// This property's value is when the configured connection string has a value equal to + /// . + /// + public Boolean RegistersInMemoryDatabaseComponents + { + get; + } + + /// + /// Represents a connection string value that instructs the module to register in-memory data store components. + /// + protected internal const String InMemoryConnectionStringValue = "InMemory"; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess.Autofac/Extensions/ContainerBuilderExtensions.cs b/src/RapidField.SolidInstruments.DataAccess.Autofac/Extensions/ContainerBuilderExtensions.cs index dc59ef23..0eb0e1af 100644 --- a/src/RapidField.SolidInstruments.DataAccess.Autofac/Extensions/ContainerBuilderExtensions.cs +++ b/src/RapidField.SolidInstruments.DataAccess.Autofac/Extensions/ContainerBuilderExtensions.cs @@ -47,5 +47,17 @@ public static void RegisterDataAccessCommandHandler(t public static void RegisterDataAccessCommandHandler(this ContainerBuilder target) where TCommand : class, IDataAccessCommand where TCommandHandler : class, IDataAccessCommandHandler => target.RegisterCommandHandler(); + + /// + /// Registers a data access repository factory of the specified type. + /// + /// + /// The type of the data access repository factory that is registered. + /// + /// + /// The current . + /// + public static void RegisterDataAccessRepositoryFactory(this ContainerBuilder target) + where TRepositoryFactory : class, IDataAccessRepositoryFactory => target.RegisterType().As().AsSelf().InstancePerLifetimeScope(); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess.DotNetNative/DotNetNativeDataStoreDependencyModule.cs b/src/RapidField.SolidInstruments.DataAccess.DotNetNative/DotNetNativeDataStoreDependencyModule.cs new file mode 100644 index 00000000..6b8ccfc6 --- /dev/null +++ b/src/RapidField.SolidInstruments.DataAccess.DotNetNative/DotNetNativeDataStoreDependencyModule.cs @@ -0,0 +1,81 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using RapidField.SolidInstruments.Core; +using RapidField.SolidInstruments.Core.ArgumentValidation; +using RapidField.SolidInstruments.InversionOfControl.DotNetNative; +using System; + +namespace RapidField.SolidInstruments.DataAccess.DotNetNative +{ + /// + /// Encapsulates native .NET container configuration for a data store connection and related data access dependencies. + /// + public abstract class DotNetNativeDataStoreDependencyModule : DotNetNativeDependencyModule, IDataStoreDependencyModule + { + /// + /// Initializes a new instance of the class. + /// + /// + /// Configuration information for the application. + /// + /// + /// The configuration connection string key name for the data store connection. If the configured connection string value is + /// equal to "InMemory", the module will register in-memory database components. + /// + /// + /// The name of the associated data store. + /// + /// + /// is empty -or- is empty. + /// + /// + /// is -or- + /// is -or- + /// is . + /// + protected DotNetNativeDataStoreDependencyModule(IConfiguration applicationConfiguration, String connectionStringConfigurationKeyName, String dataStoreName) + : base(applicationConfiguration) + { + ConnectionStringConfigurationKeyName = connectionStringConfigurationKeyName.RejectIf().IsNullOrEmpty(nameof(connectionStringConfigurationKeyName)); + DataStoreName = dataStoreName.RejectIf().IsNullOrEmpty(nameof(dataStoreName)); + RegistersInMemoryDatabaseComponents = applicationConfiguration.GetConnectionString(ConnectionStringConfigurationKeyName)?.ToLower() == InMemoryConnectionStringValue.ToLower(); + } + + /// + /// Gets the configuration connection string key name for the data store connection. + /// + public String ConnectionStringConfigurationKeyName + { + get; + } + + /// + /// Gets the name of the associated data store. + /// + public String DataStoreName + { + get; + } + + /// + /// Gets a value indicating whether or not the module registers in-memory database components. + /// + /// + /// This property's value is when the configured connection string has a value equal to + /// . + /// + public Boolean RegistersInMemoryDatabaseComponents + { + get; + } + + /// + /// Represents a connection string value that instructs the module to register in-memory data store components. + /// + protected internal const String InMemoryConnectionStringValue = "InMemory"; + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess.DotNetNative/Extensions/IServiceCollectionExtensions.cs b/src/RapidField.SolidInstruments.DataAccess.DotNetNative/Extensions/IServiceCollectionExtensions.cs index aacd38ff..8938610c 100644 --- a/src/RapidField.SolidInstruments.DataAccess.DotNetNative/Extensions/IServiceCollectionExtensions.cs +++ b/src/RapidField.SolidInstruments.DataAccess.DotNetNative/Extensions/IServiceCollectionExtensions.cs @@ -54,5 +54,25 @@ public static IServiceCollection AddDataAccessCommandHandler(this IServiceCollection target) where TCommand : class, IDataAccessCommand where TCommandHandler : class, IDataAccessCommandHandler => target.AddCommandHandler(); + + /// + /// Registers a data access repository factory of the specified type. + /// + /// + /// The type of the data access repository factory that is registered. + /// + /// + /// The current . + /// + /// + /// The resulting . + /// + public static IServiceCollection AddDataAccessRepositoryFactory(this IServiceCollection target) + where TRepositoryFactory : class, IDataAccessRepositoryFactory + { + target.AddScoped(); + target.AddScoped((serviceProvider) => serviceProvider.GetService()); + return target; + } } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess.EntityFramework/ContextDatabaseType.cs b/src/RapidField.SolidInstruments.DataAccess.EntityFramework/ContextDatabaseType.cs index e15848fd..df6c71d6 100644 --- a/src/RapidField.SolidInstruments.DataAccess.EntityFramework/ContextDatabaseType.cs +++ b/src/RapidField.SolidInstruments.DataAccess.EntityFramework/ContextDatabaseType.cs @@ -17,13 +17,13 @@ public enum ContextDatabaseType : Int32 Unspecified = 0, /// - /// The context is backed by a SQL Server database. + /// The context is backed by an in-memory database. /// - SQLServer = 1, + InMemory = 1, /// - /// The context is backed by an in-memory database. + /// The context is backed by a SQL Server database. /// - InMemory = 2 + SQLServer = 2 } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess.EntityFramework/EntityFrameworkCommandHandler.cs b/src/RapidField.SolidInstruments.DataAccess.EntityFramework/EntityFrameworkCommandHandler.cs index 3b6794cf..98d166b0 100644 --- a/src/RapidField.SolidInstruments.DataAccess.EntityFramework/EntityFrameworkCommandHandler.cs +++ b/src/RapidField.SolidInstruments.DataAccess.EntityFramework/EntityFrameworkCommandHandler.cs @@ -13,13 +13,17 @@ namespace RapidField.SolidInstruments.DataAccess.EntityFramework /// /// Processes Entity Framework data access commands. /// + /// + /// is the default implementation of + /// . + /// /// /// The type of the database session for the transaction. /// /// /// The type of the data access command that is processed by the handler. /// - public abstract class EntityFrameworkCommandHandler : DataAccessCommandHandler + public abstract class EntityFrameworkCommandHandler : DataAccessCommandHandler, IEntityFrameworkCommandHandler where TContext : DbContext where TCommand : class, IDataAccessCommand { @@ -36,7 +40,7 @@ public abstract class EntityFrameworkCommandHandler : DataAc /// is -or- is /// . /// - protected EntityFrameworkCommandHandler(ICommandMediator mediator, EntityFrameworkRepositoryFactory repositoryFactory) + protected EntityFrameworkCommandHandler(ICommandMediator mediator, IEntityFrameworkRepositoryFactory repositoryFactory) : this(mediator, repositoryFactory, DefaultIsolationLevel) { return; @@ -59,7 +63,7 @@ protected EntityFrameworkCommandHandler(ICommandMediator mediator, EntityFramewo /// is -or- is /// . /// - protected EntityFrameworkCommandHandler(ICommandMediator mediator, EntityFrameworkRepositoryFactory repositoryFactory, IsolationLevel isolationLevel) + protected EntityFrameworkCommandHandler(ICommandMediator mediator, IEntityFrameworkRepositoryFactory repositoryFactory, IsolationLevel isolationLevel) : this(mediator, repositoryFactory, new EntityFrameworkTransaction(repositoryFactory.Context, isolationLevel)) { return; @@ -84,7 +88,7 @@ protected EntityFrameworkCommandHandler(ICommandMediator mediator, EntityFramewo /// is -or- is /// -or- is . /// - protected EntityFrameworkCommandHandler(ICommandMediator mediator, EntityFrameworkRepositoryFactory repositoryFactory, EntityFrameworkTransaction transaction) + protected EntityFrameworkCommandHandler(ICommandMediator mediator, IEntityFrameworkRepositoryFactory repositoryFactory, IEntityFrameworkTransaction transaction) : base(mediator, repositoryFactory, transaction) { return; @@ -108,6 +112,10 @@ protected EntityFrameworkCommandHandler(ICommandMediator mediator, EntityFramewo /// /// Processes Entity Framework data access commands. /// + /// + /// is the default implementation of + /// . + /// /// /// The type of the database session for the transaction. /// @@ -117,7 +125,7 @@ protected EntityFrameworkCommandHandler(ICommandMediator mediator, EntityFramewo /// /// The type of the result that is emitted by the handler when processing a data access command. /// - public abstract class EntityFrameworkCommandHandler : DataAccessCommandHandler + public abstract class EntityFrameworkCommandHandler : DataAccessCommandHandler, IEntityFrameworkCommandHandler where TContext : DbContext where TCommand : class, IDataAccessCommand { @@ -134,7 +142,7 @@ public abstract class EntityFrameworkCommandHandler /// is -or- is /// . /// - protected EntityFrameworkCommandHandler(ICommandMediator mediator, EntityFrameworkRepositoryFactory repositoryFactory) + protected EntityFrameworkCommandHandler(ICommandMediator mediator, IEntityFrameworkRepositoryFactory repositoryFactory) : this(mediator, repositoryFactory, DefaultIsolationLevel) { return; @@ -157,7 +165,7 @@ protected EntityFrameworkCommandHandler(ICommandMediator mediator, EntityFramewo /// is -or- is /// . /// - protected EntityFrameworkCommandHandler(ICommandMediator mediator, EntityFrameworkRepositoryFactory repositoryFactory, IsolationLevel isolationLevel) + protected EntityFrameworkCommandHandler(ICommandMediator mediator, IEntityFrameworkRepositoryFactory repositoryFactory, IsolationLevel isolationLevel) : this(mediator, repositoryFactory, new EntityFrameworkTransaction(repositoryFactory.Context, isolationLevel)) { return; @@ -182,7 +190,7 @@ protected EntityFrameworkCommandHandler(ICommandMediator mediator, EntityFramewo /// is -or- is /// -or- is . /// - protected EntityFrameworkCommandHandler(ICommandMediator mediator, EntityFrameworkRepositoryFactory repositoryFactory, EntityFrameworkTransaction transaction) + protected EntityFrameworkCommandHandler(ICommandMediator mediator, IEntityFrameworkRepositoryFactory repositoryFactory, IEntityFrameworkTransaction transaction) : base(mediator, repositoryFactory, transaction) { return; diff --git a/src/RapidField.SolidInstruments.DataAccess.EntityFramework/EntityFrameworkRepository.cs b/src/RapidField.SolidInstruments.DataAccess.EntityFramework/EntityFrameworkRepository.cs index 77ca00f0..6c0f8f38 100644 --- a/src/RapidField.SolidInstruments.DataAccess.EntityFramework/EntityFrameworkRepository.cs +++ b/src/RapidField.SolidInstruments.DataAccess.EntityFramework/EntityFrameworkRepository.cs @@ -17,6 +17,10 @@ namespace RapidField.SolidInstruments.DataAccess.EntityFramework /// /// Performs data access operations against an Entity Framework data model type using a single transaction. /// + /// + /// is the default + /// implementation of . + /// /// /// The type of the unique primary identifier for the data model. /// @@ -29,7 +33,7 @@ namespace RapidField.SolidInstruments.DataAccess.EntityFramework /// /// The type of the database session for the repository. /// - public class EntityFrameworkRepository : EntityFrameworkRepository, IDomainModelRepository + public class EntityFrameworkRepository : EntityFrameworkRepository, IEntityFrameworkRepository where TIdentifier : IComparable, IComparable, IEquatable where TDomainModel : class, IDomainModel where TDataAccessModel : class, IDataAccessModel, new() @@ -55,6 +59,10 @@ public EntityFrameworkRepository(TContext context) /// /// Performs data access operations against an Entity Framework data model type using a single transaction. /// + /// + /// is the default implementation of + /// . + /// /// /// The type of the unique primary identifier for the data model. /// @@ -64,7 +72,7 @@ public EntityFrameworkRepository(TContext context) /// /// The type of the database session for the repository. /// - public class EntityFrameworkRepository : EntityFrameworkRepository, IDataAccessModelRepository + public class EntityFrameworkRepository : EntityFrameworkRepository, IEntityFrameworkRepository where TIdentifier : IComparable, IComparable, IEquatable where TDataAccessModel : class, IDataAccessModel where TContext : DbContext @@ -89,13 +97,17 @@ public EntityFrameworkRepository(TContext context) /// /// Performs data access operations against an Entity Framework entity type using a single transaction. /// + /// + /// is the default implementation of + /// . + /// /// /// The type of the entity. /// /// /// The type of the database session for the repository. /// - public class EntityFrameworkRepository : DataAccessRepository + public class EntityFrameworkRepository : DataAccessRepository, IEntityFrameworkRepository where TEntity : class where TContext : DbContext { @@ -302,6 +314,11 @@ protected override Boolean Contains(TEntity entity, IConcurrencyControlToken con /// protected override void UpdateRange(IEnumerable entities, IConcurrencyControlToken controlToken) => Set.UpdateRange(entities); + /// + /// Gets the database session type of the current . + /// + public Type ContextType => typeof(TContext); + /// /// Represents the database session for the current . /// diff --git a/src/RapidField.SolidInstruments.DataAccess.EntityFramework/EntityFrameworkRepositoryFactory.cs b/src/RapidField.SolidInstruments.DataAccess.EntityFramework/EntityFrameworkRepositoryFactory.cs index 63c16353..28a3864a 100644 --- a/src/RapidField.SolidInstruments.DataAccess.EntityFramework/EntityFrameworkRepositoryFactory.cs +++ b/src/RapidField.SolidInstruments.DataAccess.EntityFramework/EntityFrameworkRepositoryFactory.cs @@ -7,17 +7,20 @@ using RapidField.SolidInstruments.Core.ArgumentValidation; using RapidField.SolidInstruments.ObjectComposition; using System; -using System.Diagnostics; namespace RapidField.SolidInstruments.DataAccess.EntityFramework { /// /// Encapsulates creation of new Entity Framework instances that map to database entities. /// + /// + /// is the default implementation of + /// . + /// /// /// The type of the database session that is used by the produced repositories. /// - public abstract class EntityFrameworkRepositoryFactory : DataAccessRepositoryFactory + public abstract class EntityFrameworkRepositoryFactory : DataAccessRepositoryFactory, IEntityFrameworkRepositoryFactory where TContext : DbContext { /// @@ -82,9 +85,16 @@ protected EntityFrameworkRepositoryFactory(TContext context, IConfiguration appl protected override void Dispose(Boolean disposing) => base.Dispose(disposing); /// - /// Represents the database session that is used by the produced repositories. + /// Gets the database session that is used by the produced repositories. /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - internal readonly TContext Context; + public TContext Context + { + get; + } + + /// + /// Gets the type of the database session that is used by the produced repositories. + /// + public Type ContextType => typeof(TContext); } } \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess.EntityFramework/EntityFrameworkTransaction.cs b/src/RapidField.SolidInstruments.DataAccess.EntityFramework/EntityFrameworkTransaction.cs index b7dbf63b..99a24df0 100644 --- a/src/RapidField.SolidInstruments.DataAccess.EntityFramework/EntityFrameworkTransaction.cs +++ b/src/RapidField.SolidInstruments.DataAccess.EntityFramework/EntityFrameworkTransaction.cs @@ -18,10 +18,14 @@ namespace RapidField.SolidInstruments.DataAccess.EntityFramework /// /// Fulfills the unit of work pattern for Entity Framework data access operations. /// + /// + /// is the default implementation of + /// . + /// /// /// The type of the database session for the transaction. /// - public class EntityFrameworkTransaction : DataAccessTransaction + public class EntityFrameworkTransaction : DataAccessTransaction, IEntityFrameworkTransaction where TContext : DbContext { /// diff --git a/src/RapidField.SolidInstruments.DataAccess.EntityFramework/IEntityFrameworkCommandHandler.cs b/src/RapidField.SolidInstruments.DataAccess.EntityFramework/IEntityFrameworkCommandHandler.cs new file mode 100644 index 00000000..3fdb7761 --- /dev/null +++ b/src/RapidField.SolidInstruments.DataAccess.EntityFramework/IEntityFrameworkCommandHandler.cs @@ -0,0 +1,41 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Microsoft.EntityFrameworkCore; + +namespace RapidField.SolidInstruments.DataAccess.EntityFramework +{ + /// + /// Processes Entity Framework data access commands. + /// + /// + /// The type of the database session for the transaction. + /// + /// + /// The type of the data access command that is processed by the handler. + /// + public interface IEntityFrameworkCommandHandler : IDataAccessCommandHandler + where TContext : DbContext + where TCommand : class, IDataAccessCommand + { + } + + /// + /// Processes Entity Framework data access commands. + /// + /// + /// The type of the database session for the transaction. + /// + /// + /// The type of the data access command that is processed by the handler. + /// + /// + /// The type of the result that is emitted by the handler when processing a data access command. + /// + public interface IEntityFrameworkCommandHandler : IDataAccessCommandHandler + where TContext : DbContext + where TCommand : class, IDataAccessCommand + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess.EntityFramework/IEntityFrameworkRepository.cs b/src/RapidField.SolidInstruments.DataAccess.EntityFramework/IEntityFrameworkRepository.cs new file mode 100644 index 00000000..7a20fb02 --- /dev/null +++ b/src/RapidField.SolidInstruments.DataAccess.EntityFramework/IEntityFrameworkRepository.cs @@ -0,0 +1,92 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Microsoft.EntityFrameworkCore; +using RapidField.SolidInstruments.Core.Domain; +using System; + +namespace RapidField.SolidInstruments.DataAccess.EntityFramework +{ + /// + /// Performs data access operations against an Entity Framework data model type using a single transaction. + /// + /// + /// The type of the unique primary identifier for the data model. + /// + /// + /// The type of the data model. + /// + /// + /// The type of the domain model to which the data access model is mapped. + /// + /// + /// The type of the database session for the repository. + /// + public interface IEntityFrameworkRepository : IEntityFrameworkRepository, IDomainModelRepository + where TIdentifier : IComparable, IComparable, IEquatable + where TDomainModel : class, IDomainModel + where TDataAccessModel : class, IDataAccessModel, new() + where TContext : DbContext + { + } + + /// + /// Performs data access operations against an Entity Framework data model type using a single transaction. + /// + /// + /// The type of the unique primary identifier for the data model. + /// + /// + /// The type of the data model. + /// + /// + /// The type of the database session for the repository. + /// + public interface IEntityFrameworkRepository : IEntityFrameworkRepository, IDataAccessModelRepository + where TIdentifier : IComparable, IComparable, IEquatable + where TDataAccessModel : class, IDataAccessModel + where TContext : DbContext + { + } + + /// + /// Performs data access operations against an Entity Framework entity type using a single transaction. + /// + /// + /// The type of the entity. + /// + /// + /// The type of the database session for the repository. + /// + public interface IEntityFrameworkRepository : IDataAccessRepository, IEntityFrameworkRepository + where TEntity : class + where TContext : DbContext + { + } + + /// + /// Performs data access operations against an Entity Framework entity type using a single transaction. + /// + /// + /// The type of the database session for the repository. + /// + public interface IEntityFrameworkRepository : IEntityFrameworkRepository + where TContext : DbContext + { + /// + /// Gets the database session type of the current . + /// + public Type ContextType + { + get; + } + } + + /// + /// Performs data access operations against an Entity Framework entity type using a single transaction. + /// + public interface IEntityFrameworkRepository : IDataAccessRepository + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess.EntityFramework/IEntityFrameworkRepositoryFactory.cs b/src/RapidField.SolidInstruments.DataAccess.EntityFramework/IEntityFrameworkRepositoryFactory.cs new file mode 100644 index 00000000..a13d597a --- /dev/null +++ b/src/RapidField.SolidInstruments.DataAccess.EntityFramework/IEntityFrameworkRepositoryFactory.cs @@ -0,0 +1,41 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Microsoft.EntityFrameworkCore; +using System; + +namespace RapidField.SolidInstruments.DataAccess.EntityFramework +{ + /// + /// Encapsulates creation of new Entity Framework instances that map to database entities. + /// + /// + /// The type of the database session that is used by the produced repositories. + /// + public interface IEntityFrameworkRepositoryFactory : IEntityFrameworkRepositoryFactory + where TContext : DbContext + { + /// + /// Gets the database session that is used by the produced repositories. + /// + public TContext Context + { + get; + } + } + + /// + /// Encapsulates creation of new Entity Framework instances that map to database entities. + /// + public interface IEntityFrameworkRepositoryFactory : IDataAccessRepositoryFactory + { + /// + /// Gets the type of the database session that is used by the produced repositories. + /// + public Type ContextType + { + get; + } + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess.EntityFramework/IEntityFrameworkTransaction.cs b/src/RapidField.SolidInstruments.DataAccess.EntityFramework/IEntityFrameworkTransaction.cs new file mode 100644 index 00000000..a9eca0bd --- /dev/null +++ b/src/RapidField.SolidInstruments.DataAccess.EntityFramework/IEntityFrameworkTransaction.cs @@ -0,0 +1,26 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using Microsoft.EntityFrameworkCore; + +namespace RapidField.SolidInstruments.DataAccess.EntityFramework +{ + /// + /// Fulfills the unit of work pattern for Entity Framework data access operations. + /// + /// + /// The type of the database session for the transaction. + /// + public interface IEntityFrameworkTransaction : IEntityFrameworkTransaction + where TContext : DbContext + { + } + + /// + /// Fulfills the unit of work pattern for Entity Framework data access operations. + /// + public interface IEntityFrameworkTransaction : IDataAccessTransaction + { + } +} \ No newline at end of file diff --git a/src/RapidField.SolidInstruments.DataAccess/DataAccessCommandHandler.cs b/src/RapidField.SolidInstruments.DataAccess/DataAccessCommandHandler.cs index a29bcd2b..e6250372 100644 --- a/src/RapidField.SolidInstruments.DataAccess/DataAccessCommandHandler.cs +++ b/src/RapidField.SolidInstruments.DataAccess/DataAccessCommandHandler.cs @@ -98,7 +98,7 @@ protected sealed override void Process(TCommand command, ICommandMediator mediat try { Transaction.Begin(); - Process(command, Repositories); + Process(command, mediator, Repositories, controlToken); CommitTransaction(Transaction, controlToken); return; } @@ -118,10 +118,17 @@ protected sealed override void Process(TCommand command, ICommandMediator mediat /// /// The command to process. /// + /// + /// A processing intermediary that is used to process sub-commands. Do not process using + /// , as doing so will generally result in infinite-looping. + /// /// /// An object that provides access to data access repositories. /// - protected abstract void Process(TCommand command, IFactoryProducedInstanceGroup repositories); + /// + /// A token that represents and manages contextual thread safety. + /// + protected abstract void Process(TCommand command, ICommandMediator mediator, IFactoryProducedInstanceGroup repositories, IConcurrencyControlToken controlToken); /// /// Conditionally starts an asynchronous task that rejects all changes made within the scope of the specified transaction. @@ -265,7 +272,7 @@ protected sealed override TResult Process(TCommand command, ICommandMediator med try { Transaction.Begin(); - var result = Process(command, Repositories); + var result = Process(command, mediator, Repositories, controlToken); CommitTransaction(Transaction, controlToken); return result; } @@ -285,13 +292,20 @@ protected sealed override TResult Process(TCommand command, ICommandMediator med /// /// The command to process. /// + /// + /// A processing intermediary that is used to process sub-commands. Do not process using + /// , as doing so will generally result in infinite-looping. + /// /// /// An object that provides access to data access repositories. /// + /// + /// A token that represents and manages contextual thread safety. + /// /// /// The result that is emitted when processing the command. /// - protected abstract TResult Process(TCommand command, IFactoryProducedInstanceGroup repositories); + protected abstract TResult Process(TCommand command, ICommandMediator mediator, IFactoryProducedInstanceGroup repositories, IConcurrencyControlToken controlToken); /// /// Conditionally starts an asynchronous task that rejects all changes made within the scope of the specified transaction. diff --git a/src/RapidField.SolidInstruments.DataAccess/IDataStoreDependencyModule.cs b/src/RapidField.SolidInstruments.DataAccess/IDataStoreDependencyModule.cs new file mode 100644 index 00000000..d9e5a801 --- /dev/null +++ b/src/RapidField.SolidInstruments.DataAccess/IDataStoreDependencyModule.cs @@ -0,0 +1,42 @@ +// ================================================================================================================================= +// Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ================================================================================================================================= + +using RapidField.SolidInstruments.InversionOfControl; +using System; + +namespace RapidField.SolidInstruments.DataAccess +{ + /// + /// Encapsulates container configuration for a data store connection and related data access dependencies. + /// + /// + /// The type of the object that configures containers. + /// + public interface IDataStoreDependencyModule : IDataStoreDependencyModule, IDependencyModule + where TConfigurator : class, new() + { + } + + /// + /// Encapsulates container configuration for a data store connection and related data access dependencies. + /// + public interface IDataStoreDependencyModule : IDependencyModule + { + /// + /// Gets the name of the associated data store. + /// + public String DataStoreName + { + get; + } + + /// + /// Gets a value indicating whether or not the module registers in-memory database components. + /// + public Boolean RegistersInMemoryDatabaseComponents + { + get; + } + } +} \ No newline at end of file From 2f1ccd03f9c87790f1dfbac6011d85d1081168a3 Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Sun, 26 Jul 2020 09:35:47 -0500 Subject: [PATCH 54/55] Add v1.0.25 release notes. --- doc/releasenotes/v1.0.25.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/doc/releasenotes/v1.0.25.md b/doc/releasenotes/v1.0.25.md index 0604d6ad..9a3176c3 100644 --- a/doc/releasenotes/v1.0.25.md +++ b/doc/releasenotes/v1.0.25.md @@ -8,11 +8,19 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in ## Feature enhancements -... +Add messaging support for RabbitMQ. ## Stability enhancements -... +Bump Autofac from 4.9.2 to 4.9.3 +Bump Autofac.Extensions.DependencyInjection from 4.4.0 to 5.0.0 +Bump Microsoft.Azure.ServiceBus from 3.4.0 to 4.0.0 +Bump Microsoft.EntityFrameworkCore from 2.2.6 to 3.0.0 +Bump Microsoft.Extensions.Configuration from 2.2.0 to 3.0.0 +Bump Microsoft.Extensions.DependencyInjection.Abstractions from 2.2.0 to 3.0.0 +Bump RabbitMQ.Client from 5.1.0 to 5.1.1 +Reduce complexity of random number generation extensions. +Reduce complexity of string parsing methods. ## More information From 1fb47889979e060544251c4de54e2371a65b05ab Mon Sep 17 00:00:00 2001 From: adamjstone <8525409+adamjstone@users.noreply.github.com> Date: Sun, 26 Jul 2020 09:42:33 -0500 Subject: [PATCH 55/55] Minor documentation update. --- README.md | 3 ++- doc/index.md | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b0b4e334..5b3fc2e4 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ Coming soon. | Version | Notes | Source | Release date | End of support | | :-------------- | :---------------------------------: | :------------------------------------------------------------------------------: | :----------: | :------------: | +| 1.0.25-preview1 | [Read](doc/releasenotes/v1.0.25.md) | [Explore](https://github.com/RapidField/solid-instruments/tree/v1.0.25-preview1) | 26 Jul 2020 | 31 Jul 2023 | | 1.0.24-preview1 | [Read](doc/releasenotes/v1.0.24.md) | [Explore](https://github.com/RapidField/solid-instruments/tree/v1.0.24-preview1) | 21 Jul 2019 | 31 Jul 2022 | | 1.0.23-preview1 | [Read](doc/releasenotes/v1.0.23.md) | [Explore](https://github.com/RapidField/solid-instruments/tree/v1.0.23-preview1) | 20 Jul 2019 | 31 Jul 2022 | @@ -92,4 +93,4 @@ Check out the [**Product Roadmap**](ROADMAP.md) to see what's planned for future [![RapidField](RapidField.Logo.Color.Black.Transparent.200w.png)](https://www.rapidfield.com) -###### Copyright (c) RapidField LLC. All rights reserved. "RapidField" and "Solid Instruments" are trademarks of RapidField LLC. +###### Copyright (c) RapidField LLC. All rights reserved. "RapidField" and "Solid Instruments" are trademarks of RapidField LLC. \ No newline at end of file diff --git a/doc/index.md b/doc/index.md index f0e74e75..454e4052 100644 --- a/doc/index.md +++ b/doc/index.md @@ -37,5 +37,6 @@ Copyright (c) RapidField LLC. Licensed under the MIT License. See LICENSE.txt in | Version | Notes | Source | Release date | End of support | | :-------------- | :-----------------------------: | :------------------------------------------------------------------------------: | :----------: | :------------: | +| 1.0.25-preview1 | [Read](releasenotes/v1.0.25.md) | [Explore](https://github.com/RapidField/solid-instruments/tree/v1.0.25-preview1) | 26 Jul 2020 | 31 Jul 2023 | | 1.0.24-preview1 | [Read](releasenotes/v1.0.24.md) | [Explore](https://github.com/RapidField/solid-instruments/tree/v1.0.24-preview1) | 21 Jul 2019 | 31 Jul 2022 | | 1.0.23-preview1 | [Read](releasenotes/v1.0.23.md) | [Explore](https://github.com/RapidField/solid-instruments/tree/v1.0.23-preview1) | 20 Jul 2019 | 31 Jul 2022 | \ No newline at end of file