Re: Report on Unidirectional Streams in Minq

Eric Rescorla <ekr@rtfm.com> Mon, 02 October 2017 14:52 UTC

Return-Path: <ekr@rtfm.com>
X-Original-To: quic@ietfa.amsl.com
Delivered-To: quic@ietfa.amsl.com
Received: from localhost (localhost [127.0.0.1]) by ietfa.amsl.com (Postfix) with ESMTP id 3FCB4134681 for <quic@ietfa.amsl.com>; Mon, 2 Oct 2017 07:52:55 -0700 (PDT)
X-Virus-Scanned: amavisd-new at amsl.com
X-Spam-Flag: NO
X-Spam-Score: -0.609
X-Spam-Level:
X-Spam-Status: No, score=-0.609 tagged_above=-999 required=5 tests=[BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, HTML_MESSAGE=0.001, HTTPS_HTTP_MISMATCH=1.989, RCVD_IN_DNSWL_LOW=-0.7, URIBL_BLOCKED=0.001] autolearn=ham autolearn_force=no
Authentication-Results: ietfa.amsl.com (amavisd-new); dkim=pass (2048-bit key) header.d=rtfm-com.20150623.gappssmtp.com
Received: from mail.ietf.org ([4.31.198.44]) by localhost (ietfa.amsl.com [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id uQiZULl0pbkw for <quic@ietfa.amsl.com>; Mon, 2 Oct 2017 07:52:51 -0700 (PDT)
Received: from mail-yw0-x22b.google.com (mail-yw0-x22b.google.com [IPv6:2607:f8b0:4002:c05::22b]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by ietfa.amsl.com (Postfix) with ESMTPS id A81A1134483 for <quic@ietf.org>; Mon, 2 Oct 2017 07:52:50 -0700 (PDT)
Received: by mail-yw0-x22b.google.com with SMTP id w9so3822392ywi.11 for <quic@ietf.org>; Mon, 02 Oct 2017 07:52:50 -0700 (PDT)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=rtfm-com.20150623.gappssmtp.com; s=20150623; h=mime-version:in-reply-to:references:from:date:message-id:subject:to :cc; bh=MT+4YCAaLP6qbHghnbJMrUd3NCGUg09q+FWrtuL/x2A=; b=n73Cg73P6+FC/E8Fa6P4fYYu6vLRMviglojrwYpIYflYnVbnQJw9IAEjZx0RJ6ZiBB /lbFaFzNBz+QLqZQnu7CnlHVI5gZlpYu/sqCftGwYG9snvDGz4RCgQoHelJYSYgFHf0r /HsJa5cYqApUEE8CPjIkXBTdmPt8MNSeVG9nPyHzn1Mth9u4pVFRHS7AIeEQAzTShrQR PHpWm49WjF+WjUpuCfBQ9Q1vmqt4bAJrrr2PuYFycjeiYjprpJJSS/8tTX8uxxBKiFTO YZ6gvb6m7rpYrqOaXBEcjTzMXMfWdZHyDLsq+7pitRIY6KDmX2wA/wQL/dxEN1CLA3kr V8hg==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:mime-version:in-reply-to:references:from:date :message-id:subject:to:cc; bh=MT+4YCAaLP6qbHghnbJMrUd3NCGUg09q+FWrtuL/x2A=; b=Y1qbY07Al6CqmzdIRmwcmCqKaBSGxZGBeaKAbtRK4NJOsphQ/opHkoB3BarMFJ89zc hlW8MuBOpPBUsGem2RukW/CPTpCsXsxpxjqvpS4zMXmN6nD/d0DEqjMqJauM6QjyP9pM IzEK9um5SYQzDa9dcky1HbpvJpweH8KlA20RygJChsJB5u9j03Ykc1RUGc/MCbDORX6s zTcaVdkPGElWfzN7Jgjjtq4biWp38/z6ZTweD8W+7c3UGWWxGM2k9Hcow5B0qvsY2hDI 9KGHcWiHbFQyz8JsNOiFg8WkxE82oHbpmFgn9iCrzo+KsncJVH7nkSvKxOwripB6Jxt0 gOPg==
X-Gm-Message-State: AMCzsaX+A6+WCmBeNP/j9wwnL9hRijNXrspQneKKaOoDLpCsKcvGaQNP he0U1zJY8mksgtDt36oOL34Z9fz5C8N8AdaimWly5Q==
X-Google-Smtp-Source: AOwi7QAWUXjrwjtSSPcrE1pOq4trI9gqJJ/ThPo5ZBSLowi0hSgWtiXvduIhFXGj8grShH2p8c0/9K3f8MlTNCAfRlE=
X-Received: by 10.129.85.150 with SMTP id j144mr431190ywb.161.1506955969797; Mon, 02 Oct 2017 07:52:49 -0700 (PDT)
MIME-Version: 1.0
Received: by 10.129.75.194 with HTTP; Mon, 2 Oct 2017 07:52:09 -0700 (PDT)
In-Reply-To: <bb3767d16e0a4ff28667907defe909f5@usma1ex-dag1mb5.msg.corp.akamai.com>
References: <CABcZeBMS5U6u=O+K_rx=JPXoN8cwNAo-raQLSrf=g9A7z6SE9A@mail.gmail.com> <CAN1APdfaGmHUHSVOWOSaiM5TB7-tNn22EHt46_5t4RByM_QFXw@mail.gmail.com> <CABcZeBM73ikZrszEBoLxbqtoOcc5WQpwM541ii0Jvu5wmywKOw@mail.gmail.com> <MWHPR21MB01417BDA5D8978FCAE6BDFBA877C0@MWHPR21MB0141.namprd21.prod.outlook.com> <CABcZeBONx0mTbhUYKxQujVDDFaHYcJqY_vdz3n9W5bPzQqPtXw@mail.gmail.com> <bb3767d16e0a4ff28667907defe909f5@usma1ex-dag1mb5.msg.corp.akamai.com>
From: Eric Rescorla <ekr@rtfm.com>
Date: Mon, 2 Oct 2017 07:52:09 -0700
Message-ID: <CABcZeBOkimH+qKVJQmFExA+3Lh3HNujWg1d2mWG6_PV1bZQrhQ@mail.gmail.com>
Subject: Re: Report on Unidirectional Streams in Minq
To: "Lubashev, Igor" <ilubashe@akamai.com>
Cc: "Michael.Bishop@microsoft.com" <Michael.Bishop@microsoft.com>, "quic@ietf.org" <quic@ietf.org>, "mikkelfj@gmail.com" <mikkelfj@gmail.com>
Content-Type: multipart/alternative; boundary="001a113f176645ce75055a91875f"
Archived-At: <https://mailarchive.ietf.org/arch/msg/quic/mKwwr-AW85qsQOh6PuOx9bFG_8w>
X-BeenThere: quic@ietf.org
X-Mailman-Version: 2.1.22
Precedence: list
List-Id: Main mailing list of the IETF QUIC working group <quic.ietf.org>
List-Unsubscribe: <https://www.ietf.org/mailman/options/quic>, <mailto:quic-request@ietf.org?subject=unsubscribe>
List-Archive: <https://mailarchive.ietf.org/arch/browse/quic/>
List-Post: <mailto:quic@ietf.org>
List-Help: <mailto:quic-request@ietf.org?subject=help>
List-Subscribe: <https://www.ietf.org/mailman/listinfo/quic>, <mailto:quic-request@ietf.org?subject=subscribe>
X-List-Received-Date: Mon, 02 Oct 2017 14:52:55 -0000

On Mon, Oct 2, 2017 at 4:27 AM, Lubashev, Igor <ilubashe@akamai.com> wrote:

> > Nit: Can't you send RESET_STREAM
>
> I do not think this is an option, since RESET_STREAM may prevent data
> buffered by the transport from being delivered to the application.
>


Those are also the semantics of STREAM(offset=0, FIN).

-Ekr



I am less concerned about how long connection state has to be kept around
> -- in a cooperating peer situation, this is one rtt, which is the same
> amount of time that is required to wait for an ACK. A malicious peer could
> withhold ACKs for packets with STREAM+FIN. The bidirectional protocol is
> more chatty, though.
>


>
> -----Original Message-----
> *From:* Eric Rescorla [ekr@rtfm.com]
> *Received:* Sunday, 01 Oct 2017, 5:29PM
> *To:* Mike Bishop [Michael.Bishop@microsoft.com]
> *CC:* Mikkel Fahnøe Jørgensen [mikkelfj@gmail.com]; IETF QUIC WG [
> quic@ietf.org]
> *Subject:* Re: Report on Unidirectional Streams in Minq
>
>
>
> On Sun, Oct 1, 2017 at 2:19 PM, Mike Bishop <Michael.Bishop@microsoft.com>
> wrote:
>
>> I think the difference is for the scenario where you don’t expect a peer
>> to respond (or don’t expect them to respond stream-by-stream).  With -05,
>> the receiver still needs to send STREAM(offset=0,FIN) on each stream.
>>
>
> Nit: Can't you send RESET_STREAM?
>
>
>
>>   With either unidirectional proposal, this pattern is a bit cleaner.  In
>> #656, the sender can essentially say, “Don’t bother” at the time of stream
>> creation.  In #643, there’s no corresponding channel to close.
>>
>
> Yes, I agree that this is cleaner. Note that in #643 you presumably need
> to have some way of signaling that you don't want a return connection....
>
>
>
> In all cases, the only thing that really limits you is the peer’s
>> willingness to increase your MAX_STREAM_ID, it’s just a question of how
>> chatty you have to be – which can matter if these messages are fairly small.
>>
>
> Agreed.
>
> -Ekr
>
>
>>
>>
>> *From:* QUIC [mailto:quic-bounces@ietf.org] *On Behalf Of *Eric Rescorla
>> *Sent:* Sunday, October 1, 2017 2:03 PM
>> *To:* Mikkel Fahnøe Jørgensen <mikkelfj@gmail.com>
>> *Cc:* IETF QUIC WG <quic@ietf.org>
>> *Subject:* Re: Report on Unidirectional Streams in Minq
>>
>>
>>
>> I'm not sure I have any useful insights on this, as my implementation
>> handles them more or less the same. Can you explain why you think this
>> would be different with unidirectional versus bidirectional?
>>
>>
>>
>> -Ekr
>>
>>
>>
>>
>>
>> On Sun, Oct 1, 2017 at 1:38 PM, Mikkel Fahnøe Jørgensen <
>> mikkelfj@gmail.com> wrote:
>>
>> Thanks,
>>
>>
>>
>> I only skimmed this superfast, but one the observations appear to not
>> cover one of my key points:
>>
>>
>>
>> You can create and close uni-streams at at very high rate of many streams
>> per packet, without waiting for peer response, assuming the ACK framework
>> handles retransmission. Any insights on this?
>>
>>
>>
>> Kind Regards,
>>
>> Mikkel Fahnøe Jørgensen
>>
>>
>>
>> On 1 October 2017 at 22.32.02, Eric Rescorla (ekr@rtfm.com) wrote:
>>
>> Hi folks,
>>
>>
>>
>> As promised I spent a bunch of time hacking unidirectional streams
>>
>> into Minq and I'm here to report back [0]. Specifically, I
>>
>> implemented:
>>
>>
>>
>>   - PR#643 -- Unidirectional Streams
>>
>>   - PR#720 -- Add bidirectional streams on top of unidirectional
>>
>>   - A bidirectional stream API that mostly mimics Minq's original API
>>
>>     for -05.
>>
>>
>>
>> The following is kind of a wall of text, so you could also skip this
>>
>> and refer to my slides [1].
>>
>>
>>
>>
>>
>> MINQ'S -05 ARCHITECTURE
>>
>> API
>>
>> Minq's master object is the Connection, which has a list of Streams
>>
>> indexed by stream ID. Applications register a handler with the
>>
>> connection to be notified of new events.
>>
>>
>>
>>    type ConnectionHandler interface {
>>
>>     // The connection has changed state to state |s|
>>
>>     StateChanged(s State)
>>
>>
>>
>>     // A new stream has been created (by receiving a frame
>>
>>     // from the other side. |s| contains the stream.
>>
>>     NewStream(s *Stream)
>>
>>
>>
>>     // Stream |s| is now readable.
>>
>>     StreamReadable(s *Stream)
>>
>>    }
>>
>>
>>
>> Streams get created in two ways:
>>
>>
>>
>> - Locally via Connection.CreateStream()
>>
>> - Remotely, in which case the application is notified via a callback to
>>
>>   ConnectionHandler.NewStream().
>>
>>
>>
>> Streams themselves have the APIs you would expect, namely, Read(),
>>
>> Write(), Close(), Reset(), etc. You get notified of stream readability
>>
>> by a callback to ConnectionHandler.StreamReadable(), at which point
>>
>> you can do Stream.Read(). As expected, Stream.Read() returns
>>
>> WOULDBLOCK when no data ia available
>>
>>
>>
>>
>>
>> INTERNALS
>>
>> As noted above, we start with the Connection object (only relevant fields
>>
>> shown):
>>
>>
>>
>>    type Connection struct {
>>
>>     handler          ConnectionHandler
>>
>>     streams          []*Stream
>>
>>     maxStream        uint32
>>
>> outputClearQ     []frame // For stream 0
>>
>> outputProtectedQ []frame // For stream >= 0
>>
>>    }
>>
>>
>>
>> As you can see, streams are in an array slice, so they're contiguously
>>
>> indexed by stream ID. Right now, I have no provision for reclaiming
>>
>> the unused bottom part of the array, but it would be straightforward
>>
>> to index by |streamId| - |minStream|, which I think is consistent with
>>
>> the design implied by the requirement to create streams in sequence.
>>
>>
>>
>> Because Streams are bidirectional, each stream actually consists of a
>>
>> pair of half streams.
>>
>>
>>
>>    type streamHalf struct {
>>
>>     s             *stream           // pointer to parent
>>
>>     log           loggingFunction
>>
>>     dir           direction         // Sending or receiving
>>
>>     closed        bool              // Is the half-stream closed
>>
>>     offset        uint64            // The
>>
>>     chunks        []streamChunk
>>
>>     maxStreamData uint64
>>
>>    }
>>
>>
>>
>>    // Internal object to allow unit testing.
>>
>>    type stream struct {
>>
>>     id         uint32
>>
>>     log        loggingFunction
>>
>>     state      streamState
>>
>>     send, recv *streamHalf
>>
>>   blocked    bool // Have we returned blocked
>>
>>    }
>>
>>
>>
>>    // Public API object, which needs access to the Connection
>>
>>    type Stream struct {
>>
>>     c *Connection
>>
>>     stream
>>
>>    }
>>
>>
>>
>> Outgoing data is enqueued into |stream.chunks|, and then periodically
>>
>> the connection polls the stream for all the chunks which are permitted
>>
>> by stream-level flow control and enqueues them into
>>
>> |Connection.outputClearQ| or |Connection.outputProtectedQ|. At this
>>
>> point, the connection owns the data and is responsible for
>>
>> transmitting it, subject to connection-level flow control, and
>>
>> (presumably) congestion control once I have that implemented [2].
>>
>>
>>
>> Incoming data gets queued (sorted) into |stream.chunks| for later
>>
>> reassembly at the time when someone calls stream.read(). I'm not sure
>>
>> I love this, because it means I don't have a good view of the incoming
>>
>> queue size (which I'd need to account for separately), but it allowed
>>
>> me to share data structures between incoming and outgoing, which
>>
>> seemed kind of natural when I did it (this architecture is replicated
>>
>> in the unidirectional streams design, but it's probably less natural
>>
>> there).
>>
>>
>>
>>
>>
>> UNIDIRECTIONAL STREAMS ARCHITECTURE
>>
>> UNIDIRECTIONAL API
>>
>> With the unidirectional branch, Minq offers two APIs. The first is a
>>
>> straightforward mapping of PR#720, in which we have two objects:
>>
>>
>>
>>   SendStream -- used for writing
>>
>>   RecvStream -- used for reading
>>
>>
>>
>> As before, we have a handler object, but it's directional now:
>>
>>
>>
>>    type ConnectionHandler interface {
>>
>>     // The connection has changed state to state |s|
>>
>>     StateChanged(s State)
>>
>>
>>
>>     // A new receiving stream has been created (by receiving a frame
>>
>>     // from the other side. |s| contains the stream.
>>
>>     NewRecvStream(s *RecvStream)
>>
>>
>>
>>     // Stream |s| is now readable.
>>
>>     StreamReadable(s *RecvStream)
>>
>>    }
>>
>>
>>
>> Obviously SendStreams are locally created and RecvStreams are remotely
>>
>> created. SendStreams can be created using
>>
>> Connection.CreateSendStream() and you learn about remotely created
>>
>> RecvStreams by a callback to ConnectionHandler.NewRecvStream().
>>
>> Second-created streams can be marked as "related" to a single
>>
>> first-created stream, using Connection.CreateRelatedSendStream() with
>>
>> the appropriate RecvStream as the argument [3]. Streams have a
>>
>> Related() API to tell you if they are related to some other stream.
>>
>>
>>
>> The {Send,Recv}Stream APIs are about what you'd expect. You can
>>
>> Write() on SendStream and Read() on RecvStream(). Right now, you can
>>
>> Close() and Reset() SendStreams, but not do anything on RecvStreams()
>>
>> or than ignore them. Eventually I'll probably offer
>>
>> RecvStream().Mute() or something to let you send STOP_SENDING.
>>
>>
>>
>>
>>
>> BIDIRECTIONAL API
>>
>> Minq also includes a bidirectional API that's layered on top of
>>
>> unidirectional streams. I've created a Connection2 structure that's
>>
>> intended as a wrapper around Connection [4]:
>>
>>
>>
>>    type Connection2 struct {
>>
>>     Connection
>>
>>     shim    *connection2ShimHandler
>>
>>     streams []*Stream // Odd for client originated, even for server.
>>
>>    }
>>
>>
>>
>>    type Stream struct {
>>
>>     id   uint32
>>
>>     send *SendStream
>>
>>     recv *RecvStream
>>
>>    }
>>
>>
>>
>> Basically, Stream is just a pair of SendStream and RecvStream and
>>
>> Connection2 does the bookkeeping to keep them connected (I even do the
>>
>> odd/even ID thing that QUIC-05 has). Connection2 has the same handler
>>
>> API as Minq for QUIC-05, and the shim is responsible for translating
>>
>> unidirectional events into bidirectional events.
>>
>>
>>
>> Internally, what's going on here is that when you call CreateStream()
>>
>> Minq creates a Stream with a nil RecvStream. When a new remote stream
>>
>> is detected, we check RecvStream.Related(). If they are related to an
>>
>> existing SendStream then we will in the relevant |Stream.send| slot.
>>
>> Otherwise, we create a new SendStream that's related to the incoming
>>
>> stream and notify the application of the creation of the new
>>
>> bidirectional stream.
>>
>>
>>
>> Note that this all works fine if one side does the undirectional API
>>
>> and one does the bidirectional API. My test programs actually exercise
>>
>> this. Of course, there's an assumption that the peer conforms to a 1:1
>>
>> mapping. If we define unidirectional streams, we'll need some protocol
>>
>> mechanism to know if the other side is exercising this level of
>>
>> increased flexibility or not. I can imagine a number of options here
>>
>> (e.g., ALPN).  We could also forbid 1:N mappings but I think that
>>
>> would be a mistake as it's a cool feature/benefit of doing
>>
>> unidirectional.
>>
>>
>>
>> The entire bidirectional wrapper shim is < 150 lines of Go code.
>>
>> (https://github.com/ekr/minq/blob/unidirectional_streams/bidi.go
>> <https://urldefense.proofpoint.com/v2/url?u=https-3A__na01.safelinks.protection.outlook.com_-3Furl-3Dhttps-253A-252F-252Fgithub.com-252Fekr-252Fminq-252Fblob-252Funidirectional-5Fstreams-252Fbidi.go-26data-3D02-257C01-257Cmichael.bishop-2540microsoft.com-257C14f07987da0d4afb8e7508d5090ff6f6-257C72f988bf86f141af91ab2d7cd011db47-257C1-257C0-257C636424886546481710-26sdata-3Dwps9AWK2faa83N9R-252BDHovig-252Bwf684PS-252B5Qw5DgzYH5I-253D-26reserved-3D0&d=DwMFaQ&c=96ZbZZcaMF4w0F4jpN6LZg&r=Djn3bQ5uNJDPM_2skfL3rW1tzcIxyjUZdn_m55KPmlo&m=0Cj04Ejsj2Z08LIZaQVg8JdJLbfq-we89C1CB9Ck5UI&s=7O3v5JhTQZdRv5ANEc5hWUErnatei46z-rMJsJb4wRo&e=>
>> ).
>>
>> Converting my test application to this API was a matter of just
>>
>> changing class names, e.g., s/Connectionb/Connection2/.
>>
>>
>>
>> In my implementation, I assume that at the time Minq hears about a
>>
>> stream, you know if its related to a given existing stream.  That way
>>
>> you can immediately either associate it with that stream or make a new
>>
>> local stream. In my implementation, I always send Related Stream Id
>>
>> and assume that you never get an "unrelated" stream frame before a
>>
>> "related" one. The spec doesn't really require that right now, and
>>
>> offline MT suggested just sending related with offset=0 but I think
>>
>> that's a mistake, because it means that you need to hold stream frames
>>
>> in some provisional "undetermined" state until you get that frame. I
>>
>> would suggest instead that we require that you include the field until
>>
>> one of the frames is ACKed.
>>
>>
>>
>>
>>
>> INTERNALS
>>
>> Sending and receiving streams still share a lot of common components:
>>
>>
>>
>>     type baseStream struct {
>>
>>     state         streamState
>>
>>     id            uint32
>>
>>     log           loggingFunction
>>
>>     offset        uint64
>>
>>     chunks        []streamChunk
>>
>>     maxStreamData uint64
>>
>>     isRelated     bool
>>
>>     related       uint32
>>
>>     }
>>
>>
>>
>>     type sendStream struct {
>>
>>     baseStream
>>
>>     blocked bool
>>
>>     }
>>
>>
>>
>>     type recvStream struct {
>>
>>     baseStream
>>
>>     }
>>
>>
>>
>> Most of this is the same as with bidirectional streams.  As above,
>>
>> it's probably possible to make them more asymmetrical.
>>
>>
>>
>> The connection maintains separate lists of sending and receiving
>>
>> streams and it's straightforward to create and access them without
>>
>> worrying about the odd/even stuff.
>>
>>
>>
>>
>>
>> COMPARISON
>>
>> At the end of the day, I think this shows that these designs aren't
>>
>> really that disssimilar. I was able to convert Minq to unidirectional
>>
>> streams in about 16 total hours of work (basically a long plane flight
>>
>> plus the next morning). While I had to make a bunch of changes to the
>>
>> internal structures, basically none of them modified anything tricky,
>>
>> and in particular the flow control mechanics and the like are
>>
>> basically unchanged, except for a bunch of mechanical-type
>>
>> transformations like referring to |sendStream.chunks| instead of
>>
>> |stream.send.chunks|. The only really new protocol machinery is the
>>
>> new frame format for related streams.
>>
>>
>>
>> There are a few pros/cons that are worth noting about these designs.
>>
>>
>>
>> - Without a bidirectional API, having unidirectional streams is more
>>
>>   work for the programmer. However, with an API shim, the difference
>>
>>   is trivial.
>>
>>
>>
>> - Because undirectional streams are inherently more flexible, it's
>>
>>   possible for the sides to try to use different mappings, e.g., one
>>
>>   side expects paired and the other expects 1:N. We'll need some way
>>
>>   of making sure that doesn't happen, maybe ALPN?
>>
>>
>>
>> - The "Related Stream ID" frame indicator needs fleshing out a bit.
>>
>>   From the application's perspective, it should never hear about a
>>
>>   stream without knowing its related status. And as noted above, I
>>
>>   think it would be best if we required that all "first flight" stream
>>
>>   frames that are related include the fied
>>
>>
>>
>> - Undirectional streams kind of sharpen the confusion about exactly
>>
>>   what kinds of "closure" we want to allow. Specifically, what should
>>
>>   implementations be able to say about their willingness to receive?
>>
>>   Right now we have STOP_SENDING, but that doesn't influence the
>>
>>   sender's state. I don't think undirectional streams make this worse,
>>
>>   they just require us to think it through some more. They do simplify
>>
>>   the implementation of the closure state machine: in my QUIC-05 code,
>>
>>   whenever one side closes I have to have checks to see if I should be
>>
>>   transitioning to CLOSED or HALF-CLOSED, etc, which is odd because
>>
>>   the directions are basically independent.  It would probably be
>>
>>   easier even in QUIC-05 not to reify these states but just to
>>
>>   determine the state from the composition of the individual
>>
>>   sub-states
>>
>>
>>
>> - Unidirectional streams don't need the kind of annoying odd-even
>>
>>   mechanics, which was easier to code up (just having to create all
>>
>>   the lower-numbered streams of the same parity is kind of a pain).
>>
>>   One additional benefit here is that with QUIC-05 there are several
>>
>>   messages which involve implicit stream creation (e.g.,
>>
>>   STREAM_MAX_DATA, and RST_STREAM) and so you need to check whether
>>
>>   the stream is one that should have been created locally or
>>
>>   remotely. This just doesn't happen with bidirectional streams; I do
>>
>>   implement odd/even mechanics but because the other side has to have
>>
>>   its stream ids increment by one, I can make sure that they have the
>>
>>   right IDs by construction and just check to see if the stream exists
>>
>>   in these cases.  I'd like to see us get rid of odd/even no matter
>>
>>   what.
>>
>>
>>
>> - Unidirectional streams also helps avoid some of the corner cases around
>>
>>   bidirectional streams. Specifically, suppose I am the client and
>>
>>   I get MAX_STREAM_DATA as the first frame on stream 2. Am I allowed
>>
>>   to just start sending or not? You can sort of get into this situation
>>
>>   with unidirectional streams, but because it's explicit, one might
>>
>>   hope that the application semantics would require clear specification.
>>
>>
>>
>> - As noted above, bidirectional streams are more flexible because they
>>
>>   let you have mappings that you can't have with unidirectional
>>
>>   streams (unpaired, 1:N).
>>
>>
>>
>>
>>
>> Happy to answer more questions if people have them. Otherwise we can
>>
>> talk about this in Seattle.
>>
>>
>>
>> -Ekr
>>
>>
>>
>>
>>
>> [0] https://github.com/ekr/minq/tree/unidirectional_streams
>> <https://urldefense.proofpoint.com/v2/url?u=https-3A__na01.safelinks.protection.outlook.com_-3Furl-3Dhttps-253A-252F-252Fgithub.com-252Fekr-252Fminq-252Ftree-252Funidirectional-5Fstreams-26data-3D02-257C01-257Cmichael.bishop-2540microsoft.com-257C14f07987da0d4afb8e7508d5090ff6f6-257C72f988bf86f141af91ab2d7cd011db47-257C1-257C0-257C636424886546481710-26sdata-3DxSeSXYG4OC6iTZhicVgH6qJTNrZEQhaUBc0XRsxiypA-253D-26reserved-3D0&d=DwMFaQ&c=96ZbZZcaMF4w0F4jpN6LZg&r=Djn3bQ5uNJDPM_2skfL3rW1tzcIxyjUZdn_m55KPmlo&m=0Cj04Ejsj2Z08LIZaQVg8JdJLbfq-we89C1CB9Ck5UI&s=BPMv_u6PoAUyNQT6qRBo91s_Pkhbape3JbKe6qdSgoU&e=>
>>
>> [1] https://github.com/ekr/wg-materials/blob/404898fa2d2f0a9f9bd
>> 244d2c945e66ea88502a2/interim-17-10/Unidirectional%
>> 20Streams%20in%20Minq.pdf
>> <https://urldefense.proofpoint.com/v2/url?u=https-3A__na01.safelinks.protection.outlook.com_-3Furl-3Dhttps-253A-252F-252Fgithub.com-252Fekr-252Fwg-2Dmaterials-252Fblob-252F404898fa2d2f0a9f9bd244d2c945e66ea88502a2-252Finterim-2D17-2D10-252FUnidirectional-252520Streams-252520in-252520Minq.pdf-26data-3D02-257C01-257Cmichael.bishop-2540microsoft.com-257C14f07987da0d4afb8e7508d5090ff6f6-257C72f988bf86f141af91ab2d7cd011db47-257C1-257C1-257C636424886546481710-26sdata-3DZ-252BINOkAAvbXwY7YuwaPj0f1yRXY7S79AkD-252FvHGpwLoQ-253D-26reserved-3D0&d=DwMFaQ&c=96ZbZZcaMF4w0F4jpN6LZg&r=Djn3bQ5uNJDPM_2skfL3rW1tzcIxyjUZdn_m55KPmlo&m=0Cj04Ejsj2Z08LIZaQVg8JdJLbfq-we89C1CB9Ck5UI&s=UJUU41uCwH4JKTdNyZS-W60jzn7htulXzFuEWmojhuI&e=>
>>
>> [2] Thanks to Patrick McManus for suggesting this design.
>>
>> [3] In C++, you would have a single function with a default argument of
>>
>>     |nullptr| but Go doesn't support that, hence two different arguments.
>>
>> [4] For implementation reasons, it's actually using Connection as a mixin,
>>
>>     which means it's simultaneously possible to use the unidirectional
>>
>>     and bidirectional APIs, but that's going to cause a lot of confusion.
>>
>>     A real implementation would probably have to either commit to one
>>
>>     or the other or do a real wrapper, so you could only use one set of
>>
>>     APIs, at the cost of having to do more forwarded methods.
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>
>