1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
use super::Error;
use super::WriterAction;
use crate::crypt_file_data;
use crate::crypt_name_bytes;
use crate::crypt_u32;
use crate::DEFAULT_KEY;
use crate::HEADER_LEN;
use crate::MAGIC;
use crate::MAGIC_LEN;
use crate::MAX_FILE_NAME_LEN;
use crate::U32_LEN;
use crate::VERSION;

const DEFAULT_BUFFER_CAPACITY: usize = 10 * 1024;

/// A sans-io writer state machine.
#[derive(Debug)]
pub struct Writer {
    buffer: oval::Buffer,
    key: u32,
    state: State,
    remaining: u32,
    last_file_size: Option<u32>,
}

impl Writer {
    /// Create a new writer state machine.
    pub fn new() -> Self {
        Self {
            buffer: oval::Buffer::with_capacity(DEFAULT_BUFFER_CAPACITY),
            key: DEFAULT_KEY,
            state: State::Header,
            remaining: 0,
            last_file_size: None,
        }
    }

    /// Get a reference to the output data buffer where data should be taken from.
    ///
    /// The amount of data copied should be marked with [`consume`].
    pub fn data(&self) -> &[u8] {
        self.buffer.data()
    }

    /// Get a reference to the space data buffer where data should inserted.
    pub fn space(&mut self) -> &mut [u8] {
        self.buffer.space()
    }

    /// Consume a number of bytes from the output buffer.
    pub fn consume(&mut self, size: usize) {
        self.buffer.consume(size);
    }

    /// Step the state machine, performing the action of writing the header.
    ///
    /// If the header has already been written, `Ok(Writer::Done(()))` is returned and no work is performed.
    /// Calling this method is optional.
    /// The state machine will automatically write the header is if has not been written.
    pub fn step_write_header(&mut self) -> Result<WriterAction<()>, Error> {
        match self.state {
            State::Header => {}
            State::FileHeader | State::FileData { .. } => {
                return Ok(WriterAction::Done(()));
            }
        }

        let space = self.buffer.space();
        if space.len() < HEADER_LEN {
            return Ok(WriterAction::Write);
        }

        space[..MAGIC_LEN].copy_from_slice(&MAGIC);
        space[MAGIC_LEN] = VERSION;
        self.buffer.fill(HEADER_LEN);

        self.state = State::FileHeader;

        Ok(WriterAction::Done(()))
    }

    /// Step the state machine, performing the action of writing the next file header.
    ///
    /// This will write the header if it has not been written already.
    pub fn step_write_file_header(
        &mut self,
        name: &str,
        size: u32,
    ) -> Result<WriterAction<()>, Error> {
        loop {
            match self.state {
                State::Header => {
                    let action = self.step_write_header()?;
                    if !action.is_done() {
                        return Ok(action);
                    }
                }
                State::FileHeader => {
                    break;
                }
                State::FileData { .. } => {
                    // We never transition to the FileData state without setting the last_file_size field.
                    let size = self.last_file_size.unwrap();
                    return Err(Error::FileDataSizeMismatch {
                        actual: size - self.remaining,
                        expected: size,
                    });
                }
            }
        }

        let name_len = name.len();
        if name_len > usize::try_from(MAX_FILE_NAME_LEN).unwrap() {
            return Err(Error::FileNameTooLongUsize { len: name_len });
        }
        // We check the name size above.
        let name_len_u32 = u32::try_from(name_len).unwrap();

        let space = self.buffer.space();
        let file_header_size = (U32_LEN * 2) + name_len;
        if space.len() < file_header_size {
            return Ok(WriterAction::Write);
        }

        let mut key = self.key;

        let data = crypt_u32(&mut key, name_len_u32);
        let (bytes, space) = space.split_at_mut(U32_LEN);
        bytes.copy_from_slice(&data.to_le_bytes());

        let (bytes, space) = space.split_at_mut(name_len);
        bytes.copy_from_slice(name.as_bytes());
        crypt_name_bytes(&mut key, bytes);

        let data = crypt_u32(&mut key, size);
        let (bytes, _space) = space.split_at_mut(U32_LEN);
        bytes.copy_from_slice(&data.to_le_bytes());

        self.key = key;
        self.buffer.fill(file_header_size);

        self.state = State::FileData { key, counter: 0 };
        self.remaining = size;
        self.last_file_size = Some(size);

        Ok(WriterAction::Done(()))
    }

    /// Step the state machine, performing the action of writing the file data.
    ///
    /// This will write the header if it has not been written already.
    /// Populate the space buffer with the bytes to write, then pass the number of bytes written to this function.
    pub fn step_write_file_data(&mut self, size: usize) -> Result<WriterAction<usize>, Error> {
        let (key, counter) = loop {
            match &mut self.state {
                State::Header => {
                    let action = self.step_write_header()?;
                    if !action.is_done() {
                        return Ok(action.map_done(|_| unreachable!()));
                    }
                }
                State::FileHeader => match self.last_file_size {
                    Some(size) => {
                        return Err(Error::FileDataSizeMismatch {
                            actual: size - self.remaining,
                            expected: size,
                        });
                    }
                    None => {
                        return Err(Error::InvalidState);
                    }
                },
                State::FileData { key, counter, .. } => {
                    break (key, counter);
                }
            }
        };

        let size_u32 = u32::try_from(size).expect("number of bytes written cannot fit in a `u32`");

        let space = self.buffer.space();
        crypt_file_data(key, counter, &mut space[..size]);

        self.remaining -= size_u32;
        if self.remaining == 0 {
            self.state = State::FileHeader;
        }

        self.buffer.fill(size);

        Ok(WriterAction::Done(size))
    }
}

impl Default for Writer {
    fn default() -> Self {
        Self::new()
    }
}

#[derive(Debug)]
enum State {
    Header,
    FileHeader,
    FileData { key: u32, counter: u8 },
}