KStateMachine / kstatemachine

KStateMachine is a powerful Kotlin Multiplatform library with clean DSL syntax for creating complex state machines and statecharts driven by Kotlin Coroutines.
https://kstatemachine.github.io/kstatemachine/
Boost Software License 1.0
358 stars 21 forks source link

Data validation and transformation via events or state #99

Closed Adelrisk closed 4 months ago

Adelrisk commented 5 months ago

I am really impressed with the capabilities of this library, but seem to be running into trouble because of my real-world use-cases and lack of knowledge of this state-machine library.

The use case description leads is also the problem description - the majority of events we expect to be fired will have a command APDU - structured byte arrays. Depending on the internal state of the machine, I would like to evaluate the bytes differently. (At the moment I am hesitant to assume the flags and identifiers are globally unique within the problem space.)

I modelled the initial state as follows, which might lead one of two additional states:

        val notAuthenticated = addInitialState(NotAuthenticated()) {
            dataTransition<CardCommandAPDUEvent, CardCommandAPDU> {
                guard = { event.data.p2 == 0x01u.toUByte() }
                targetState = waitForAcknowledgement
            }
            dataTransition<CardCommandAPDUEvent, CardCommandAPDU> {
                guard = { event.data.p2 == 0x03u.toUByte() }
                targetState = verifyPairing
            }
        }

My problem is, the above guard is actually only a very superficial introspection of the data expected, and not an actual verification of the full byte payload. I see myself implementing the parsing of the payload in the onEntry of the next state, as such:

        val waitForAcknowledgement = addState(WaitForAcknowledgement()) {
            onEntry {
                val body = CardCommandAPDU.getBody(data.data)
                // parse, evaluate, and verify - on fail ... cancel/rollback/trigger new event?
           }
        }

As a consequence, if the payload is not valid, I must then somehow cancel the transition during onEntry by whatever means and "return" to the previous state.

This feels wrong. Is there a better way?

My gut feeling lead me to search for the functionality to convert the data within declared dataTransition-declaration, but I believe the data type of both the data event and the data state have to match.

nsk90 commented 5 months ago

Hello! Nice to see such a use case as I also used to work with smart cards for a while)

1) yes you can call processEvent() from onEntry callback - it is ok and will work normally if you use queuePendingEventHandler() (the default) 2) you can also use Undo method/event to rollback to the previous state. It is also should be working fine. 3) maybe you can change your StateMachine's structure little bit and use transitionConditionally method for your CardCommandAPDUEvent transition. This method is more powerful and it allows you to choose the targetState dynamically or even select not to trigger transition at all (see noTransition())

So I don't feel that you are doing something wrong, seems that all cases are fine and you are free to select the best option for you needs. For me it looks simpler to navigate to some state only when you have made all the necessary parsing and checks as there would be no need in rollbacks on errors, but maybe it is superficial conclusion.

Adelrisk commented 5 months ago

For me it looks simpler to navigate to some state only when you have made all the necessary parsing and checks as there would be no need in rollbacks on errors, but maybe it is superficial conclusion.

That's right, the need for rollback/cancel/whatever-technically-available is code smell I feel uncomfortable with. The problem lies not in the use-case, but with me poorly matching the tool to the job. Thanks for filling in the picture! I think I got caught up in the simplicity of using dataTransition calls, but they are (obviously) side-effect free.

Can transitionConditionally also inject data into the state from the event? Similar to the type-safe dataTransition? (I'm also interested in non-type-safe solutions.)

nsk90 commented 5 months ago

yes transitionConditionally is not so type-safe as dataTransition but it will do all the job correctly.

Adelrisk commented 4 months ago

Thank you for your help! I've iterated over both the technical possibilities and (not-so-?)desirable modellings, and can confirm that the supported transitions fit my problem.

A side note: I also found the hidden specifications of the command APDUs, so I now pre-process all incoming APDUs and convert them into data value classes representing the event, and submit those to the state machine. This eliminates the interpretation of array bytes/APDU values within the transitions or entry conditions themselves.