Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for BBFRAME headers #34

Merged
merged 2 commits into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 23 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,12 @@ This corresponds to BBFRAMEs fragmented into multiple UDP packets (since usually
DVB-S2 BBFRAMEs are larger than a 1500 byte MTU). The following rules need to be
followed.

* The beginning of each BBFRAME should be aligned with the beginning of the
payload of a UDP packet.
* The payload of each UDP packet can optionally be begin by a header of up to 64
bytes, which is discarded by this application. The header length is set with
the `--header-length` argument. By default, no header is assumed.

* The beginning of each BBFRAME should happen at the end of a header, or at the
beginning of the payload of a UDP packet if there is no header.

* The BBFRAME padding can either be removed or be present. If possible, it is
recommended to remove the BBFRAME padding, in order to reduce the network
Expand All @@ -74,8 +78,12 @@ The CLI application tries to recover from dropped UDP packets.
This corresponds to BBFRAMEs carried in a single UDP packet (it will typically
be a jumbo packet). The following rules need to be followed.

* Each BBFRAME should be completely contained at the start of a single UDP
packet.
* The payload of each UDP packet can optionally be begin by a header of up to 64
bytes, which is discarded by this application. The header length is set with
the `--header-length` argument. By default, no header is assumed.

* Each BBFRAME should be completely contained in a single UDP packet. The
BBFRAME should follow the header immediately.

* There can be padding or any other data following the BBFRAME in the same UDP
packet.
Expand All @@ -87,12 +95,19 @@ UDP packets can be dropped. The CLI application will handle this gracefully.
This corresponds to receiving BBFRAMEs in a TCP stream. The CLI application acts
as server. The following rules need to be followed.

* BBFRAMEs need to be present back to back in the TCP stream.
* Each BBFRAME can optionally be preceded by a header of up to 64 bytes, which
is discarded by this application. The header length is set with the
`--header-length` argument. By default, no header is assumed.

* BBFRAMEs (including their headers, if applicable) need to be present back to
back in the TCP stream.

* BBFRAMEs padding must be remoted. The length of the BBFRAMEs in the stream
must equal 10 bytes for the BBHEADER plus the value of their DFL dividided by 8.
* BBFRAMEs padding must be removed. The length of the BBFRAMEs in the stream
must equal 10 bytes for the BBHEADER plus the value of their DFL dividided by
8.

* No other data can be present in the TCP stream.
* No other data besides the headers and BBFRAMEs can be present in the TCP
stream.

If an error occurrs or the client closes the connection, the CLI application
will continue listen for new clients.
Expand Down
131 changes: 127 additions & 4 deletions src/bbframe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,22 @@ use std::net::UdpSocket;
#[allow(unused_imports)]
use std::net::TcpStream;

/// Maximum BBFRAME length in bytes.
/// Maximum header length in bytes.
///
/// This is used to allocate buffers for the BBFRAMEs, taking into account that
/// they can optionally be preceded by a header whose maximum length is given by
/// this constant. The value of the constant is set to a somewhat arbitrary
/// choice that should be good for most use cases.
pub const HEADER_MAX_LEN: usize = 64;

/// Maximum BBFRAME length in bytes (including header).
///
/// The maximum BBFRAME size possible corresponds to r=9/10 DVB-S2 with normal
/// FECFRAMEs, which is 58192 bits or 7274 bytes.
pub const BBFRAME_MAX_LEN: usize = 7274;
///
/// The constant includes the [`HEADER_MAX_LEN`] in the calculation of the
/// maximum BBFRAME length.
pub const BBFRAME_MAX_LEN: usize = 7274 + HEADER_MAX_LEN;

/// BBFRAME defragmenter.
///
Expand All @@ -32,6 +43,7 @@ pub struct BBFrameDefrag<R> {
buffer: Box<[u8; BBFRAME_MAX_LEN]>,
occupied_bytes: usize,
validator: BBFrameValidator,
header_bytes: usize,
}

/// BBFRAME receiver.
Expand All @@ -43,6 +55,7 @@ pub struct BBFrameRecv<R> {
recv_bbframe: R,
buffer: Box<[u8; BBFRAME_MAX_LEN]>,
validator: BBFrameValidator,
header_bytes: usize,
}

/// BBFRAME stream receiver.
Expand All @@ -54,6 +67,7 @@ pub struct BBFrameStream<R> {
recv_stream: R,
buffer: Box<[u8; BBFRAME_MAX_LEN]>,
validator: BBFrameValidator,
header_bytes: usize,
}

