mirror of
https://gitlab.com/futo-org/fcast.git
synced 2025-08-24 00:02:50 +00:00
Sender SDK
This commit is contained in:
parent
fdbefc63e0
commit
afc46f3022
147 changed files with 17638 additions and 114 deletions
8
sdk/sender/parsers-common/Cargo.toml
Normal file
8
sdk/sender/parsers-common/Cargo.toml
Normal file
|
@ -0,0 +1,8 @@
|
|||
[package]
|
||||
name = "parsers-common"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
thiserror.workspace = true
|
191
sdk/sender/parsers-common/src/lib.rs
Normal file
191
sdk/sender/parsers-common/src/lib.rs
Normal file
|
@ -0,0 +1,191 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
/// Find the first `\r\n` sequence in `data` and returns the index of the `\r`.
|
||||
pub fn find_first_cr_lf(data: &[u8]) -> Option<usize> {
|
||||
for (i, win) in data.windows(2).enumerate() {
|
||||
if win == *b"\r\n" {
|
||||
return Some(i);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Find the first `\r\n\r\n` sequence in `data` and returns the index of the first `\r`.
|
||||
pub fn find_first_double_cr_lf(data: &[u8]) -> Option<usize> {
|
||||
for (i, win) in data.windows(4).enumerate() {
|
||||
if win == b"\r\n\r\n" {
|
||||
return Some(i);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error, PartialEq)]
|
||||
pub enum ParseHeaderMapError {
|
||||
#[error("Missing key name")]
|
||||
MissingKeyName,
|
||||
#[error("Missing end CR LF")]
|
||||
MissingEndCrLf,
|
||||
#[error("Missing value")]
|
||||
MissingValue,
|
||||
#[error("Missing value (CR LF)")]
|
||||
MissingValueCrLf,
|
||||
#[error("Malformed header map")]
|
||||
Malformed,
|
||||
#[error("Key is not UTF-8")]
|
||||
KeyIsNotUtf8,
|
||||
#[error("Value is not UTF-8")]
|
||||
ValueIsNotUtf8,
|
||||
#[error("Duplicated header")]
|
||||
DuplicatedHeader,
|
||||
}
|
||||
|
||||
/// Parse an RTSP/HTTP header map.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `data` a byte buffer with key value pairs in the format `<key>: <value>\r\n` that must include
|
||||
/// the trailing `\r\n` line.
|
||||
pub fn parse_header_map(data: &[u8]) -> Result<HashMap<&'_ str, &'_ str>, ParseHeaderMapError> {
|
||||
let mut map = HashMap::new();
|
||||
let mut i = 0;
|
||||
|
||||
while i < data.len() {
|
||||
if data[i] == b'\r' {
|
||||
break;
|
||||
}
|
||||
|
||||
let mut semicolon_idx = i;
|
||||
while semicolon_idx < data.len() {
|
||||
if data[semicolon_idx] == b':' {
|
||||
break;
|
||||
}
|
||||
semicolon_idx += 1;
|
||||
}
|
||||
if semicolon_idx >= data.len() || i == semicolon_idx || data[semicolon_idx] != b':' {
|
||||
return Err(ParseHeaderMapError::MissingKeyName);
|
||||
}
|
||||
|
||||
let key = &data[i..semicolon_idx];
|
||||
|
||||
if semicolon_idx + 1 >= data.len() || data[semicolon_idx + 1] != b' ' {
|
||||
return Err(ParseHeaderMapError::MissingValue);
|
||||
}
|
||||
|
||||
i = semicolon_idx + 2;
|
||||
|
||||
let mut cr_idx = semicolon_idx + 2;
|
||||
while cr_idx < data.len() {
|
||||
if data[cr_idx] == b'\r' {
|
||||
if cr_idx + 1 >= data.len() || data[cr_idx + 1] != b'\n' {
|
||||
return Err(ParseHeaderMapError::MissingValueCrLf);
|
||||
}
|
||||
break;
|
||||
}
|
||||
cr_idx += 1;
|
||||
}
|
||||
|
||||
if cr_idx >= data.len() || i == cr_idx || data[cr_idx] != b'\r' {
|
||||
return Err(ParseHeaderMapError::MissingValue);
|
||||
}
|
||||
|
||||
let value = &data[i..cr_idx];
|
||||
|
||||
i = cr_idx + 2;
|
||||
|
||||
let Ok(key) = str::from_utf8(key) else {
|
||||
return Err(ParseHeaderMapError::KeyIsNotUtf8);
|
||||
};
|
||||
let Ok(value) = str::from_utf8(value) else {
|
||||
return Err(ParseHeaderMapError::ValueIsNotUtf8);
|
||||
};
|
||||
|
||||
if map.insert(key, value).is_some() {
|
||||
return Err(ParseHeaderMapError::DuplicatedHeader);
|
||||
}
|
||||
}
|
||||
|
||||
if i + 1 >= data.len() || data[i + 1] != b'\n' {
|
||||
return Err(ParseHeaderMapError::MissingEndCrLf);
|
||||
}
|
||||
|
||||
if i + 1 != data.len() - 1 {
|
||||
return Err(ParseHeaderMapError::Malformed);
|
||||
}
|
||||
|
||||
Ok(map)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_find_first_cr_lf() {
|
||||
assert_eq!(find_first_cr_lf(b"01234\r\n"), Some(5));
|
||||
assert_eq!(find_first_cr_lf(b"01234"), None);
|
||||
assert_eq!(find_first_cr_lf(b"01234\r\nabc\r\n"), Some(5));
|
||||
assert_eq!(find_first_cr_lf(b"\r\n"), Some(0));
|
||||
assert_eq!(find_first_cr_lf(b"\r"), None);
|
||||
assert_eq!(find_first_cr_lf(b"abc\r"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_first_double_cr_lf() {
|
||||
assert_eq!(find_first_double_cr_lf(b"01234\r\n"), None);
|
||||
assert_eq!(find_first_double_cr_lf(b"01234\r\n\r\n"), Some(5));
|
||||
assert_eq!(find_first_double_cr_lf(b"01234"), None);
|
||||
assert_eq!(find_first_double_cr_lf(b"01234\r\nabc\r\n"), None);
|
||||
assert_eq!(find_first_double_cr_lf(b"01234\r\n\r\nabc\r\n"), Some(5));
|
||||
assert_eq!(find_first_double_cr_lf(b"01234\r\nabc\r\n\r\n"), Some(10));
|
||||
assert_eq!(find_first_double_cr_lf(b"\r\n\r\n"), Some(0));
|
||||
assert_eq!(find_first_double_cr_lf(b"\r"), None);
|
||||
assert_eq!(find_first_double_cr_lf(b"abc\r"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_valid_header_map() {
|
||||
assert_eq!(
|
||||
parse_header_map(
|
||||
b"Content-Length: 0\r\n\
|
||||
\r\n"
|
||||
)
|
||||
.unwrap(),
|
||||
HashMap::from([("Content-Length", "0"),])
|
||||
);
|
||||
assert_eq!(
|
||||
parse_header_map(
|
||||
b"Content-Length: 0\r\n\
|
||||
Content-Type: application/octet-stream\r\n\
|
||||
\r\n"
|
||||
)
|
||||
.unwrap(),
|
||||
HashMap::from([
|
||||
("Content-Length", "0"),
|
||||
("Content-Type", "application/octet-stream",),
|
||||
])
|
||||
);
|
||||
assert_eq!(parse_header_map(b"\r\n").unwrap(), HashMap::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_invalid_header_map() {
|
||||
assert_eq!(
|
||||
parse_header_map(b"Content-Length: 0\r\n"),
|
||||
Err(ParseHeaderMapError::MissingEndCrLf),
|
||||
);
|
||||
assert_eq!(
|
||||
parse_header_map(b"Content-Length: 0\r\n\r\n this makes it malformed"),
|
||||
Err(ParseHeaderMapError::Malformed),
|
||||
);
|
||||
assert_eq!(
|
||||
parse_header_map(b": 0\r\n\r\n"),
|
||||
Err(ParseHeaderMapError::MissingKeyName),
|
||||
);
|
||||
assert_eq!(
|
||||
parse_header_map(b"Content-Length: \r\n\r\n"),
|
||||
Err(ParseHeaderMapError::MissingValue),
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue