My question is whether it is possible to lock an applet from within the code of the applet itself as a countermeasure to detected manipulations from within the code.
The obvious choice is to use GPSystem.lockCard(); and it works, however I wonder if it is possible to only lock the applet. Also I can lock the applet itself from an authenticated session of the associated security domain. But is it possible from the applet code itself. It seems, given the GPSystem.setCardContentState(); method used with GPSystem.APPLICATION_LOCKED, so I also tested that but it does not work.
Rereading the description of the GP Card specification 2.2 PDF:
The OPEN shall reject any transition request from the Life Cycle State LOCKED;
In my Eclipse, the JavaDoc says:
The OPEN shall reject any transition request to the Life Cycle State LOCKED
What's going on here?
It's interesting to see how this mechanism evolved from GlobalPlatform Card specification 2.1.1 to 2.2.1 (still the same in 2.3):
In GP 2.1.1, only the card issuer (off-card entity) or the OPEN (as a result of "exceptions") is allowed to initiate locking of an application:
The Card Issuer has a mechanism to disable the continued execution status of an on-card Application. This mechanism may be invoked from within the OPEN based on exceptions handled by the OPEN or from the use of externally invoked commands. The Card Issuer is the only entity that may initiate the locking of an Application.
The method GPSystem.setCardContentState() is clearly defined to allow only state changes to application specific life-cycle states (values between 0x07 and 0x7F with the lowest 3 bits set). Since the constant for APPLICATION_LOCKED in later specifications is 0x80 setting this state is not allowed. This is also made clear in the notes to this method:
The OPEN shall reject any transition request to the Life Cycle States INSTALLED or LOCKED.
Consequently, trying to set the application state to locked from within the application itself must fail on a card implementing GP 2.1.1.
UPDATE (2016-05-20): I tested this on a few NXP JCOP cards (that are claimed to comply to GP 2.1.1) and setting values that have the upper bit set or any of the lower 3 bits cleared indeed fails.
This changed in GP 2.2. Now, an application is allowed to lock itself:
The card has a mechanism to disable and subsequently re-enable the continued execution status of an on-card
Application. This mechanism may be invoked from within the OPEN based on exceptions handled by the OPEN or
from the use of externally invoked commands. An Application with Global Lock privilege, the Application itself or
a directly or indirectly associated Security Domain are the only entities that may initiate the locking of an
Application.
The GP Card specification does not require an application to hold any specific permission to lock itself.
Unfortunately, the API specification for the method GPSystem.setCardContentState() is still not quite clear. First, the description of the method still states that only values between 0x07 and 0x7F with the lowest 3 bits set must be allowed:
This method sets the Application specific Life Cycle State of the current applet context. Application specific Life Cycle States range from 0x07 to 0x7F as long as the 3 low order bits are set.
Second, there are deviating notes in the API documentation thats part of appendix A of the GP Card specification 2.2 document and the JavaDoc in the API export files. While the notes in the specification were changed to:
The OPEN shall reject any transition request to the Life Cycle State INSTALLED;
The OPEN shall reject any transition request from the Life Cycle State LOCKED;
The notes in the API export files (GPSystem.java and JavaDoc) remained the same as in GP 2.1.1.
Consequently, if this method was implemented according to the specification, it should still reject setting the application life-cycle state to APPLICATION_LOCKED.
UPDATE (2016-05-20): I tested this on a NXP J3D081 (JCOP v2.4.2 R2) card (that is claimed to comply to GP 2.2). Setting values that have the upper bit set or any of the lower 3 bits cleared, unfortunately, fails.
However, there is also the method GPRegistryEntry.setState(). The documentation of this method states that:
A transition request to Life Cycle state other than APPLICATION_LOCKED and APPLICATION_UNLOCKED shall be accepted only if the invoking Application corresponds to this GPRegistryEntry;
An Application shall be able to lock and shall not be able to unlock itself;
Thus, it would be interesting to see if the following worked on the same card where using setCardContentState() failed:
GPSystem.getRegistryEntry(null).setState(GPSystem.APPLICATION_LOCKED);
UPDATE (2016-05-20): I tested this on a NXP J3D081 (JCOP v2.4.2 R2) card (that is claimed to comply to GP 2.2). Unfortunately, this fails as well. Btw. it does not seem to make a difference if null or JCSystem.getAID() is used as parameter for getRegistryEntry().
UPDATE (2016-06-14): According to Paul Bastian, an NXP representative has confirmed that applications cannot set themselves to locked state on JCOP v2.4.x cards.
UPDATE (2016-06-06): I tested this on a Infineon SLE97CNFX card (that is claimed to comply to GP 2.2.1) and it worked. I could successfully set the state to locked by using APPLICATION_LOCKED (0x80). The state is then set to previous_state | 0x80. Trying to set other state values that have the upper bit set (e.g. 0x8F) does not work (just as I expected).
In GP 2.2.1, the documentation of the method GPSystem.setCardContentState() was changed (again). The change note clearly indicates that the method was updated to now allow an application to lock itself (export file version 1.5. maps to GP 2.2.1):
export file version 1.5: this method now allows the application associated with the current applet context to lock itself.
The method definition was changed to:
This method allows the application associated with the current applet context to change its state to an application specific life cycle state or to lock itself. An application cannot unlock itself using this method.
The value range for the state parameter passed to the method now explicitly includes the value of APPLICATION_LOCKED:
bState - an application specific life cycle state (0x07 to 0x7F with 3 low order bits set), or APPLICATION_LOCKED (0x80).
Consequently, cards implementing GP 2.2.1 or higher should eventually allow applications to change their own life-cycle state to locked using the method GPSystem.setCardContentState().
UPDATE (2016-06-06): I tested this on a Infineon SLE97CNFX card (that is claimed to comply to GP 2.2.1 (or is it 2.3?)) and it worked. I could successfully set the state to locked by using APPLICATION_LOCKED (0x80). The state is then set to previous_state | 0x80. Trying to set other state values that have the upper bit set (e.g. 0x8F) does not work (just as I expected).
An alternative solution
What you could do to overcome your problem without being able to set the application life-cycle to state APPLICATION_LOCKED, is to use application-specific life-cycle states:
public class LockableApplet extends Applet {
[... applet installation / instantiation code ...]
private static final byte APPLICATION_STATE_UNLOCKED = (byte)0x07;
private static final byte APPLICATION_STATE_LOCKED = (byte)0x7F;
public boolean select() {
if (GPSystem.getCardContentState() == APPLICATION_STATE_LOCKED) {
return false;
}
return true;
}
public void process(APDU apdu) {
if (selectingApplet()) {
return;
}
if (GPSystem.getCardContentState() == APPLICATION_STATE_LOCKED) {
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
[... applet logic code ...]
}
}
Upon detecting a problem that should cause your application to be locked, you could lock the applet with the following call:
GPSystem.setCardContentState(APPLICATION_STATE_LOCKED);
You could later unlock the application again using a SET STATUS command through the security domain.
(Buyer beware: It seems this way simply does not work -- see comments)
(In context of GlobalPlatform Card Specification 2.2.1)
You must obey the Application Life Cycle State rules depicted in figure 5-2 (the arrow marked '5' applies here).
The correct way should be:
GPSystem.setCardContentState((byte)(GPSystem.getCardContentState() | GPSystem.APPLICATION_LOCKED));
or
GPSystem.getRegistryEntry(JCSystem.getAID()).setState((byte)(GPSystem.getCardContentState() | GPSystem.APPLICATION_LOCKED))
The 0x80 life cycle state is invalid for an application. See table 11-4 ( at least the b1 and b2 bits must be set, the b3 bit probably as well).
EDIT>
(I confess to write this answer based solely on the remembrance of fact that OPEN keeps the original state from which the entity was locked)
I am quite curious about this so I did some tests using the following applet (excerpt):
public void process(APDU apdu) {
byte[] buffer = apdu.getBuffer();
if(selectingApplet()) {
return;
}
short claIns = Util.getShort(buffer, ISO7816.OFFSET_CLA);
switch(claIns) {
case (short) 0x8007:
buffer[0]=GPSystem.getCardContentState();
if(buffer[0]==buffer[ISO7816.OFFSET_P1]) {
if(GPSystem.setCardContentState(buffer[ISO7816.OFFSET_P2])) {
buffer[1]=0x01;
} else {
buffer[1]=0x00;
}
} else {
buffer[1]=(byte)0xFF;
}
buffer[2]=GPSystem.getCardContentState();
apdu.setOutgoingAndSend((short)0, (short)3);
return;
default: {
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
return;
}
}
}
And the following APDUs:
8007070F03 // To test transition into Application Specific State
80070F8F03 // To test my theory
80070F8003 // To test the GPSystem.APPLICATION_LOCKED constant directly
The results for my set of cards (Gemalto, Morpho, JCOP -- unfortunately all of them are GP 2.1.1) are in line with Michael Roland's great answer and GP specs -- the application's attempt to block itself is refused.
Received response APDUs for all GP 2.1.1 cards:
8007070F03 -> 07010F9000 // Succeeded in transition from `07` to `0F`
80070F8F03 -> 0F000F9000 // Failed transition from `0F` to `8F`
80070F8003 -> 0F000F9000 // Failed transition from `0F` to `80`
Just a note: This tool is quite useful to determine the implemented GP version as it parses the Card Recognition Data.
Yes. It is common and intended operation of a GlobalPlatform application. You must install your application with the right privilege (gp -install -privs CardLock) and the code to do it is:
GPSystem.setCardContentState(GPSystem.APPLICATION_LOCKED);
You can later unlock the application with
gp -unlock-applet <aid>
There is no compliance to GP core spec per se. A product is GP compliant to a GP configuration. GP configuration are not free of charge. JCOP 2.4.x products are compliant to GP 2.2.x 'Mapping Guidelines of Existing GP 2.1.1 Implementation on v2.2.1' configuration. As the name suggests, this configuration is for backward compatibility mapping. Basically JCOP 2.4.x products are GP 2.1.1 compliant products only (with a couple features from GP 2.2.x). Global Lock privilege is optional for applets.
Yes, it is quite simple: use a private static boolean flag and check it in the beginning of the process(APDU apdu) method:
public class MiniApplet extends Applet {
public static void install(byte[] bArray, short bOffset, byte bLength) {
new MiniApplet();
}
protected MiniApplet() {
register();
}
private static final short SW_APPLET_IS_LOCKED = (short) 0x9199; //any error SW
private static boolean appletLocked = false; //static -> faster access, this flag is checked each call! "private" modifier is VERY important!
public void process(APDU apdu) {
if (selectingApplet()) {
return; //it is a good practice not to throw any exceptions on SELECT command
}
if (appletLocked) {
ISOException.throwIt(SW_APPLET_IS_LOCKED);
}
if (attackDetected()) { //implement your attack detection
appletLocked = true;
}
}
}
Related
I am trying to delete diagnostic information but I am strugling to find the configuration in Tresos which would enable ClearDTC interface.
What would be the right way to clear all DTC's internally from the context of SWC?
AUTOSAR Dem component has a standardized Client/Server Interface "ClearDTC", which has two operations:
ClearDTC
SelectDTC
In your authoring tool (not Tresos), you have to add these to your project with your SWC. (They can be found also in the AUTOSAR Blueprints)
You add a new R-Port (e.g. rDemClearDTC) with that PortInterface. In your runnable entity, you should add then for both operations of that interface a SynchronousServerCallPoint.
Finally, in your implementation you should then call these operations over the port by Rte calls.
void MySwc_Runnable(void) {
Std_ReturnType ret;
// sscp_SelectDTC
ret = Rte_Call_rDemClearDTC_SelectDTC(0xFFFFFFu, DEM_DTC_FORMAT_UDS, DEM_DTC_ORIGIN_PRIMARY_MEMORY);
// sscp_ClearDTC
ret = Rte_Call_rDemClearDTC_ClearDTC();
}
You might have to configure also in Dem a second DemClient, because this ClientId should be part of the Tresos-generated Dem ServiceSwComponent as port-defined argument.
The C/S Interface ClearDTC is (according to the Dem SWS) actually only related to ComplexDrivers and not normal ApplicationSwComponents. The question is also, why you would want to call ClearDTC at all from your SWC.
I am new to service fabric and started by looking at the MSDN articles covering the topic. I began by implementing the Hello World sample here.
I changed their original RunAsync implementation to:
var myDictionary = await this.StateManager.GetOrAddAsync<IReliableDictionary<int, DataObject>>("myDictionary");
while (!cancellationToken.IsCancellationRequested)
{
DataObject dataObject;
using (var tx = this.StateManager.CreateTransaction())
{
var result = await myDictionary.TryGetValueAsync(tx, 1);
if (result.HasValue)
dataObject = result.Value;
else
dataObject = new DataObject();
//
dataObject.UpdateDate = DateTime.Now;
//
//ServiceEventSource.Current.ServiceMessage(
// this,
// "Current Counter Value: {0}",
// result.HasValue ? result.Value.ToString() : "Value does not exist.");
await myDictionary.AddOrUpdateAsync(tx, 1, dataObject, ((k, o) => dataObject));
await tx.CommitAsync();
}
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
}
I also introduced a DataObject type and have exposed an UpdateDate property on that type.
[DataContract(Namespace = "http://www.contoso.com")]
public class DataObject
{
[DataMember]
public DateTime UpdateDate { get; set; }
}
When I run the app (F5 in visual studio 2015), a dataObject instance (keyed as 1) is not found in the dictionary so I create one, set UpdateDate, add it to the dictionary and commit the transaction. During the next loop, it finds the dataObject (keyed as 1) and sets UpdateDate, updates the object in the dictionary and commits the transaction. Perfect.
Here's my question. When I stop and restart the service project (F5 in visual studio 2015) I would expect that on my first iteration of the RunAsync that the dataObject (keyed as 1) would be found but it's not. I would expect all state to be flushed to its replica.
Do I have to do anything for the stateful service to flush its internal state to its primary replica?
From what I've read, it makes it sound as though all of this is handled by service fabric and that calling commit (on the transaction) is sufficient. If I locate the primary replica (in Service Fabric Explorer->Application View) I can see that the RemoteReplicator_xxx LastACKProcessedTimeUTC is updated once I commit the transaction (when stepping through).
Any help is greatly appreciated.
Thank you!
-Mark
This is a function of the default local development experience in Visual Studio. If you watch the Output window closely after hitting F5 you'll see a message like this:
The deployment script detects that there's an existing app of the same type and version already registered, so it removes it and deploys the new one. In doing that, the data associated with the old application is removed.
You have a couple of options to deal with this.
In production, you would perform an application upgrade to safely roll out the updated code while maintaining the state. But constantly updating your versions while doing quick iteration on your dev box can be tedious.
An alternative is to flip the project property "Preserve Data on Start" to "Yes". This will automatically bump all versions of the generated application package (without touching the versions in your source) and then perform an app upgrade on your behalf.
Note that because of some of the system checks inherent in the upgrade path, this deployment option is likely to be a bit slower than the default remove-and-replace. However, when you factor in the time it takes to recreate the test data, it's often a wash.
You need to think of a ReliableDictionary as holding collections of objects as opposed to a collection of references. That is, when you add an “object” to the dictionary, you must think that you are handing the object off completely; and you must not alter this object’s state in the anymore. When you ask ReliableDictionary for an “object”, it gives you back a reference to its internal object. The reference is returned for performance reasons and you are free to READ the object’s state. (It would be great if the CLR supported read-only objects but it doesn't.) However, you MUST NOT MODIFY the object’s state (or call any methods that would modify the object’s state) as you would be modifying the internal data structures of the dictionary corrupting its state.
To modify the object’s state, you MUST make a copy of the object pointed to by the returned reference. You can do this by serializing/deserializing the object or by some other means (such as creating a whole new object and copying the old state to the new object). Then, you write the NEW OBJECT into the dictionary. In a future version of Service Fabric, We intend to improve ReliableDictionary’s APIs to make this required pattern of use more discoverable.
I have written an Event Sourced Aggregate and now implemented an Event Sourced Saga... I have noticed the two are similair and created an event sourced object as a base class from which both derive.
I have seen one demo here http://blog.jonathanoliver.com/cqrs-sagas-with-event-sourcing-part-ii-of-ii/ but feel there may be an issue as Commands could be lost in the event of a process crash as the sending of commands is outside the write transaction?
public void Save(ISaga saga)
{
var events = saga.GetUncommittedEvents();
eventStore.Write(new UncommittedEventStream
{
Id = saga.Id,
Type = saga.GetType(),
Events = events,
ExpectedVersion = saga.Version - events.Count
});
foreach (var message in saga.GetUndispatchedMessages())
bus.Send(message); // can be done in different ways
saga.ClearUncommittedEvents();
saga.ClearUndispatchedMessages();
}
Instead I am using Greg Young's EventStore and when I save an EventSourcedObject (either an aggregate or a saga) the sequence is as follows:
Repository gets list of new MutatingEvents.
Writes them to stream.
EventStore fires off new events when streams are written to and committed to the stream.
We listen for the events from the EventStore and handle them in EventHandlers.
I am implementing the two aspects of a saga:
To take in events, which may transition state, which in turn may emit commands.
To have an alarm where at some point in the future (via an external timer service) we can be called back).
Questions
As I understand event handlers should not emit commands (what happens if the command fails?) - but am I OK with the above since the Saga is the actual thing controlling the creation of commands (in reaction to events) via this event proxy, and any failure of Command sending can be handled externally (in the external EventHandler that deals with CommandEmittedFromSaga and resends if the command fails)?
Or do I forget wrapping events and store native Commands and Events in the same stream (intermixed with a base class Message - the Saga would consume both Commands and Events, an Aggregate would only consume Events)?
Any other reference material on the net for implementation of event sourced Sagas? Anything I can sanity check my ideas against?
Some background code is below.
Saga issues a command to Run (wrapped in a CommandEmittedFromSaga event)
Command below is wrapped inside event:
public class CommandEmittedFromSaga : Event
{
public readonly Command Command;
public readonly Identity SagaIdentity;
public readonly Type SagaType;
public CommandEmittedFromSaga(Identity sagaIdentity, Type sagaType, Command command)
{
Command = command;
SagaType = sagaType;
SagaIdentity = sagaIdentity;
}
}
Saga requests a callback at some point in future (AlarmRequestedBySaga event)
Alarm callback request is wrapped onside an event, and will fire back and event to the Saga on or after the requested time:
public class AlarmRequestedBySaga : Event
{
public readonly Event Event;
public readonly DateTime FireOn;
public readonly Identity Identity;
public readonly Type SagaType;
public AlarmRequestedBySaga(Identity identity, Type sagaType, Event #event, DateTime fireOn)
{
Identity = identity;
SagaType = sagaType;
Event = #event;
FireOn = fireOn;
}
}
Alternatively I can store both Commands and Events in the same stream of base type Message
public abstract class EventSourcedSaga
{
protected EventSourcedSaga() { }
protected EventSourcedSaga(Identity id, IEnumerable<Message> messages)
{
Identity = id;
if (messages == null) throw new ArgumentNullException(nameof(messages));
var count = 0;
foreach (var message in messages)
{
var ev = message as Event;
var command = message as Command;
if(ev != null) Transition(ev);
else if(command != null) _messages.Add(command);
else throw new Exception($"Unsupported message type {message.GetType()}");
count++;
}
if (count == 0)
throw new ArgumentException("No messages provided");
// All we need to know is the original number of events this
// entity has had applied at time of construction.
_unmutatedVersion = count;
_constructing = false;
}
readonly IEventDispatchStrategy _dispatcher = new EventDispatchByReflectionStrategy("When");
readonly List<Message> _messages = new List<Message>();
readonly int _unmutatedVersion;
private readonly bool _constructing = true;
public readonly Identity Identity;
public IList<Message> GetMessages()
{
return _messages.ToArray();
}
public void Transition(Event e)
{
_messages.Add(e);
_dispatcher.Dispatch(this, e);
}
protected void SendCommand(Command c)
{
// Don't add a command whilst we are in the constructor. Message
// state transition during construction must not generate new
// commands, as those command will already be in the message list.
if (_constructing) return;
_messages.Add(c);
}
public int UnmutatedVersion() => _unmutatedVersion;
}
I believe the first two questions are the result of a wrong understanding of Process Managers (aka Sagas, see note on terminology at bottom).
Shift your thinking
It seems like you are trying to model it (as I once did) as an inverse aggregate. The problem with that: the "social contract" of an aggregate is that its inputs (commands) can change over time (because systems must be able to change over time), but its outputs (events) cannot. Once written, events are a matter of history and the system must always be able to handle them. With that condition in place, an aggregate can be reliably loaded from an immutable event stream.
If you try to just reverse the inputs and outputs as a process manager implementation, it's output cannot be a matter of record because commands can be deprecated and removed from the system over time. When you try to load a stream with a removed command, it will crash. Therefore a process manager modeled as an inverse aggregate could not be reliably reloaded from an immutable message stream. (Well I'm sure you could devise a way... but is it wise?)
So let's think about implementing a Process Manager by looking at what it replaces. Take for example an employee who manages a process like order fulfillment. The first thing you do for this user is setup a view in the UI for them to look at. The second thing you do is to make buttons in the UI for the user to perform actions in response to what they see on the view. Ex. "This row has PaymentFailed, so I click CancelOrder. This row has PaymentSucceeded and OrderItemOutOfStock, so I click ChangeToBackOrder. This order is Pending and 1 day old, so I click FlagOrderForReview"... and so forth. Once the decision process is well-defined and starts requiring too much of the user's time, you are tasked to automate this process. To automate it, everything else can stay the same (the view, even some of the UI so you can check on it), but the user has changed to be a piece of code.
"Go away or I will replace you with a very small shell script."
The process manager code now periodically reads the view and may issue commands if certain data conditions are present. Essentially, the simplest version of a Process Manager is some code that runs on a timer (e.g. every hour) and depends on particular view(s). That's the place where I would start... with stuff you already have (views/view updaters) and minimal additions (code that runs periodically). Even if you decide later that you need different capability for certain use cases, "Future You" will have a better idea of the specific shortcomings that need addressing.
And this is a great place to remind you of Gall's law and probably also YAGNI.
Any other reference material on the net for implementation of event sourced Sagas? Anything I can sanity check my ideas against?
Good material is hard to find as these concepts have very malleable implementations, and there are diverse examples, many of which are over-engineered for general purposes. However, here are some references that I have used in the answer.
DDD - Evolving Business Processes
DDD/CQRS Google Group (lots of reading material)
Note that the term Saga has a different implication than a Process Manager. A common saga implementation is basically a routing slip with each step and its corresponding failure compensation included on the slip. This depends on each receiver of the routing slip performing what is specified on the routing slip and successfully passing it on to the next hop or performing the failure compensation and routing backward. This may be a bit too optimistic when dealing with multiple systems managed by different groups, so process managers are often used instead. See this SO question for more information.
It seems that actions added to choice pseudo-states are silently ignored. Doing this:
Builder builder = StateMachineBuilder.builder();
StateConfigurer states = builder.configureStates().withStates();
StateMachineTransitionConfigurer transitions = builder.configureTransitions();
StateConfigurer statesConfig = states.initial(INITIAL).states(EnumSet.allOf(StateType.class));
statesConfig.choice(StateType.CHOICE_STATE);
transitions.withChoice().source(StateType.CHOICE_STATE). //
first(StateType.S1, someGuard). //
last(StateType.S2);
states.state(StateType.CHOICE_STATE, someAction, null);
Results in someAction never being executed when CHOICE_STATE is entered.
Adding actions to transitions out of CHOICE_STATE (for example, to S1 or S2 above) is simply not permitted by the framework.
To get around this, we have implemented a state that precedes CHOICE_STATE. We are then free to add actions to this state, as usual. I was just wondering what is the reason for this limitation, or if there is some way of putting actions on a pseudo-state that I may have missed.
That's because choice is a pseudostate which is supposed to be transitient so there should not be behavioural changed in that state itself.
Have you tried to define Action with a Transition which takes you into a choice state?
#Override
public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> transitions) throws Exception {
transitions
.withExternal()
.source(TestStates.S1)
.target(TestStates.S2)
.event(TestEvents.E2)
.action(externalTestAction());
}
#Bean
public Action<TestStates, TestEvents> externalTestAction() {
return new TestAction();
}
Preceding state is a good workaround, especially it that is accompanied with a triggerless transition
I can try to see if in Spring Statemachine we could add feature to a transition(configurer for choice transition) itself. I created a ticket for this https://github.com/spring-projects/spring-statemachine/issues/108.
While we're mostly trying to follow UML model, spec is very vague in most parts and leave a lot of implementation specifics to the implementation itself.
I'd like to be able to implement this in my windsor castle container set up:
"For all types that implement IStartable in the current assembly register them and run the Start method for them."
Similar to what you can do using Autofac for things like registering Automapper mappings. eg
public class MyBlahViewModelMapper : IStartable
{
public void Start()
{
Mapper.CreateMap<MyBlahEntity, MyBlahViewModel>();
}
}
Autofac does it automagically.... I'm thinking Windsor can't help me here?
Windsor has its own IStartable interface. If you want Windsor to register your objects and create/run them immediately after that you'd use Startable Facility for that.
To clarify, there are two concepts here:
IStartable interface, which provides Start and Stop methods. This is a lifecycle interfaces that provide lifecycle callbacks: Start being called right after a component instance gets created (after the constructor runs)
Startable Facility, which forces your IStartable components to be instantiated and started immediately after installers have ran.
Here's what the code would look like:
container.AddFacility<StartableFacility>(f => f.DeferredStart());
container.Install(FromAssembly.This());
// by here all startable are started
If you're on Windsor 3.3 or later you can also manually trigger the startables to start (which is useful if you need to do some extra setup for them)
var flag = new StartFlag();
container.AddFacility<StartableFacility>(f => f.DeferredStart(flag));
container.Install(FromAssembly.This());
// do whatever else set up your app needs
// when ready, signal the flag
flag.Signal();
// by here all startable are started
The closest is Castle Windows Installers - they can trivially scanned from an assembly and installed (or 'started'). Installers are usually used to register components, but they can be used for other initialization as well.
Windsor uses installers (that is types implementing IWindsorInstaller interface) to encapsulate and partition your registration logic .. FromAssembly [makes] working with installers a breeze.
After creating an installer use one of the fluent configurations in the main IoC bootstrap, eg:
container.Install(
FromAssembly.This());
Note that the order is unspecified; installers that must occur in an order must be specified with an explicit order to Install, possibly through a modified assembly reflector.