trussed_chunked/
lib.rs

1// Copyright (C) Nitrokey GmbH
2// SPDX-License-Identifier: Apache-2.0 or MIT
3
4#![no_std]
5#![warn(non_ascii_idents, trivial_casts, unused, unused_qualifications)]
6#![deny(unsafe_code)]
7
8pub mod utils;
9
10use serde::{Deserialize, Serialize};
11use serde_byte_array::ByteArray;
12use trussed_core::{
13    serde_extensions::{Extension, ExtensionClient, ExtensionResult},
14    types::{KeyId, Location, Message, PathBuf, UserAttribute},
15    FilesystemClient,
16};
17
18pub const CHACHA8_STREAM_NONCE_LEN: usize = 8;
19
20#[derive(Debug, Default)]
21pub struct ChunkedExtension;
22
23impl Extension for ChunkedExtension {
24    type Request = ChunkedRequest;
25    type Reply = ChunkedReply;
26}
27
28#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
29#[allow(missing_docs, clippy::large_enum_variant)]
30pub enum ChunkedRequest {
31    StartChunkedWrite(request::StartChunkedWrite),
32    StartEncryptedChunkedWrite(request::StartEncryptedChunkedWrite),
33    StartChunkedRead(request::StartChunkedRead),
34    StartEncryptedChunkedRead(request::StartEncryptedChunkedRead),
35    ReadChunk(request::ReadChunk),
36    WriteChunk(request::WriteChunk),
37    AbortChunkedWrite(request::AbortChunkedWrite),
38    PartialReadFile(request::PartialReadFile),
39    AppendFile(request::AppendFile),
40}
41
42#[derive(Debug, Deserialize, Serialize)]
43#[allow(missing_docs)]
44pub enum ChunkedReply {
45    ReadChunk(reply::ReadChunk),
46    StartChunkedWrite(reply::StartChunkedWrite),
47    StartEncryptedChunkedWrite(reply::StartEncryptedChunkedWrite),
48    StartChunkedRead(reply::StartChunkedRead),
49    StartEncryptedChunkedRead(reply::StartEncryptedChunkedRead),
50    WriteChunk(reply::WriteChunk),
51    AbortChunkedWrite(reply::AbortChunkedWrite),
52    PartialReadFile(reply::PartialReadFile),
53    AppendFile(reply::AppendFile),
54}
55
56pub mod request {
57    use super::*;
58    use serde::{Deserialize, Serialize};
59    use serde_byte_array::ByteArray;
60    use trussed_core::types::{KeyId, Location, Message, PathBuf, UserAttribute};
61    use trussed_core::Error;
62
63    #[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
64    pub struct ReadChunk {}
65
66    impl TryFrom<ChunkedRequest> for ReadChunk {
67        type Error = Error;
68        fn try_from(request: ChunkedRequest) -> Result<Self, Self::Error> {
69            match request {
70                ChunkedRequest::ReadChunk(request) => Ok(request),
71                _ => Err(Error::InternalError),
72            }
73        }
74    }
75
76    impl From<ReadChunk> for ChunkedRequest {
77        fn from(request: ReadChunk) -> Self {
78            Self::ReadChunk(request)
79        }
80    }
81
82    #[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
83    pub struct StartChunkedWrite {
84        pub location: Location,
85        pub path: PathBuf,
86        pub user_attribute: Option<UserAttribute>,
87    }
88
89    impl TryFrom<ChunkedRequest> for StartChunkedWrite {
90        type Error = Error;
91        fn try_from(request: ChunkedRequest) -> Result<Self, Self::Error> {
92            match request {
93                ChunkedRequest::StartChunkedWrite(request) => Ok(request),
94                _ => Err(Error::InternalError),
95            }
96        }
97    }
98
99    impl From<StartChunkedWrite> for ChunkedRequest {
100        fn from(request: StartChunkedWrite) -> Self {
101            Self::StartChunkedWrite(request)
102        }
103    }
104
105    #[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
106    pub struct StartEncryptedChunkedWrite {
107        pub location: Location,
108        pub path: PathBuf,
109        pub user_attribute: Option<UserAttribute>,
110        pub key: KeyId,
111        pub nonce: Option<ByteArray<CHACHA8_STREAM_NONCE_LEN>>,
112    }
113
114    impl TryFrom<ChunkedRequest> for StartEncryptedChunkedWrite {
115        type Error = Error;
116        fn try_from(request: ChunkedRequest) -> Result<Self, Self::Error> {
117            match request {
118                ChunkedRequest::StartEncryptedChunkedWrite(request) => Ok(request),
119                _ => Err(Error::InternalError),
120            }
121        }
122    }
123
124    impl From<StartEncryptedChunkedWrite> for ChunkedRequest {
125        fn from(request: StartEncryptedChunkedWrite) -> Self {
126            Self::StartEncryptedChunkedWrite(request)
127        }
128    }
129
130    #[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
131    pub struct StartChunkedRead {
132        pub location: Location,
133        pub path: PathBuf,
134    }
135
136    impl TryFrom<ChunkedRequest> for StartChunkedRead {
137        type Error = Error;
138        fn try_from(request: ChunkedRequest) -> Result<Self, Self::Error> {
139            match request {
140                ChunkedRequest::StartChunkedRead(request) => Ok(request),
141                _ => Err(Error::InternalError),
142            }
143        }
144    }
145
146    impl From<StartChunkedRead> for ChunkedRequest {
147        fn from(request: StartChunkedRead) -> Self {
148            Self::StartChunkedRead(request)
149        }
150    }
151
152    #[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
153    pub struct StartEncryptedChunkedRead {
154        pub location: Location,
155        pub path: PathBuf,
156        pub key: KeyId,
157    }
158
159    impl TryFrom<ChunkedRequest> for StartEncryptedChunkedRead {
160        type Error = Error;
161        fn try_from(request: ChunkedRequest) -> Result<Self, Self::Error> {
162            match request {
163                ChunkedRequest::StartEncryptedChunkedRead(request) => Ok(request),
164                _ => Err(Error::InternalError),
165            }
166        }
167    }
168
169    impl From<StartEncryptedChunkedRead> for ChunkedRequest {
170        fn from(request: StartEncryptedChunkedRead) -> Self {
171            Self::StartEncryptedChunkedRead(request)
172        }
173    }
174
175    #[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
176    pub struct WriteChunk {
177        pub data: Message,
178    }
179
180    impl TryFrom<ChunkedRequest> for WriteChunk {
181        type Error = Error;
182        fn try_from(request: ChunkedRequest) -> Result<Self, Self::Error> {
183            match request {
184                ChunkedRequest::WriteChunk(request) => Ok(request),
185                _ => Err(Error::InternalError),
186            }
187        }
188    }
189
190    impl From<WriteChunk> for ChunkedRequest {
191        fn from(request: WriteChunk) -> Self {
192            Self::WriteChunk(request)
193        }
194    }
195
196    #[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
197    pub struct AbortChunkedWrite {}
198
199    impl TryFrom<ChunkedRequest> for AbortChunkedWrite {
200        type Error = Error;
201        fn try_from(request: ChunkedRequest) -> Result<Self, Self::Error> {
202            match request {
203                ChunkedRequest::AbortChunkedWrite(request) => Ok(request),
204                _ => Err(Error::InternalError),
205            }
206        }
207    }
208
209    impl From<AbortChunkedWrite> for ChunkedRequest {
210        fn from(request: AbortChunkedWrite) -> Self {
211            Self::AbortChunkedWrite(request)
212        }
213    }
214
215    #[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
216    pub struct PartialReadFile {
217        pub location: Location,
218        pub path: PathBuf,
219        pub offset: usize,
220        pub length: usize,
221    }
222
223    impl TryFrom<ChunkedRequest> for PartialReadFile {
224        type Error = Error;
225        fn try_from(request: ChunkedRequest) -> Result<Self, Self::Error> {
226            match request {
227                ChunkedRequest::PartialReadFile(request) => Ok(request),
228                _ => Err(Error::InternalError),
229            }
230        }
231    }
232
233    impl From<PartialReadFile> for ChunkedRequest {
234        fn from(request: PartialReadFile) -> Self {
235            Self::PartialReadFile(request)
236        }
237    }
238
239    #[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
240    pub struct AppendFile {
241        pub location: Location,
242        pub path: PathBuf,
243        pub data: Message,
244    }
245
246    impl TryFrom<ChunkedRequest> for AppendFile {
247        type Error = Error;
248        fn try_from(request: ChunkedRequest) -> Result<Self, Self::Error> {
249            match request {
250                ChunkedRequest::AppendFile(request) => Ok(request),
251                _ => Err(Error::InternalError),
252            }
253        }
254    }
255
256    impl From<AppendFile> for ChunkedRequest {
257        fn from(request: AppendFile) -> Self {
258            Self::AppendFile(request)
259        }
260    }
261}
262
263pub mod reply {
264    use super::*;
265    use serde::{Deserialize, Serialize};
266    use trussed_core::types::Message;
267    use trussed_core::Error;
268
269    #[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
270    pub struct ReadChunk {
271        pub data: Message,
272        pub len: usize,
273    }
274
275    impl TryFrom<ChunkedReply> for ReadChunk {
276        type Error = Error;
277        fn try_from(reply: ChunkedReply) -> Result<Self, Self::Error> {
278            match reply {
279                ChunkedReply::ReadChunk(reply) => Ok(reply),
280                _ => Err(Error::InternalError),
281            }
282        }
283    }
284
285    impl From<ReadChunk> for ChunkedReply {
286        fn from(reply: ReadChunk) -> Self {
287            Self::ReadChunk(reply)
288        }
289    }
290
291    #[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
292    pub struct StartChunkedWrite {}
293
294    impl TryFrom<ChunkedReply> for StartChunkedWrite {
295        type Error = Error;
296        fn try_from(reply: ChunkedReply) -> Result<Self, Self::Error> {
297            match reply {
298                ChunkedReply::StartChunkedWrite(reply) => Ok(reply),
299                _ => Err(Error::InternalError),
300            }
301        }
302    }
303
304    impl From<StartChunkedWrite> for ChunkedReply {
305        fn from(reply: StartChunkedWrite) -> Self {
306            Self::StartChunkedWrite(reply)
307        }
308    }
309
310    #[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
311    pub struct StartEncryptedChunkedWrite {}
312
313    impl TryFrom<ChunkedReply> for StartEncryptedChunkedWrite {
314        type Error = Error;
315        fn try_from(reply: ChunkedReply) -> Result<Self, Self::Error> {
316            match reply {
317                ChunkedReply::StartEncryptedChunkedWrite(reply) => Ok(reply),
318                _ => Err(Error::InternalError),
319            }
320        }
321    }
322
323    impl From<StartEncryptedChunkedWrite> for ChunkedReply {
324        fn from(reply: StartEncryptedChunkedWrite) -> Self {
325            Self::StartEncryptedChunkedWrite(reply)
326        }
327    }
328
329    #[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
330    pub struct StartChunkedRead {
331        pub data: Message,
332        pub len: usize,
333    }
334
335    impl TryFrom<ChunkedReply> for StartChunkedRead {
336        type Error = Error;
337        fn try_from(reply: ChunkedReply) -> Result<Self, Self::Error> {
338            match reply {
339                ChunkedReply::StartChunkedRead(reply) => Ok(reply),
340                _ => Err(Error::InternalError),
341            }
342        }
343    }
344
345    impl From<StartChunkedRead> for ChunkedReply {
346        fn from(reply: StartChunkedRead) -> Self {
347            Self::StartChunkedRead(reply)
348        }
349    }
350
351    #[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
352    pub struct StartEncryptedChunkedRead {}
353
354    impl TryFrom<ChunkedReply> for StartEncryptedChunkedRead {
355        type Error = Error;
356        fn try_from(reply: ChunkedReply) -> Result<Self, Self::Error> {
357            match reply {
358                ChunkedReply::StartEncryptedChunkedRead(reply) => Ok(reply),
359                _ => Err(Error::InternalError),
360            }
361        }
362    }
363
364    impl From<StartEncryptedChunkedRead> for ChunkedReply {
365        fn from(reply: StartEncryptedChunkedRead) -> Self {
366            Self::StartEncryptedChunkedRead(reply)
367        }
368    }
369
370    #[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
371    pub struct WriteChunk {}
372
373    impl TryFrom<ChunkedReply> for WriteChunk {
374        type Error = Error;
375        fn try_from(reply: ChunkedReply) -> Result<Self, Self::Error> {
376            match reply {
377                ChunkedReply::WriteChunk(reply) => Ok(reply),
378                _ => Err(Error::InternalError),
379            }
380        }
381    }
382
383    impl From<WriteChunk> for ChunkedReply {
384        fn from(reply: WriteChunk) -> Self {
385            Self::WriteChunk(reply)
386        }
387    }
388
389    #[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
390    pub struct AbortChunkedWrite {
391        pub aborted: bool,
392    }
393
394    impl TryFrom<ChunkedReply> for AbortChunkedWrite {
395        type Error = Error;
396        fn try_from(reply: ChunkedReply) -> Result<Self, Self::Error> {
397            match reply {
398                ChunkedReply::AbortChunkedWrite(reply) => Ok(reply),
399                _ => Err(Error::InternalError),
400            }
401        }
402    }
403
404    impl From<AbortChunkedWrite> for ChunkedReply {
405        fn from(reply: AbortChunkedWrite) -> Self {
406            Self::AbortChunkedWrite(reply)
407        }
408    }
409
410    #[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
411    pub struct PartialReadFile {
412        pub data: Message,
413        pub file_length: usize,
414    }
415
416    impl TryFrom<ChunkedReply> for PartialReadFile {
417        type Error = Error;
418        fn try_from(reply: ChunkedReply) -> Result<Self, Self::Error> {
419            match reply {
420                ChunkedReply::PartialReadFile(reply) => Ok(reply),
421                _ => Err(Error::InternalError),
422            }
423        }
424    }
425
426    impl From<PartialReadFile> for ChunkedReply {
427        fn from(reply: PartialReadFile) -> Self {
428            Self::PartialReadFile(reply)
429        }
430    }
431
432    #[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
433    pub struct AppendFile {
434        pub file_length: usize,
435    }
436
437    impl TryFrom<ChunkedReply> for AppendFile {
438        type Error = Error;
439        fn try_from(reply: ChunkedReply) -> Result<Self, Self::Error> {
440            match reply {
441                ChunkedReply::AppendFile(reply) => Ok(reply),
442                _ => Err(Error::InternalError),
443            }
444        }
445    }
446
447    impl From<AppendFile> for ChunkedReply {
448        fn from(reply: AppendFile) -> Self {
449            Self::AppendFile(reply)
450        }
451    }
452}
453
454pub type ChunkedResult<'a, R, C> = ExtensionResult<'a, ChunkedExtension, R, C>;
455
456pub trait ChunkedClient: ExtensionClient<ChunkedExtension> + FilesystemClient {
457    /// Begin writing a file that can be larger than 1KiB
458    ///
459    /// More chunks can be written with [`write_file_chunk`](ChunkedClient::write_file_chunk).
460    /// The data is flushed and becomes readable when a chunk smaller than the maximum capacity of a `Message` is transfered.
461    fn start_chunked_write(
462        &mut self,
463        location: Location,
464        path: PathBuf,
465        user_attribute: Option<UserAttribute>,
466    ) -> ChunkedResult<'_, reply::StartChunkedWrite, Self> {
467        self.extension(request::StartChunkedWrite {
468            location,
469            path,
470            user_attribute,
471        })
472    }
473
474    /// Begin writing an encrypted file that can be larger than 1KiB
475    ///
476    /// More chunks can be written with [`write_file_chunk`](ChunkedClient::write_file_chunk).
477    /// The data is flushed and becomes readable when a chunk smaller than the maximum capacity of a [`Message`] is transfered.
478    fn start_encrypted_chunked_write(
479        &mut self,
480        location: Location,
481        path: PathBuf,
482        key: KeyId,
483        nonce: Option<ByteArray<CHACHA8_STREAM_NONCE_LEN>>,
484        user_attribute: Option<UserAttribute>,
485    ) -> ChunkedResult<'_, reply::StartEncryptedChunkedWrite, Self> {
486        self.extension(request::StartEncryptedChunkedWrite {
487            location,
488            path,
489            key,
490            user_attribute,
491            nonce,
492        })
493    }
494
495    /// Begin reading a file that can be larger than 1KiB
496    ///
497    /// More chunks can be read with [`read_file_chunk`](ChunkedClient::read_file_chunk).
498    /// The read is over once a chunk of size smaller than the maximum capacity of a [`Message`] is transfered.
499    fn start_chunked_read(
500        &mut self,
501        location: Location,
502        path: PathBuf,
503    ) -> ChunkedResult<'_, reply::StartChunkedRead, Self> {
504        self.extension(request::StartChunkedRead { location, path })
505    }
506
507    /// Begin reading an encrypted file that can be larger than 1KiB
508    ///
509    /// More chunks can be read with [`read_file_chunk`](ChunkedClient::read_file_chunk).
510    /// The read is over once a chunk of size smaller than the maximum capacity of a [`Message`] is transfered.
511    /// Only once the entire file has been read does the data have been properly authenticated.
512    fn start_encrypted_chunked_read(
513        &mut self,
514        location: Location,
515        path: PathBuf,
516        key: KeyId,
517    ) -> ChunkedResult<'_, reply::StartEncryptedChunkedRead, Self> {
518        self.extension(request::StartEncryptedChunkedRead {
519            location,
520            path,
521            key,
522        })
523    }
524
525    /// Write part of a file
526    ///
527    /// See [`start_chunked_write`](ChunkedClient::start_chunked_write).
528    fn write_file_chunk(&mut self, data: Message) -> ChunkedResult<'_, reply::WriteChunk, Self> {
529        self.extension(request::WriteChunk { data })
530    }
531
532    /// Abort writes to a file opened with [`start_chunked_write`](ChunkedClient::start_chunked_write).
533    fn abort_chunked_write(&mut self) -> ChunkedResult<'_, reply::AbortChunkedWrite, Self> {
534        self.extension(request::AbortChunkedWrite {})
535    }
536
537    // Read part of a file, up to 1KiB starting at `pos`
538    fn read_file_chunk(&mut self) -> ChunkedResult<'_, reply::ReadChunk, Self> {
539        self.extension(request::ReadChunk {})
540    }
541
542    /// Partially read a file from a given offset, returning a chunk of the given length and the
543    /// total file size.
544    ///
545    /// If the length is greater than [`trussed_core::config::MAX_MESSAGE_LENGTH`][] or if the offset is
546    /// greater than the file size, an error is returned.
547    fn partial_read_file(
548        &mut self,
549        location: Location,
550        path: PathBuf,
551        offset: usize,
552        length: usize,
553    ) -> ChunkedResult<'_, reply::PartialReadFile, Self> {
554        self.extension(request::PartialReadFile {
555            location,
556            path,
557            offset,
558            length,
559        })
560    }
561
562    /// Append data to an existing file and return the size of the file after the write.
563    fn append_file(
564        &mut self,
565        location: Location,
566        path: PathBuf,
567        data: Message,
568    ) -> ChunkedResult<'_, reply::AppendFile, Self> {
569        self.extension(request::AppendFile {
570            location,
571            path,
572            data,
573        })
574    }
575}
576
577impl<C: ExtensionClient<ChunkedExtension> + FilesystemClient> ChunkedClient for C {}