Prettier TLS State Tables (and Assessing the Cost)

So the spec-driven TLS originally had this state table. On the left are states and on the right is a block of the legal states you can transition to from that state (if any).

Here are the states for a TLS read:

read-proto-states: [
    client-hello [server-hello]
    server-hello [certificate]
    certificate [server-hello-done server-key-exchange]
    server-key-exchange [server-hello-done]
    server-hello-done [#complete]
    finished [change-cipher-spec alert]
    change-cipher-spec [encrypted-handshake]
    encrypted-handshake [application #complete]
    application [application alert #complete]
    alert [#complete]
    close-notify [alert]

It seemed to me this wasn't exploiting the types all that well. We see an ISSUE! being used for #complete. I imagined it would be better to move the issue annotation onto the state itself: If a state was represented by an ISSUE!, then that meant the state could legally be transitioned to the completion state.

So instead of:

encrypted-handshake [application #complete]

That would become:

#encrypted-handshake [#application]

(Since the application state could be terminal as well.)

Now you know just by looking at it that #encrypted-handshake is a potentially-terminal state...and that's everywhere that encrypted-handshake appears. It's not that much more typing, since you have to put an apostrophe on most uses anyway...and it helps the state "stand out".

Plus making the non-terminal states stand out would be good, so I turned them into tags.

I also thought that the blocks looked a bit boring, and I didn't like having to put single elements into blocks just for the convenience of the implementation. I tried adding arrows and letting single elements stand alone on the mapped-to side:

read-proto-states: [
    <client-hello>          -> <server-hello>
    <server-hello>          -> <certificate>
    <certificate>           -> [#server-hello-done <server-key-exchange>]
    <server-key-exchange>   -> #server-hello-done
    <finished>              -> [<change-cipher-spec> #alert]
    <change-cipher-spec>    -> #encrypted-handshake
    #encrypted-handshake    -> #application
    #application            -> [#application #alert]
    #alert                  -> []
    <close-notify>          -> #alert

Of course, this needs to be parsed into a MAP!, so we need some code like:

transitions: make map! []  ; map from states to block of states
state-rule: [tag! | issue!]
uparse transdialect [
    while [
        left: state-rule '-> right: [
            into block! [while state-rule, <input>]
            | collect keep state-rule
        (append transitions :[left right])

Improvement...Or Dialecting For The Sake of Dialecting?

The general question of whether or not to use things like ISSUE! and TAG! for states as opposed to the "cleaner" WORD!s is something that is a bit controversial.

One issue is that there's traditionally a greater cost to comparing strings than there is to comparing words. So state = #encrypted-handshake is slower than state = 'encrypted-handshake. The worst case scenario is equality--because all the characters have to be compared to decide if they are equal.

I'd like to focus more on the qualities of the source representation than worry about the performance however.

There's certainly a point of view where the original wasn't as "noisy". But if you take the extreme opinion of using WORD! for everything, then the code all blurs together.

Parsing Has The Benefit Of Validation

When you stylize your input and do some processing of it to get it into a canonized form, that process of translating your input is a chance to check that the data is what you expect.

Using a generic structure doesn't just keep you from throwing in "superficial" flourishes like ->, but the generality usually goes with accepting anything. Once you have a processing step that puts you in the position of being able to validate...which is a powerful thing.

I Think These Kinds of Things Are "The Point"

I don't know if this particular example is the best example. But I do think that this is what PARSE is meant for, and UPARSE is pushing things to a new level. We should be looking for opportunities to show off this kind of idea, vs just doing what other languages do in a weird way.

1 Like