1#![cfg_attr(not(feature = "std"), no_std)]
49#![allow(async_fn_in_trait)]
50
51#[cfg(not(any(feature = "alloc", feature = "heapless")))]
52compile_error!(
53 "thincan-file-transfer requires either `alloc` (or `std`) or `heapless` (for no-alloc)."
54);
55
56#[cfg(feature = "alloc")]
57extern crate alloc;
58
59#[doc(hidden)]
62pub use capnp;
63
64use core::marker::PhantomData;
65
66capnp::generated_code!(pub mod file_transfer_capnp);
67pub use file_transfer_capnp as schema;
68
69pub const DEFAULT_CHUNK_SIZE: usize = 64;
71
72pub const fn capnp_padded_len(len: usize) -> usize {
74 (len + 7) & !7
75}
76
77pub const fn file_req_max_encoded_len() -> usize {
79 40
80}
81
82pub const fn file_offer_max_encoded_len(metadata_len: usize, hash_len: usize) -> usize {
84 40 + capnp_padded_len(metadata_len) + capnp_padded_len(hash_len)
85}
86
87pub const fn file_chunk_max_encoded_len(data_len: usize) -> usize {
89 32 + capnp_padded_len(data_len)
90}
91
92pub const fn file_ack_max_encoded_len() -> usize {
94 24
95}
96
97pub const fn capnp_scratch_words_for_bytes(bytes: usize) -> usize {
99 bytes.div_ceil(8)
100}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104#[derive(Default)]
105pub struct ReceiverConfig {
106 pub max_chunk_size: u32,
111}
112
113
114pub trait Atlas {
116 type FileReq: thincan::CapnpMessage;
117 type FileChunk: thincan::CapnpMessage;
118 type FileAck: thincan::CapnpMessage;
119}
120
121pub const FILE_TRANSFER_MESSAGE_COUNT: usize = 3;
123
124#[derive(Clone, Copy, Debug, Default)]
126pub struct FileTransferBundle<A>(PhantomData<A>);
127
128impl<A> thincan::BundleSpec<FILE_TRANSFER_MESSAGE_COUNT> for FileTransferBundle<A>
129where
130 A: Atlas,
131{
132 const MESSAGE_IDS: [u16; FILE_TRANSFER_MESSAGE_COUNT] = [
133 <A::FileReq as thincan::Message>::ID,
134 <A::FileChunk as thincan::Message>::ID,
135 <A::FileAck as thincan::Message>::ID,
136 ];
137}
138
139pub trait AsyncFileStore {
144 type Error;
145 type WriteHandle;
146
147 async fn begin_write(
148 &mut self,
149 transfer_id: u32,
150 total_len: u32,
151 ) -> Result<Self::WriteHandle, Self::Error>;
152
153 async fn write_at(
154 &mut self,
155 handle: &mut Self::WriteHandle,
156 offset: u32,
157 bytes: &[u8],
158 ) -> Result<(), Self::Error>;
159
160 async fn commit(&mut self, handle: Self::WriteHandle) -> Result<(), Self::Error>;
161
162 async fn abort(&mut self, handle: Self::WriteHandle);
163}
164
165#[derive(Debug)]
167pub enum Error<E> {
168 Store(E),
169 Protocol,
170 Capnp(capnp::Error),
171}
172
173#[derive(Debug, Clone, Copy, PartialEq, Eq)]
175pub struct PendingAck {
176 pub transfer_id: u32,
177 pub kind: schema::FileAckKind,
178 pub next_offset: u32,
179 pub chunk_size: u32,
180 pub error: schema::FileAckError,
181}
182
183#[derive(Debug, Clone, Copy, PartialEq, Eq)]
185pub struct FileReqValue<A> {
186 pub transfer_id: u32,
187 pub total_len: u32,
188 _atlas: PhantomData<A>,
189}
190
191#[derive(Debug, Clone, Copy, PartialEq, Eq)]
193pub struct FileOfferValue<'a, A> {
194 pub transfer_id: u32,
195 pub total_len: u32,
196 pub file_metadata: &'a [u8],
197 pub sender_max_chunk_size: u32,
198 pub file_hash_algo: schema::FileHashAlgo,
199 pub file_hash: &'a [u8],
200 _atlas: PhantomData<A>,
201}
202
203#[derive(Debug, Clone, Copy, PartialEq, Eq)]
205pub struct FileChunkValue<'a, A> {
206 pub transfer_id: u32,
207 pub offset: u32,
208 pub data: &'a [u8],
209 _atlas: PhantomData<A>,
210}
211
212impl<A> FileReqValue<A> {
213 pub fn new(transfer_id: u32, total_len: u32) -> Self {
215 Self {
216 transfer_id,
217 total_len,
218 _atlas: PhantomData,
219 }
220 }
221}
222
223pub fn file_req<A>(transfer_id: u32, total_len: u32) -> FileReqValue<A> {
225 FileReqValue::new(transfer_id, total_len)
226}
227
228impl<'a, A> FileOfferValue<'a, A> {
229 pub fn new(
231 transfer_id: u32,
232 total_len: u32,
233 sender_max_chunk_size: u32,
234 file_metadata: &'a [u8],
235 file_hash_algo: schema::FileHashAlgo,
236 file_hash: &'a [u8],
237 ) -> Self {
238 Self {
239 transfer_id,
240 total_len,
241 file_metadata,
242 sender_max_chunk_size,
243 file_hash_algo,
244 file_hash,
245 _atlas: PhantomData,
246 }
247 }
248}
249
250pub fn file_offer<'a, A>(
252 transfer_id: u32,
253 total_len: u32,
254 sender_max_chunk_size: u32,
255 file_metadata: &'a [u8],
256 file_hash_algo: schema::FileHashAlgo,
257 file_hash: &'a [u8],
258) -> FileOfferValue<'a, A> {
259 FileOfferValue::new(
260 transfer_id,
261 total_len,
262 sender_max_chunk_size,
263 file_metadata,
264 file_hash_algo,
265 file_hash,
266 )
267}
268
269impl<'a, A> FileChunkValue<'a, A> {
270 pub fn new(transfer_id: u32, offset: u32, data: &'a [u8]) -> Self {
272 Self {
273 transfer_id,
274 offset,
275 data,
276 _atlas: PhantomData,
277 }
278 }
279}
280
281pub fn file_chunk<'a, A>(transfer_id: u32, offset: u32, data: &'a [u8]) -> FileChunkValue<'a, A> {
283 FileChunkValue::new(transfer_id, offset, data)
284}
285
286#[derive(Debug, Clone, Copy, PartialEq, Eq)]
288pub struct FileAckValue<A> {
289 pub transfer_id: u32,
290 pub kind: schema::FileAckKind,
291 pub next_offset: u32,
292 pub chunk_size: u32,
293 pub error: schema::FileAckError,
294 _atlas: PhantomData<A>,
295}
296
297impl<A> FileAckValue<A> {
298 pub fn new(
299 transfer_id: u32,
300 kind: schema::FileAckKind,
301 next_offset: u32,
302 chunk_size: u32,
303 error: schema::FileAckError,
304 ) -> Self {
305 Self {
306 transfer_id,
307 kind,
308 next_offset,
309 chunk_size,
310 error,
311 _atlas: PhantomData,
312 }
313 }
314}
315
316pub fn file_ack_accept<A>(transfer_id: u32, chunk_size: u32) -> FileAckValue<A> {
317 FileAckValue::new(
318 transfer_id,
319 schema::FileAckKind::Accept,
320 0,
321 chunk_size,
322 schema::FileAckError::None,
323 )
324}
325
326pub fn file_ack_progress<A>(transfer_id: u32, next_offset: u32) -> FileAckValue<A> {
327 FileAckValue::new(
328 transfer_id,
329 schema::FileAckKind::Ack,
330 next_offset,
331 0,
332 schema::FileAckError::None,
333 )
334}
335
336pub fn file_ack_reject<A>(transfer_id: u32, error: schema::FileAckError) -> FileAckValue<A> {
337 FileAckValue::new(transfer_id, schema::FileAckKind::Reject, 0, 0, error)
338}
339
340#[cfg(feature = "alloc")]
341mod alloc_encode;
342
343#[cfg(feature = "heapless")]
344mod heapless_encode;
345#[cfg(feature = "heapless")]
346pub use heapless_encode::{
347 CapnpScratch, decode_file_ack_fields, encode_file_ack_into, encode_file_chunk_into,
348 encode_file_offer_into,
349};
350
351#[cfg(any(feature = "tokio", feature = "embassy"))]
352mod tokio_impl;
353#[cfg(any(feature = "tokio", feature = "embassy"))]
354pub use tokio_impl::{
355 Ack, FileTransferBundleInstance, SendConfig, SendFileResult, SendState, decode_file_ack,
356};
357
358#[cfg(feature = "alloc")]
359#[cfg(any(feature = "tokio", feature = "embassy"))]
360pub use tokio_impl::RecvFileResult;
361
362#[cfg(feature = "heapless")]
363#[cfg(any(feature = "tokio", feature = "embassy"))]
364pub use tokio_impl::RecvFileResultNoAlloc;
365
366