use crate::{FileTransferHeader, TransferError};
use serde::{Deserialize, Serialize};
pub const REQUIRED_CAPABILITIES_METADATA_KEY: &str = "openrtc.required-capabilities.v1";
pub const MAX_CAPABILITY_BYTES: usize = 4096;
pub const MAX_SAFE_FILE_BYTES: u64 = 9_007_199_254_740_991;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct RequiredTransferCapabilities {
pub version: u8,
pub durable_resume: bool,
pub completion_receipt: bool,
pub extensions: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct TransferCapabilities {
pub version: u8,
pub durable_resume: bool,
pub completion_receipt: bool,
pub extensions: Vec<String>,
pub receive_available: bool,
pub max_file_bytes: u64,
}
fn normalize_extensions(extensions: &mut [String]) -> Result<(), TransferError> {
if extensions.len() > 16
|| extensions.iter().any(|extension| {
extension.len() > 128
|| extension
.split(['.', '_', '-'])
.enumerate()
.any(|(index, part)| {
part.is_empty()
|| !part
.bytes()
.all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit())
|| index == 0 && !part.as_bytes()[0].is_ascii_lowercase()
})
})
{
return Err(TransferError::Protocol(
"invalid transfer capability extensions".into(),
));
}
extensions.sort();
if extensions.windows(2).any(|pair| pair[0] == pair[1]) {
return Err(TransferError::Protocol(
"duplicate transfer capability extension".into(),
));
}
Ok(())
}
impl RequiredTransferCapabilities {
pub fn normalized(mut self) -> Result<Self, TransferError> {
if self.version != 1 {
return Err(TransferError::Protocol(
"invalid transfer capability version".into(),
));
}
normalize_extensions(&mut self.extensions)?;
Ok(self)
}
}
impl TransferCapabilities {
pub fn normalized(mut self) -> Result<Self, TransferError> {
if self.version != 1 || self.max_file_bytes > MAX_SAFE_FILE_BYTES {
return Err(TransferError::Protocol(
"invalid transfer capability version or file limit".into(),
));
}
normalize_extensions(&mut self.extensions)?;
Ok(self)
}
pub fn require(&self, header: &FileTransferHeader) -> Result<(), TransferError> {
let caps = self.clone().normalized()?;
let required = header.required_capabilities()?;
if !caps.receive_available
|| header.size > caps.max_file_bytes
|| required.is_some_and(|r| {
r.durable_resume && !caps.durable_resume
|| r.completion_receipt && !caps.completion_receipt
|| r.extensions
.iter()
.any(|extension| !caps.extensions.contains(extension))
})
{
return Err(TransferError::Protocol("transfer_capability_required: receiver cannot admit the selected size or required transfer features".into()));
}
Ok(())
}
}
impl FileTransferHeader {
pub fn required_capabilities(
&self,
) -> Result<Option<RequiredTransferCapabilities>, TransferError> {
let Some(encoded) = self.metadata.get(REQUIRED_CAPABILITIES_METADATA_KEY) else {
return Ok(None);
};
if encoded.len() > MAX_CAPABILITY_BYTES {
return Err(TransferError::Limit(
"required transfer capability metadata is too large".into(),
));
}
let required: RequiredTransferCapabilities = serde_json::from_str(encoded)?;
Ok(Some(required.normalized()?))
}
pub fn with_required_capabilities(
mut self,
required: RequiredTransferCapabilities,
) -> Result<Self, TransferError> {
let encoded = serde_json::to_string(&required.normalized()?)?;
if self
.metadata
.get(REQUIRED_CAPABILITIES_METADATA_KEY)
.is_some_and(|old| old != &encoded)
{
return Err(TransferError::Protocol("transfer_identity_conflict: required transfer capabilities differ from the accepted header".into()));
}
self.metadata
.insert(REQUIRED_CAPABILITIES_METADATA_KEY.into(), encoded);
Ok(self)
}
}