Change:
This is my attempt to address with something that's (IMO) less clunky in the common case than . I'm not a big fan of this way of doing things either -- specifically, I think the semantics of RendezvousAndSend(), while consistent, are probably wrong.
After some discussion with Brian I agree that the only solution is to provide some initial date to the preconnection when calling initiate. However, why do we need to have two different function. If we only have initiate and provide idempotent data as an optional input parameter that should be fine, right?
NAME your question is now
In commentary on , NAME asks: However, why do we need to have two different function. If we only have initiate and provide idempotent data as an optional input parameter that should be fine, right? The argument here is that you really want to be explicit about the fact that the data you pass to is idempotent, so you have to put it in the name. Another way to do this, though, would be to require a with the property to be passed when message data is passed to on pain of an error.
No, you can also put it in the name of the input parameter of the initiate action: 'Initiate(idempotentData)' Do we even need a messageContext here?
I think both are OK. We've visited this in the past I recall. I favour: InitateWithIdempotentData For exactly the reason you say, because it makes it very explicit. If Apple's API does it different, I would listen to why.... Gorry
now covered by InitiateWithSend(), closing.
How is idempotent data, to be send in the zero-RTT connection establishment, specified? The Use 0-RTT session establishment with an idempotent Message property "specifies whether an application would like to supply a Message to the transport protocol before Connection establishment" which suggests the idempotent message is set on the and used later when the is established, but it might be more natural to specify the idempotent data as a parameter to /?
This also relates to Section 7, which says: This seems incompatible with the / split, since the only way to get a on which to call is by calling , , or , which starts the process of establishing the connection. (Edit: and section 7.1.1.4)
paging NAME to discuss the pros and cons of the Apple and Linux approaches to TFO...
There are three top-level approaches that we’ve used/tried: Mark fast open in parameters (Preconnection) such that connect() (Initiate) becomes a fake action, and you then send after that, and the first send kicks off the real Initiate. Add your initial fast-open data to the parameters (Preconnection), such that it’s already known by the time Initiate is called. Allow marking sends as idempotent, and allow enqueuing sends after create and before start. (1) gets tricky if you want to monitor when connections actually complete, because they’re marked as Connected/Ready too early. This is a bad application contract, especially when there’s racing involved. (2) is not great since it requires an entirely new way of specifying data that isn’t Send(). Neither (1) nor (2) clearly support sending multiple chunks of idempotent data into the stack as fast open data. Thus, we chose (3). However, Preconnection has certainly messed this up a bit. My suggestion is to no longer take Preconnection as an argument to Initiate(), but instead use a NewConnection(Preconnection) -> Initiate() pattern, which you can insert Sends in between. There are many other reasons to do this as well—if I want to set my callbacks for events for this connection, etc, in languages like C that’s a lot easier if I can call create() and then set my event handlers, and then start once those are set. Note: I already removed the text in the architecture that referred to Preconnection being passed to calls like Initiate and Listen, since that was API, not architecture, and since I didn’t quite agree :)
So you create a from a , then call on the ? That would work, provided we're clear when the parameters of that connection lock in (when it's created or when it's initiated)
Yes, the parameters from the Preconnection are immutable once the Connection is created.
Okay, makes sense. I'll put a pull request together to address this (although it'll most likely be tomorrow now)
I created a branch to implement the change to create a from a , then call on the , rather than . However, I'm increasingly unconvinced this is the right approach. The approach: Gives a much clearer split between the phases Makes it clearer what parameters affect connection establishment and which affect ongoing connections (i.e., we don't have to specify the effect of setting connection properties on a connection that has been created but not established) Makes it clearer how works, in particular around resolving candidates Prevents the , , error case, since you can't call on a And, in strongly typed implementations, can prevent errors due to trying to call , , or more than once on a , forgetting to call them, or calling / on an unestablished connection – having taught introductory networking recently, these are all common bugs, and can be made compile time errors if we get the / split right. So, please review the branch, but I don't think we should merge it. Rather, I think the right way to handle idempotent data is to add an optional idempotent to and . Event handlers seem like something that should be specified on initiate, etc., too.
NAME That's not exactly how I imagined the flow being allowed—I don't think we should allow Connection to turn into a Listen or Rendezvous, etc. A listener vends Connections, for example, and isn't a Listen itself. I was thinking that with the current terminology, Preconnection could Create an outbound connection, which could be initiated later. The interaction with listen taking a preconnection would be the same as it is today. Perhaps instead we can just change the terms here, and add an equivalent of a "start"/"resume" for the objects. Thus, we would have: Preconnection.Initiate() -> Connection (not started) Connection.SetEventHandler(...) Connection.Send(Message) Connection.Start() And for listening, Preconnection.Listen() -> Listener (not started) Listener.SetNewConnectionHandler(...) Listener.Start() This is pretty much how our implementation works today.
NAME I really don't like the model of adding many optional parameters to Initiate to handle the various callbacks or extra data that might need to be sent—this list will continually grow and shrink, and most languages don't cleanly support having a single symbol/function/method support an extensible list of parameters. These items also don't belong on the preconnection, since I should be able to have the same Preconnection across many connections to make them look similar, without requiring the same part of the app to handle their events, and to have the same initial data.
NAME hm. from an API expressiveness standpoint, I'm not wild about this. Metacomment: I think we need to meet in person to converge on this. Can we stay with what we have (which gets a little twitchy with more than one Message on 0RTT), keep NAME branch, and defer to post-London?
NAME Sure, I'm fine to defer to converge more. Specifically, what's the issue from the API expressiveness issue? Is it that you'd prefer to have a single call that takes all possible parameters and initial work to schedule? We actually did start with that for our APIs in Objective-C, but moved to the delayed start model to better support languages like C, and to make it cleaner to be clear about which events you want to handle, and who handles them. It allows someone to create a Connection from a Preconnection with certain setup, and then hand it to another part of the app/framework to in turn use it and schedule the event handlers on it. If you require all of that to be fore-known (both the properties of the connection AND the runtime "who gets this callback" state), it's more limiting, since where the events get delivered is not really a fundamental property. This isn't an issue in languages that support an arbitrary number of event watchers without registering, etc, but that's not true across all languages.
I updated the branch, to sketch out how this might look. I don't think it's too bad now.
i should have said elegance instead of expressiveness; it's late. I really don't like the idea, aesthetically, that every connection has a two-phase startup , more or less just to support the (uncommon) case where you want to send more than one idempotent message per RTT. It seems to me we have two overarching design goals (at least, I do): The common case ("I don't care, just send my damn packets") should be as easy or easier to deal with in the transport services API as it is in the sockets API. Code written to the common case should benefit from advanced features in shiny new protocols (e.g. 0RTT) by default if possible. 0RTT is a special snowflake here, because it actually changes the order of operations at startup, and maybe we can't get away from leaking that complexity into the don't care case. But it'd be neat if we could find a way to make send-on-initiate work via a protocol (selection) property anyway.
NAME I opened this as so we can comment on the details of the wording over there.
discussion continues in , propose to defer after -00 and tag this post-London if no objections...
Expanding from my last thought on , here's what I think my current proposal for this is, designed to let application developers not really care all that much about 0RTT but still get benefits from it when available: Setting a protocol selection property that prefers 0RTT-capable transports changes the semantics of and : an will be delayed until either: an initial call, in order to allow idempotent send on startup; or a (fairly aggressively short) timeout, selected such that if the doesn't happen until after the timeout, then the sender is so overloaded or otherwise slow that it wouldn't realize any material speedup from cutting an RTT out of establishment. This allows 0RTT for one message. An application that wants to bundle multiple Messages into a single 0RTT flight can set a new third state on the send parameter (or a new send parameter that is mutually exclusive with , doesn't matter). When is set, a Message will be held until the following Message on send; during 0RTT this will mean (explicitly) that the Initiate should also be delayed until the end of the run. All this behavior can be overridden with an call, or with an parameter on (which would default to false). I can write this up in more detail in an alternate PR (after submitting -00) if y'all don't think it's insane.
That could work for , although I'm not sure I like the invisible change in semantics, but I don't think it works for . The approach in has the advantage of having very similar APIs for zero-RTT data for both and .
Can you walk me through why it doesn't work for ? (Are you somehow callable at the moment?)
I don't think it works for because there's no to call on until the rendezvous has completed, and by then it's too late for zero-RTT. (At home due to a snow day, but Skype me if a call is useful)
(for posterity, I'm going to go off and think about how my suggestion above might work in a non ugly way with Rendezvous, and if I get time before London, write that up in another PR targeted to land in this branch)
For in-person discussion in Montreal; we should turn the suggestions in this issue and in into a message to the list.
NAME and NAME go into the thunderdome first week of June.
this was bikeshedding is now in

Discussion:
Preconnection Object with a Remote Endpoint specifier, optionally a Local Endpoint specifier (if not specified, the system will attempt to determine a suitable Local Endpoint), as well as all properties <del> necessary for candidate selection.  After calling Initiate, no further properties may be added to the Preconnection.  The Initiate() call consumes the Preconnection and creates a Connection Object.  A Preconnection can only be initiated once. </del> <ins> necessary for candidate selection. The Initiate() Action consumes the Preconnection.  Once Initiate() has been called, no further properties may be added to the Preconnection, and no subsequent establishment call may be made on the Preconnection. </ins> Once Initiate is called, the candidate Protocol Stack(s) may cause one or more candidate transport-layer connections to be created to

connections, or the application is prohibited from opening a Connection by the operating system). <ins> See also initiate-and-send to combine Connection establishment and transmission of the first message in a single action. </ins> 6.2. Passive open is the Action of waiting for Connections from remote

invalid to send a Message over a UDP connection that is larger than the available datagram sending size. <del> If Send is called on a Connection which has not yet been established, an Initiate Action will be implicitly performed simultaneously with the Send.  Together with the Idempotent property (see send- idempotent), this can be used to send data during establishment for 0-RTT session resumption on Protocol Stacks that support it. </del> 7.2. Like all Actions in this interface, the Send Action is asynchronous.

7.6. <ins> For application-layer protocols where the Connection initiator also sends the first message, the InitiateWithIdempotentSend() action combines Connection initiation with a first Message sent, provided that message is idempotent. Without a message context (as in send-basic): With a message context (as in message-props): The message passed to InitiateWithIdempotentSend() is, as suggested by the name, considered to be idempotent (see send-idempotent) regardless of declared message properties or defaults.  If protocol stacks supporting 0-RTT establishment with idempotent data are available on the Preconnection, then 0-RTT establishment may be used with the given message when establishing candidate connections.  For a non-idemponent initial message, or when the selected stack(s) do not support 0-RTT establishment, InitiateWithIdempotentSend is identical to Initiate() followed by Send(). Neither partial sends nor send batching are supported by InitiateWithIdempotentSend(). The Events that may be sent after InitiateWithIdempotentSend() are equivalent to those that would be sent by an invocation of Initate() followed immediately by an invocation of Send(), with the caveat that a send failure that occurs because the Connection could not be established will not result in a SendError separate from the InitiateError signaling the failure of Connection establishment. 7.7. </ins> Sender-side framing allows a caller to provide the interface with a function that takes a Message of an appropriate application-layer type and returns an array of octets, the on-the-wire representation