|
1 | 1 | use std::error::Error as StdError; |
2 | 2 | use std::future::Future; |
3 | | -use std::io::{Cursor, IoSlice}; |
| 3 | +use std::io::Cursor; |
4 | 4 | use std::pin::Pin; |
5 | 5 | use std::task::{Context, Poll}; |
6 | 6 |
|
@@ -282,6 +282,9 @@ enum SendBuf<B> { |
282 | 282 | None, |
283 | 283 | } |
284 | 284 |
|
| 285 | +// Intentionally use `Buf`'s single-chunk `chunks_vectored` default. Forwarding |
| 286 | +// to `B::chunks_vectored` could expose non-contiguous user data to h2's framing |
| 287 | +// buffer when `B::chunk()` is only a partial prefix. |
285 | 288 | impl<B: Buf> Buf for SendBuf<B> { |
286 | 289 | #[inline] |
287 | 290 | fn remaining(&self) -> usize { |
@@ -309,12 +312,72 @@ impl<B: Buf> Buf for SendBuf<B> { |
309 | 312 | Self::None => {} |
310 | 313 | } |
311 | 314 | } |
| 315 | +} |
312 | 316 |
|
313 | | - fn chunks_vectored<'a>(&'a self, dst: &mut [IoSlice<'a>]) -> usize { |
314 | | - match *self { |
315 | | - Self::Buf(ref b) => b.chunks_vectored(dst), |
316 | | - Self::Cursor(ref c) => c.chunks_vectored(dst), |
317 | | - Self::None => 0, |
| 317 | +#[cfg(test)] |
| 318 | +mod tests { |
| 319 | + use std::io::IoSlice; |
| 320 | + |
| 321 | + use bytes::{Buf, Bytes}; |
| 322 | + |
| 323 | + use super::SendBuf; |
| 324 | + |
| 325 | + struct OneByteAtATime { |
| 326 | + data: &'static [u8], |
| 327 | + pos: usize, |
| 328 | + } |
| 329 | + |
| 330 | + impl Buf for OneByteAtATime { |
| 331 | + fn remaining(&self) -> usize { |
| 332 | + self.data.len() - self.pos |
| 333 | + } |
| 334 | + |
| 335 | + fn chunk(&self) -> &[u8] { |
| 336 | + let end = std::cmp::min(self.pos + 1, self.data.len()); |
| 337 | + &self.data[self.pos..end] |
| 338 | + } |
| 339 | + |
| 340 | + fn advance(&mut self, cnt: usize) { |
| 341 | + self.pos += cnt; |
318 | 342 | } |
319 | 343 | } |
| 344 | + |
| 345 | + fn drain_vectored(mut buf: impl Buf) -> Vec<u8> { |
| 346 | + let mut out = Vec::new(); |
| 347 | + while buf.has_remaining() { |
| 348 | + let mut iovs = [IoSlice::new(&[]); 8]; |
| 349 | + let n = buf.chunks_vectored(&mut iovs); |
| 350 | + assert!(n > 0, "chunks_vectored returned 0 while bytes remain"); |
| 351 | + let mut wrote = 0; |
| 352 | + for iov in &iovs[..n] { |
| 353 | + out.extend_from_slice(iov); |
| 354 | + wrote += iov.len(); |
| 355 | + } |
| 356 | + buf.advance(wrote); |
| 357 | + } |
| 358 | + out |
| 359 | + } |
| 360 | + |
| 361 | + #[test] |
| 362 | + fn send_buf_vectored_is_contiguous_prefix() { |
| 363 | + let body = OneByteAtATime { |
| 364 | + data: b"abc", |
| 365 | + pos: 0, |
| 366 | + } |
| 367 | + .chain(Bytes::from_static(b"XYZ")); |
| 368 | + assert_eq!(drain_vectored(SendBuf::Buf(body)), b"abcXYZ"); |
| 369 | + } |
| 370 | + |
| 371 | + #[test] |
| 372 | + fn send_buf_bytes_stays_vectored_with_framing() { |
| 373 | + let buf = |
| 374 | + Bytes::from_static(b"frame header").chain(SendBuf::Buf(Bytes::from_static(b"hello"))); |
| 375 | + let mut iovs = [IoSlice::new(&[]); 2]; |
| 376 | + |
| 377 | + let n = buf.chunks_vectored(&mut iovs); |
| 378 | + |
| 379 | + assert_eq!(n, 2); |
| 380 | + assert_eq!(&*iovs[0], b"frame header"); |
| 381 | + assert_eq!(&*iovs[1], b"hello"); |
| 382 | + } |
320 | 383 | } |
0 commit comments