openrtc_file_transfer/
capabilities.rs1use crate::{FileTransferHeader, TransferError};
4use serde::{Deserialize, Serialize};
5
6pub const REQUIRED_CAPABILITIES_METADATA_KEY: &str = "openrtc.required-capabilities.v1";
7pub const MAX_CAPABILITY_BYTES: usize = 4096;
8pub const MAX_SAFE_FILE_BYTES: u64 = 9_007_199_254_740_991;
9
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
11#[serde(rename_all = "camelCase", deny_unknown_fields)]
12pub struct RequiredTransferCapabilities {
13 pub version: u8,
14 pub durable_resume: bool,
15 pub completion_receipt: bool,
16 pub extensions: Vec<String>,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
20#[serde(rename_all = "camelCase", deny_unknown_fields)]
21pub struct TransferCapabilities {
22 pub version: u8,
23 pub durable_resume: bool,
24 pub completion_receipt: bool,
25 pub extensions: Vec<String>,
26 pub receive_available: bool,
27 pub max_file_bytes: u64,
28}
29
30fn normalize_extensions(extensions: &mut [String]) -> Result<(), TransferError> {
31 if extensions.len() > 16
32 || extensions.iter().any(|extension| {
33 extension.len() > 128
34 || extension
35 .split(['.', '_', '-'])
36 .enumerate()
37 .any(|(index, part)| {
38 part.is_empty()
39 || !part
40 .bytes()
41 .all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit())
42 || index == 0 && !part.as_bytes()[0].is_ascii_lowercase()
43 })
44 })
45 {
46 return Err(TransferError::Protocol(
47 "invalid transfer capability extensions".into(),
48 ));
49 }
50 extensions.sort();
51 if extensions.windows(2).any(|pair| pair[0] == pair[1]) {
52 return Err(TransferError::Protocol(
53 "duplicate transfer capability extension".into(),
54 ));
55 }
56 Ok(())
57}
58impl RequiredTransferCapabilities {
59 pub fn normalized(mut self) -> Result<Self, TransferError> {
60 if self.version != 1 {
61 return Err(TransferError::Protocol(
62 "invalid transfer capability version".into(),
63 ));
64 }
65 normalize_extensions(&mut self.extensions)?;
66 Ok(self)
67 }
68}
69impl TransferCapabilities {
70 pub fn normalized(mut self) -> Result<Self, TransferError> {
71 if self.version != 1 || self.max_file_bytes > MAX_SAFE_FILE_BYTES {
72 return Err(TransferError::Protocol(
73 "invalid transfer capability version or file limit".into(),
74 ));
75 }
76 normalize_extensions(&mut self.extensions)?;
77 Ok(self)
78 }
79
80 pub fn require(&self, header: &FileTransferHeader) -> Result<(), TransferError> {
83 let caps = self.clone().normalized()?;
84 let required = header.required_capabilities()?;
85 if !caps.receive_available
86 || header.size > caps.max_file_bytes
87 || required.is_some_and(|r| {
88 r.durable_resume && !caps.durable_resume
89 || r.completion_receipt && !caps.completion_receipt
90 || r.extensions
91 .iter()
92 .any(|extension| !caps.extensions.contains(extension))
93 })
94 {
95 return Err(TransferError::Protocol("transfer_capability_required: receiver cannot admit the selected size or required transfer features".into()));
96 }
97 Ok(())
98 }
99}
100impl FileTransferHeader {
101 pub fn required_capabilities(
102 &self,
103 ) -> Result<Option<RequiredTransferCapabilities>, TransferError> {
104 let Some(encoded) = self.metadata.get(REQUIRED_CAPABILITIES_METADATA_KEY) else {
105 return Ok(None);
106 };
107 if encoded.len() > MAX_CAPABILITY_BYTES {
108 return Err(TransferError::Limit(
109 "required transfer capability metadata is too large".into(),
110 ));
111 }
112 let required: RequiredTransferCapabilities = serde_json::from_str(encoded)?;
113 Ok(Some(required.normalized()?))
114 }
115
116 pub fn with_required_capabilities(
117 mut self,
118 required: RequiredTransferCapabilities,
119 ) -> Result<Self, TransferError> {
120 let encoded = serde_json::to_string(&required.normalized()?)?;
121 if self
122 .metadata
123 .get(REQUIRED_CAPABILITIES_METADATA_KEY)
124 .is_some_and(|old| old != &encoded)
125 {
126 return Err(TransferError::Protocol("transfer_identity_conflict: required transfer capabilities differ from the accepted header".into()));
127 }
128 self.metadata
129 .insert(REQUIRED_CAPABILITIES_METADATA_KEY.into(), encoded);
130 Ok(self)
131 }
132}