bitwarden_ipc/fragmentation/
mod.rs1mod reassembler;
21mod split;
22
23pub(crate) use reassembler::{REASSEMBLY_IDLE_TIMEOUT, REASSEMBLY_TIMEOUT, Reassembler};
24use serde::{Deserialize, Serialize};
25pub(crate) use split::{FragmentationError, Fragments};
26
27pub(crate) const FRAGMENT_TOPIC: &str = "IpcFragment";
30
31pub(crate) const MAX_PAYLOAD_SIZE: usize = 5 * 1024 * 1024;
34
35const MAX_HEADER_LEN: usize = 128;
37
38#[derive(Debug, Serialize, Deserialize)]
40struct FragmentHeader {
41 message_id: u64,
43 index: u32,
44 total_len: u32,
46}
47
48struct FragmentFrame<'a> {
51 header: FragmentHeader,
52 data: &'a [u8],
53}
54
55impl<'a> FragmentFrame<'a> {
56 fn encode(&self) -> Vec<u8> {
57 let mut payload = Vec::with_capacity(MAX_HEADER_LEN + self.data.len());
58 ciborium::into_writer(&self.header, &mut payload)
59 .expect("Ciborium serialization should not fail");
60 payload.extend_from_slice(self.data);
61 payload
62 }
63
64 fn decode(payload: &'a [u8]) -> Option<Self> {
65 let mut data = payload;
66 let header = ciborium::from_reader(&mut data).ok()?;
67 Some(Self { header, data })
68 }
69}
70
71type OriginalTopic = Option<String>;
73
74#[cfg(test)]
75pub(crate) mod test_support {
76 use super::*;
77 use crate::{
78 endpoint::{Endpoint, HostId, Source},
79 message::{IncomingMessage, OutgoingMessage},
80 };
81
82 pub(crate) const MAX_LEN: usize = 64 * 1024;
84 pub(crate) const FRAGMENT_LEN: usize = MAX_LEN - MAX_HEADER_LEN;
85
86 pub(crate) fn large_payload(len: usize, seed: u8) -> Vec<u8> {
87 (0..len).map(|i| (i % 251) as u8 ^ seed).collect()
88 }
89
90 pub(crate) fn foreground_source(id: i32) -> Source {
91 Source::BrowserForeground { id }
92 }
93
94 pub(crate) fn web_source(tab_id: i32) -> Source {
95 Source::Web {
96 tab_id,
97 document_id: "doc".to_owned(),
98 origin: "https://example.com".to_owned(),
99 }
100 }
101
102 pub(crate) fn outgoing(payload: Vec<u8>, topic: &str) -> OutgoingMessage {
103 OutgoingMessage {
104 payload,
105 destination: Endpoint::BrowserBackground { id: HostId::Own },
106 topic: Some(topic.to_owned()),
107 }
108 }
109
110 pub(crate) fn incoming(message: OutgoingMessage, source: Source) -> IncomingMessage {
111 IncomingMessage {
112 payload: message.payload,
113 destination: message.destination,
114 source,
115 topic: message.topic,
116 }
117 }
118
119 pub(crate) fn crafted(
121 source: Source,
122 message_id: u64,
123 index: u32,
124 total_len: usize,
125 ) -> IncomingMessage {
126 let payload = FragmentFrame {
127 header: FragmentHeader {
128 message_id,
129 index,
130 total_len: total_len as u32,
131 },
132 data: &[0; 16],
133 }
134 .encode();
135 incoming(
136 outgoing(payload, &Fragments::fragment_topic(Some("bulk"))),
137 source,
138 )
139 }
140
141 pub(crate) fn split_all(
142 message: OutgoingMessage,
143 max_payload_len: usize,
144 ) -> Vec<OutgoingMessage> {
145 Fragments::split(message, max_payload_len)
146 .unwrap()
147 .collect()
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::{test_support::*, *};
154
155 #[test]
156 fn fragment_topic_round_trips_and_other_topics_pass_through() {
157 for topic in [None, Some(""), Some("bulk"), Some("a/b")] {
158 assert_eq!(
159 Reassembler::original_topic(Some(&Fragments::fragment_topic(topic))),
160 Some(topic.map(str::to_owned))
161 );
162 }
163 for topic in [
164 None,
165 Some("bulk"),
166 Some("IpcFragments"),
167 Some("IpcFragment-x"),
168 ] {
169 assert_eq!(Reassembler::original_topic(topic), None);
170 }
171 }
172
173 #[test]
174 fn largest_header_fits_and_decodes_up_to_its_data() {
175 let header = || FragmentHeader {
176 message_id: u64::MAX,
177 index: u32::MAX,
178 total_len: u32::MAX,
179 };
180 let empty = FragmentFrame {
181 header: header(),
182 data: &[],
183 };
184 assert!(empty.encode().len() <= MAX_HEADER_LEN);
185
186 let data = large_payload(FRAGMENT_LEN, 0);
187 let payload = FragmentFrame {
188 header: header(),
189 data: &data,
190 }
191 .encode();
192 assert!(payload.len() <= MAX_LEN);
193 let decoded = FragmentFrame::decode(&payload).unwrap();
194 assert_eq!(decoded.header.message_id, u64::MAX);
195 assert_eq!(decoded.data, data);
196 }
197}