Jens Geyer created THRIFT-6165:
----------------------------------

             Summary: Java: bind the read budget to the frame that carries the 
message
                 Key: THRIFT-6165
                 URL: https://issues.apache.org/jira/browse/THRIFT-6165
             Project: Thrift
          Issue Type: Bug
          Components: Java - Library
            Reporter: Jens Geyer
             Fix For: 0.25.0


h3. Problem

{{TFramedTransport}} and {{TFastFramedTransport}} read a frame header that 
tells them the exact
size of the message they are about to hand to the protocol, and never tell the 
budget.
{{checkReadBytesAvailable}} is therefore measured against 
{{TConfiguration.maxMessageSize}} for
the life of the connection, so a 68-byte frame may declare a 64 MB field and 
the array is
allocated before the shortfall is found.

h3. The API this needs

Binding the budget to the frame needs an entry point that does not exist today.

* {{resetConsumedMessageSize(-1)}} is the only genuine full reset, and it is 
{{protected}}, so a
layered transport cannot call it on its inner transport.
* {{updateKnownMessageSize()}} refuses to grow a budget an earlier, smaller 
frame narrowed, and
carries that frame's consumption forward besides.

So this adds {{resetMessageSizeAndConsumedBytes(long)}} to {{TTransport}}, 
mirroring netstd's
{{ResetMessageSizeAndConsumedBytes}}, implemented on {{TEndpointTransport}}, 
delegated by
{{TLayeredTransport}}, and a no-op on {{TFileTransport}} alongside its existing 
no-op budget
methods.

h3. Change

{{readFrame()}} resets twice, for two different reasons.

* *Before* the framing reads, because what is left of the previous frame's 
bound describes a frame
we are done with, and an inner transport that decrements on read -- 
{{TMemoryBuffer}}, or the
{{TMemoryInputTransport}} the nonblocking server uses -- would otherwise refuse 
a frame larger
than the last one.
* *After* them, binding to the frame, discarding what the framing itself spent 
rather than
charging it twice.

{{TMemoryInputTransport.reset()}} binds to the buffer it is given, as its 
constructor already did.
Reads there are clamped to the buffer and short-read rather than checked 
against the budget, so
this only tightens what the protocol may declare.

h3. Source compatibility

The new abstract method is a source-compatibility break for anything extending 
{{TTransport}}
directly rather than through {{TEndpointTransport}} or {{TLayeredTransport}}. 
Four test doubles in
this repository needed it.

h3. Tests

Nine in {{TestFrameBoundReadBudget}}. Three fail against the unmodified 
library; the rest describe
traffic that is legitimate today and must keep passing, because the natural 
implementation of frame
binding breaks them. One records why the bound cannot go on the inner 
transport: with the
nonblocking server's shape, getting the frame off the wire spends the inner 
budget entirely, and
without the first reset the frame's own bytes are then unreadable. 346 tests 
pass.

h3. Relationship to THRIFT-5371

THRIFT-5371 reports the C++ half of this, and asks there whether Java is 
affected too. Partly: Java
does *not* accumulate across frames the way C++ does, because 
{{TIOStreamTransport}} never
decrements the budget on read -- its only reset is the {{flush()}} one on the 
write side. What Java
does share is the other half, the budget never being narrowed to the frame, 
which is what the three
failing tests describe.

_Drafted with AI assistance (Claude Opus 5); filed by Jens Geyer._



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to