Skip to content

Commit e793b24

Browse files
authored
fix: auto-release padding from DATA frames (#869)
We did not used to include padding when returning flow control. This now automatically releases any padded length if a DATA frame with padding is received. Closes #867
1 parent 7c9a874 commit e793b24

File tree

3 files changed

+107
-1
lines changed

3 files changed

+107
-1
lines changed

src/frame/data.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,20 @@ impl Data<Bytes> {
139139
pad_len,
140140
})
141141
}
142+
143+
pub(crate) fn flow_controlled_len(&self) -> usize {
144+
if let Some(pad_len) = self.pad_len {
145+
// if PADDED, pad length field counts too (the + 1)
146+
self.data.len() + usize::from(pad_len) + 1
147+
} else {
148+
self.data.len()
149+
}
150+
}
151+
152+
/// If this frame is PADDED, it returns the pad len + 1 (length field).
153+
pub(crate) fn padded_len(&self) -> Option<u8> {
154+
self.pad_len.map(|n| n + 1)
155+
}
142156
}
143157

144158
impl<T: Buf> Data<T> {

src/proto/streams/recv.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -577,7 +577,8 @@ impl Recv {
577577
}
578578

579579
pub fn recv_data(&mut self, frame: frame::Data, stream: &mut store::Ptr) -> Result<(), Error> {
580-
let sz = frame.payload().len();
580+
// could include padding
581+
let sz = frame.flow_controlled_len();
581582

582583
// This should have been enforced at the codec::FramedRead layer, so
583584
// this is just a sanity check.
@@ -628,6 +629,7 @@ impl Recv {
628629
return Err(Error::library_reset(stream.id, Reason::FLOW_CONTROL_ERROR));
629630
}
630631

632+
// use payload len, padding doesn't count for content-length
631633
if stream.dec_content_length(frame.payload().len()).is_err() {
632634
proto_err!(stream:
633635
"recv_data: content-length overflow; stream={:?}; len={:?}",
@@ -672,6 +674,18 @@ impl Recv {
672674
// Track the data as in-flight
673675
stream.in_flight_recv_data += sz;
674676

677+
// We auto-release the padded length, since the user cannot.
678+
if let Some(padded_len) = frame.padded_len() {
679+
tracing::trace!(
680+
"recv_data; auto-releasing padded length of {:?} for {:?}",
681+
padded_len,
682+
stream.id,
683+
);
684+
let _res = self.release_capacity(padded_len.into(), stream, &mut None);
685+
// cannot fail, we JUST added more in_flight data above.
686+
debug_assert!(_res.is_ok());
687+
}
688+
675689
let event = Event::Data(frame.into_payload());
676690

677691
// Push the frame onto the recv buffer

tests/h2-tests/tests/flow_control.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,84 @@ async fn release_capacity_sends_window_update() {
117117
join(mock, h2).await;
118118
}
119119

120+
#[tokio::test]
121+
async fn window_updates_include_padded_length() {
122+
h2_support::trace_init!();
123+
124+
// Our manual way of sending padding frames, not supported publicly
125+
const PAYLOAD_LEN: usize = 16_378; // 16_384; does padding + payload count for max frame size?
126+
let mut payload = Vec::with_capacity(PAYLOAD_LEN + 6);
127+
payload.push(5);
128+
payload.extend_from_slice(&[b'z'; PAYLOAD_LEN][..]);
129+
payload.extend_from_slice(&[b'0'; 5][..]);
130+
131+
let (io, mut srv) = mock::new();
132+
133+
let mock = async move {
134+
let settings = srv.assert_client_handshake().await;
135+
assert_default_settings!(settings);
136+
srv.recv_frame(
137+
frames::headers(1)
138+
.request("GET", "https://http2.akamai.com/")
139+
.eos(),
140+
)
141+
.await;
142+
srv.send_frame(frames::headers(1).response(200)).await;
143+
srv.send_frame(frames::data(1, &payload[..]).padded()).await;
144+
srv.send_frame(frames::data(1, &payload[..]).padded()).await;
145+
srv.send_frame(frames::data(1, &payload[..]).padded()).await;
146+
// the other 6 was auto-released earlier
147+
srv.recv_frame(frames::window_update(0, 32_774)).await;
148+
srv.recv_frame(frames::window_update(1, 32_774)).await;
149+
srv.send_frame(frames::data(1, &payload[..]).padded().eos())
150+
.await;
151+
// but not double released here
152+
srv.recv_frame(frames::window_update(0, 32_762)).await;
153+
// and no one cares about closed stream window
154+
};
155+
156+
let h2 = async move {
157+
let (mut client, h2) = client::handshake(io).await.unwrap();
158+
let request = Request::builder()
159+
.method(Method::GET)
160+
.uri("https://http2.akamai.com/")
161+
.body(())
162+
.unwrap();
163+
164+
let req = async move {
165+
let resp = client.send_request(request, true).unwrap().0.await.unwrap();
166+
// Get the response
167+
assert_eq!(resp.status(), StatusCode::OK);
168+
let mut body = resp.into_parts().1;
169+
170+
// read some body to use up window size to below half
171+
let buf = body.data().await.unwrap().unwrap();
172+
assert_eq!(buf.len(), PAYLOAD_LEN);
173+
174+
let buf = body.data().await.unwrap().unwrap();
175+
assert_eq!(buf.len(), PAYLOAD_LEN);
176+
177+
let buf = body.data().await.unwrap().unwrap();
178+
assert_eq!(buf.len(), PAYLOAD_LEN);
179+
body.flow_control().release_capacity(buf.len() * 2).unwrap();
180+
181+
let buf = body.data().await.unwrap().unwrap();
182+
assert_eq!(buf.len(), PAYLOAD_LEN);
183+
drop(body);
184+
idle_ms(20).await;
185+
};
186+
187+
join(
188+
async move {
189+
h2.await.unwrap();
190+
},
191+
req,
192+
)
193+
.await
194+
};
195+
join(mock, h2).await;
196+
}
197+
120198
#[tokio::test]
121199
async fn release_capacity_of_small_amount_does_not_send_window_update() {
122200
h2_support::trace_init!();

0 commit comments

Comments
 (0)