/// Receiver of BBFrames.
Expand Down Expand Up @@ -148,7 +162,7 @@ impl BBFrameValidator {
log::error!("unsupported data field length not a multiple of 8 bits");
return false;
}
if usize::from(bbheader.dfl() / 8) > BBFRAME_MAX_LEN - BBHeader::LEN {
if usize::from(bbheader.dfl() / 8) > BBFRAME_MAX_LEN - BBHeader::LEN - HEADER_MAX_LEN {
log::error!("DFL value {} too large", bbheader.dfl());
return false;
}
Expand Down Expand Up @@ -300,10 +314,31 @@ impl<R> BBFrameDefrag<R> {
buffer: Box::new([0; BBFRAME_MAX_LEN]),
occupied_bytes: 0,
validator: BBFrameValidator::new(),
header_bytes: 0,
}
}

impl_set_isi!(BBFrameDefrag);

/// Sets the number of bytes used in the header in each fragment.
///
/// The header is removed before reading the rest of the fragment, which
/// should correpond to a BBFRAME fragment. By default, a header length of
/// zero bytes is assumed.
///
/// The function returns an error if `header_bytes` is larger than
/// [`HEADER_MAX_LEN`].
pub fn set_header_bytes(&mut self, header_bytes: usize) -> Result<()> {
if header_bytes > HEADER_MAX_LEN {
log::error!("header bytes larger than maximum header size");
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"header bytes larger than maximum header size",
));
}
self.header_bytes = header_bytes;
Ok(())
}
}

impl<R: RecvFragment> BBFrameReceiver for BBFrameDefrag<R> {
Expand Down Expand Up @@ -339,7 +374,21 @@ impl<R: RecvFragment> BBFrameDefrag<R> {
let n = self
.recv_fragment
.recv_fragment(&mut self.buffer[self.occupied_bytes..])?;
self.occupied_bytes += n;
if self.header_bytes != 0 {
if self.header_bytes > n {
log::error!("received a fragment smaller than the header size");
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"BBFRAME is too short",
));
}
// overwrite the header with the rest of the data
self.buffer.copy_within(
self.occupied_bytes + self.header_bytes..self.occupied_bytes + n,
self.occupied_bytes,
);
}
self.occupied_bytes += n - self.header_bytes;
Ok(())
}
}
Expand All @@ -354,10 +403,30 @@ impl<R> BBFrameRecv<R> {
recv_bbframe,
buffer: Box::new([0; BBFRAME_MAX_LEN]),
validator: BBFrameValidator::new(),
header_bytes: 0,
}
}

impl_set_isi!(BBFrameRecv);

/// Sets the number of bytes used in the header in each BBFRAME.
///
/// The header is removed before reading the BBFRAME. By default, a header
/// length of zero bytes is assumed.
///
/// The function returns an error if `header_bytes` is larger than
/// [`HEADER_MAX_LEN`].
pub fn set_header_bytes(&mut self, header_bytes: usize) -> Result<()> {
if header_bytes > HEADER_MAX_LEN {
log::error!("header bytes larger than maximum header size");
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"header bytes larger than maximum header size",
));
}
self.header_bytes = header_bytes;
Ok(())
}
}

impl<R: RecvBBFrame> BBFrameReceiver for BBFrameRecv<R> {
Expand All @@ -369,6 +438,19 @@ impl<R: RecvBBFrame> BBFrameReceiver for BBFrameRecv<R> {
/// an error in reception.
fn get_bbframe(&mut self) -> Result<BBFrame> {
let recv_len = self.recv_bbframe.recv_bbframe(&mut self.buffer)?;
if self.header_bytes != 0 {
if self.header_bytes > recv_len {
log::error!("received a fragment smaller than the header size");
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"BBFRAME is too short",
));
}
// This could be removed if we allowed the functions below to use an
// offset in the buffer to access the BBFRAME.
self.buffer.copy_within(self.header_bytes..recv_len, 0);
}
let recv_len = recv_len - self.header_bytes;
if recv_len < BBHeader::LEN {
log::error!("received BBFRAME is too short (length {recv_len})");
return Err(std::io::Error::new(
Expand Down Expand Up @@ -409,10 +491,27 @@ impl<R> BBFrameStream<R> {
recv_stream,
buffer: Box::new([0; BBFRAME_MAX_LEN]),
validator: BBFrameValidator::new(),
header_bytes: 0,
}
}

impl_set_isi!(BBFrameStream);

/// Sets the number of bytes used in the header in each BBFRAME.
///
/// The function returns an error if `header_bytes` is larger than
/// [`HEADER_MAX_LEN`].
pub fn set_header_bytes(&mut self, header_bytes: usize) -> Result<()> {
if header_bytes > HEADER_MAX_LEN {
log::error!("header bytes larger than maximum header size");
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"header bytes larger than maximum header size",
));
}
self.header_bytes = header_bytes;
Ok(())
}
}

impl<R: RecvStream> BBFrameReceiver for BBFrameStream<R> {
Expand All @@ -423,6 +522,11 @@ impl<R: RecvStream> BBFrameReceiver for BBFrameStream<R> {
/// BBFRAME, returning an error if the BBFRAME is not valid or if there is
/// an error in reception.
fn get_bbframe(&mut self) -> Result<BBFrame> {
if self.header_bytes != 0 {
// read header (will be discarded)
self.recv_stream
.recv_stream(&mut self.buffer[..self.header_bytes])?;
}
// read full BBHEADER
self.recv_stream
.recv_stream(&mut self.buffer[..BBHeader::LEN])?;
Expand Down Expand Up @@ -643,6 +747,25 @@ mod test {
assert_eq!(times_called.get(), 1);
}

#[test]
fn recv_one_bbframe_with_header() {
let header_bytes = 4;
let times_called = std::cell::Cell::new(0);
let mut defrag = BBFrameRecv::new(|buff: &mut [u8; BBFRAME_MAX_LEN]| {
times_called.replace(times_called.get() + 1);
buff[..header_bytes].fill(0);
let total_len = header_bytes + SINGLE_FRAGMENT.len();
buff[header_bytes..total_len].copy_from_slice(&SINGLE_FRAGMENT);
Ok(total_len)
});
defrag.set_header_bytes(header_bytes).unwrap();
assert_eq!(
defrag.get_bbframe().unwrap(),
Bytes::from_static(&SINGLE_FRAGMENT)
);
assert_eq!(times_called.get(), 1);
}

#[test]
fn stream_one_bbframe() {
let stream = &SINGLE_FRAGMENT[..];
Expand Down
18 changes: 15 additions & 3 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ struct Args {
/// Input format: "UDP fragments", "UDP complete", or "TCP"
#[arg(long, default_value_t)]
input: InputFormat,
/// Input header length (the header is discarded)
#[arg(long, default_value_t = 0)]
header_length: usize,
/// ISI to process in MIS mode (if this option is not specified, run in SIS mode)
#[arg(long)]
isi: Option<u8>,
Expand Down Expand Up @@ -157,17 +160,23 @@ pub fn main() -> Result<()> {
setup_multicast(&socket, &args.listen)?;
match args.input {
InputFormat::UdpFragments => {
let mut bbframe_recv = BBFrameDefrag::new(socket);
bbframe_recv.set_isi(args.isi);
bbframe_recv.set_header_bytes(args.header_length)?;
let mut app = AppLoop {
bbframe_recv: Some(BBFrameDefrag::new(socket)),
bbframe_recv: Some(bbframe_recv),
gsepacket_defrag,
tun,
bbframe_recv_errors_fatal: true,
};
app.app_loop()?;
}
InputFormat::UdpComplete => {
let mut bbframe_recv = BBFrameRecv::new(socket);
bbframe_recv.set_isi(args.isi);
bbframe_recv.set_header_bytes(args.header_length)?;
let mut app = AppLoop {
bbframe_recv: Some(BBFrameRecv::new(socket)),
bbframe_recv: Some(bbframe_recv),
gsepacket_defrag,
tun,
bbframe_recv_errors_fatal: false,
Expand Down Expand Up @@ -200,7 +209,10 @@ pub fn main() -> Result<()> {
"TCP client connected (but could not retrieve peer address): {err}"
),
}
app.bbframe_recv = Some(BBFrameStream::new(stream));
let mut bbframe_recv = BBFrameStream::new(stream);
bbframe_recv.set_isi(args.isi);
bbframe_recv.set_header_bytes(args.header_length)?;
app.bbframe_recv = Some(bbframe_recv);
if let Err(err) = app.app_loop() {
log::error!("error; waiting for another client: {err:#}");
}
Expand Down
Loading