Hi Martin, (Individual hat on)
On Mon, Jul 3, 2023 at 11:40 PM Martin Thomson <[email protected]> wrote: > draft-ietf-quic-reliable-stream-reset-01 includes a change to make the > Application Protocol Error Code field optional. > > I don't think that this is a necessary change. There are essentially > three cases for stream termination that this draft attempts to unify: > > 1. No data kept. RESET_STREAM covers this. > 2. Some data is kept. This is the new functionality. > 3. All data is kept. STREAM with the FIN bit covers this already. > > The draft rightly observes that the new frame with a retained limit of 0 > is exactly equivalent to RESET_STREAM. That's redundant, but I see no real > harm in it other than the waste implied by sending the new frame type > instead. Changing this new frame so that a 0 could not be encoded would > not save much, but it would make the frame processing more error prone. > > Where things get interesting are those cases where there is no error. The > new version of the draft proposes that abandonment of a stream without > delivering all of the (maybe promised) content is not necessarily an error > condition. > This is pretty much the entire reason motivating this change. > > That's a rather maximal design, covering the above options 1 and 2 in > cases where there is no error (and case 3 where all data is delivered, but > an error occurs anyway). I don't see any way in which this is worth using > a high-value codepoint for. Yes, applications might consider a reduction > in stream length as a useful tool, but - if they do - supplying an error > code for use in signaling that is not a terrible thing. > Supplying an error code introduces an inconsistency in how an application terminates streams, and in fact has already caused confusion in the early discussions of this draft. Specifically, when this draft was initially proposed the framing of it was as a way to "partially" reset a stream, thus the name. The initial implementation, and some of the hesitation, came from the fact that implementers thought about implementing this as a "variant" of a RESET. But once you think long and hard about the problem, you realize it's actually not a RESET. This confusion stems from the fact that a reset is inextricably tied to a RESET. Framing it as a semantic independent from a RESET clarifies the implementation and the usage of the new functionality. It is a way to terminate the send side of a stream. Sure, the motivating usecase (web transport) doesn't care about using this for terminating without an error (partially because of HTTP's particular fixation with using error statuses/codes all the time), but why should we foist that constraint on all other applications? If we had made it so that streams _always_ terminate with an error code, i.e. instead of a FIN bit we had a code, there wouldn't be this inconsistency. Viewed another way -- the new proposed frame is a more generalized way to terminate the send state of stream. The properties of that termination are whether there's an error attached, and an offset. Allowing it to not communicate an error provides a way to accomplish both. It is also more honest about what the frame can be used for -- a constrained partial reliability semantic that is particular to stream termination. If this was framed as a component of stream partial reliability, the mandatory inclusion of an error would seem even stranger. > Probably the most difficult aspect of this change is the API that would be > implied by the use of non-error versions of the new frame. An application > would receive bytes up to (or maybe exceeding) the reliable length, but no > error signal - no indication that the bytes were truncated. I can easily > imagine how that would lead to security problems in applications that were > unprepared for the possibility. I'm not sure I understand the concern here so I think it is worth elaborating. A fairly straightforward API for reading from a QUIC stream is that data is yielded to the application, and when the offset corresponding to the FIN is read, that is communicated and no further data is yielded. With such an API the CLOSE_STREAM without error would be more or less transparent to the application as it would effectively result in the application's view of the data "stopping" being variable. "Truncation" implies it was unexpected, but again, the whole point of not having an error is that sometimes it _is_ expected for this application. For such an application that is communicating a variable amount of bytes on a stream, _after_ those bytes have already been "written", there isn't any truncation. What if the application puts bytes after the "reliable point" which are advisory and/or not critical to the application protocol's functioning? Applications that leverage this would, by definition, have to be able to handle this. Just like today, it is the onus of an application to know how to handle things like RESET, the proper usage has to be specified by the application protocol. I also don't think we should be making inclusion judgments about what deserves or does not deserve a low codepoint. Would it be less objectionable if we had already crossed the two byte codepoint threshold? > > On the other hand, allowing the bytes to be delivered before generating an > error signal is a fairly easy way to signal truncation, without losing > those bytes. In other words, the only safe option is to ensure that an > error signal is always generated when anything less than an entire stream > is delivered. > > Cheers, > Martin > > (Separately, this is the sort of change that really benefits from on-list > discussion. I couldn't find any justification for the change, even in > GitHub: https://github.com/quicwg/reliable-stream-reset/pull/11 appears > without much commentary.) > > Matt Joras
