fix(nix-compat/wire/bytes/reader): always read trailer tag data
Even if we have an aligned (or zero) size (and thus have no padding), we may still have a non-padding trailer. Since an exact read of only the user data bytes must always read the trailer bytes as well, this has to happen for aligned sizes too. For zero-sized trailers, we solve this by reading immediately, since no read is required of the user at all. user_len thus becomes a NonZeroU64, and the definition of "body data" is adjusted to always exclude the final block. Change-Id: I2c6bb51270fb22ad72a65076a3468673ab56ef68 Reviewed-on: https://cl.tvl.fyi/c/depot/+/11597 Reviewed-by: flokli <flokli@flokli.de> Tested-by: BuildkiteCI
This commit is contained in:
parent
0472b55b20
commit
ebad318ab3
3 changed files with 73 additions and 16 deletions
|
@ -1,4 +1,4 @@
|
||||||
mod wire;
|
pub(crate) mod wire;
|
||||||
|
|
||||||
pub mod reader;
|
pub mod reader;
|
||||||
pub mod writer;
|
pub mod writer;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use std::{
|
use std::{
|
||||||
future::Future,
|
future::Future,
|
||||||
io,
|
io,
|
||||||
|
num::NonZeroU64,
|
||||||
ops::RangeBounds,
|
ops::RangeBounds,
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
task::{self, ready, Poll},
|
task::{self, ready, Poll},
|
||||||
|
@ -33,14 +34,26 @@ pub struct BytesReader<R, T: Tag = Pad> {
|
||||||
state: State<R, T>,
|
state: State<R, T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Split the `user_len` into `body_len` and `tail_len`, which are respectively
|
||||||
|
/// the non-terminal 8-byte blocks, and the ≤8 bytes of user data contained in
|
||||||
|
/// the trailer block.
|
||||||
|
#[inline(always)]
|
||||||
|
fn split_user_len(user_len: NonZeroU64) -> (u64, u8) {
|
||||||
|
let n = user_len.get() - 1;
|
||||||
|
let body_len = n & !7;
|
||||||
|
let tail_len = (n & 7) as u8 + 1;
|
||||||
|
(body_len, tail_len)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum State<R, T: Tag> {
|
enum State<R, T: Tag> {
|
||||||
/// Full 8-byte blocks are being read and released to the caller.
|
/// Full 8-byte blocks are being read and released to the caller.
|
||||||
|
/// NOTE: The final 8-byte block is *always* part of the trailer.
|
||||||
Body {
|
Body {
|
||||||
reader: Option<R>,
|
reader: Option<R>,
|
||||||
consumed: u64,
|
consumed: u64,
|
||||||
/// The total length of all user data contained in both the body and trailer.
|
/// The total length of all user data contained in both the body and trailer.
|
||||||
user_len: u64,
|
user_len: NonZeroU64,
|
||||||
},
|
},
|
||||||
/// The trailer is in the process of being read.
|
/// The trailer is in the process of being read.
|
||||||
ReadTrailer(ReadTrailer<R, T>),
|
ReadTrailer(ReadTrailer<R, T>),
|
||||||
|
@ -76,11 +89,17 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
state: State::Body {
|
state: match NonZeroU64::new(size) {
|
||||||
|
Some(size) => State::Body {
|
||||||
reader: Some(reader),
|
reader: Some(reader),
|
||||||
consumed: 0,
|
consumed: 0,
|
||||||
user_len: size,
|
user_len: size,
|
||||||
},
|
},
|
||||||
|
None => State::ReleaseTrailer {
|
||||||
|
consumed: 0,
|
||||||
|
data: read_trailer::<R, T>(reader, 0).await?,
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,7 +115,7 @@ where
|
||||||
match self.state {
|
match self.state {
|
||||||
State::Body {
|
State::Body {
|
||||||
consumed, user_len, ..
|
consumed, user_len, ..
|
||||||
} => user_len - consumed,
|
} => user_len.get() - consumed,
|
||||||
State::ReadTrailer(ref fut) => fut.len() as u64,
|
State::ReadTrailer(ref fut) => fut.len() as u64,
|
||||||
State::ReleaseTrailer { consumed, ref data } => data.len() as u64 - consumed as u64,
|
State::ReleaseTrailer { consumed, ref data } => data.len() as u64 - consumed as u64,
|
||||||
}
|
}
|
||||||
|
@ -119,13 +138,12 @@ impl<R: AsyncRead + Unpin, T: Tag> AsyncRead for BytesReader<R, T> {
|
||||||
consumed,
|
consumed,
|
||||||
user_len,
|
user_len,
|
||||||
} => {
|
} => {
|
||||||
let body_len = *user_len & !7;
|
let (body_len, tail_len) = split_user_len(*user_len);
|
||||||
let remaining = body_len - *consumed;
|
let remaining = body_len - *consumed;
|
||||||
|
|
||||||
let reader = if remaining == 0 {
|
let reader = if remaining == 0 {
|
||||||
let reader = reader.take().unwrap();
|
let reader = reader.take().unwrap();
|
||||||
let user_len = (*user_len & 7) as u8;
|
*this = State::ReadTrailer(read_trailer(reader, tail_len));
|
||||||
*this = State::ReadTrailer(read_trailer(reader, user_len));
|
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
reader.as_mut().unwrap()
|
reader.as_mut().unwrap()
|
||||||
|
@ -277,6 +295,45 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read the trailer immediately if there is no payload.
|
||||||
|
#[tokio::test]
|
||||||
|
async fn read_trailer_immediately() {
|
||||||
|
use crate::nar::wire::PadPar;
|
||||||
|
|
||||||
|
let mut mock = Builder::new()
|
||||||
|
.read(&[0; 8])
|
||||||
|
.read(&PadPar::PATTERN[8..])
|
||||||
|
.build();
|
||||||
|
|
||||||
|
BytesReader::<_, PadPar>::new_internal(&mut mock, ..)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// The mock reader will panic if dropped without reading all data.
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read the trailer even if we only read the exact payload size.
|
||||||
|
#[tokio::test]
|
||||||
|
async fn read_exact_trailer() {
|
||||||
|
use crate::nar::wire::PadPar;
|
||||||
|
|
||||||
|
let mut mock = Builder::new()
|
||||||
|
.read(&16u64.to_le_bytes())
|
||||||
|
.read(&[0x55; 16])
|
||||||
|
.read(&PadPar::PATTERN[8..])
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let mut reader = BytesReader::<_, PadPar>::new_internal(&mut mock, ..)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut buf = [0; 16];
|
||||||
|
reader.read_exact(&mut buf).await.unwrap();
|
||||||
|
assert_eq!(buf, [0x55; 16]);
|
||||||
|
|
||||||
|
// The mock reader will panic if dropped without reading all data.
|
||||||
|
}
|
||||||
|
|
||||||
/// Fail if the padding is not all zeroes
|
/// Fail if the padding is not all zeroes
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn read_fail_if_nonzero_padding() {
|
async fn read_fail_if_nonzero_padding() {
|
||||||
|
|
|
@ -9,11 +9,11 @@ use std::{
|
||||||
|
|
||||||
use tokio::io::{self, AsyncRead, ReadBuf};
|
use tokio::io::{self, AsyncRead, ReadBuf};
|
||||||
|
|
||||||
/// Trailer represents up to 7 bytes of data read as part of the trailer block(s)
|
/// Trailer represents up to 8 bytes of data read as part of the trailer block(s)
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct Trailer {
|
pub(crate) struct Trailer {
|
||||||
data_len: u8,
|
data_len: u8,
|
||||||
buf: [u8; 7],
|
buf: [u8; 8],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for Trailer {
|
impl Deref for Trailer {
|
||||||
|
@ -28,7 +28,7 @@ impl Deref for Trailer {
|
||||||
pub(crate) trait Tag {
|
pub(crate) trait Tag {
|
||||||
/// The expected suffix
|
/// The expected suffix
|
||||||
///
|
///
|
||||||
/// The first 7 bytes may be ignored, and it must be an 8-byte aligned size.
|
/// The first 8 bytes may be ignored, and it must be an 8-byte aligned size.
|
||||||
const PATTERN: &'static [u8];
|
const PATTERN: &'static [u8];
|
||||||
|
|
||||||
/// Suitably sized buffer for reading [Self::PATTERN]
|
/// Suitably sized buffer for reading [Self::PATTERN]
|
||||||
|
@ -67,7 +67,7 @@ pub(crate) fn read_trailer<R: AsyncRead + Unpin, T: Tag>(
|
||||||
reader: R,
|
reader: R,
|
||||||
data_len: u8,
|
data_len: u8,
|
||||||
) -> ReadTrailer<R, T> {
|
) -> ReadTrailer<R, T> {
|
||||||
assert!(data_len < 8, "payload in trailer must be less than 8 bytes");
|
assert!(data_len <= 8, "payload in trailer must be <= 8 bytes");
|
||||||
|
|
||||||
let buf = T::make_buf();
|
let buf = T::make_buf();
|
||||||
assert_eq!(buf.as_ref().len(), T::PATTERN.len());
|
assert_eq!(buf.as_ref().len(), T::PATTERN.len());
|
||||||
|
@ -108,8 +108,8 @@ impl<R: AsyncRead + Unpin, T: Tag> Future for ReadTrailer<R, T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if this.filled as usize == T::PATTERN.len() {
|
if this.filled as usize == T::PATTERN.len() {
|
||||||
let mut buf = [0; 7];
|
let mut buf = [0; 8];
|
||||||
buf.copy_from_slice(&this.buf.as_ref()[..7]);
|
buf.copy_from_slice(&this.buf.as_ref()[..8]);
|
||||||
|
|
||||||
return Ok(Trailer {
|
return Ok(Trailer {
|
||||||
data_len: this.data_len,
|
data_len: this.data_len,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue