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
mod reader;
mod writer;

pub use self::reader::Reader;
pub use self::writer::Writer;
use crate::MAX_FILE_NAME_LEN;

/// An error that may occur while using sans-io state machines.
#[derive(Debug)]
pub enum Error {
    /// Invalid magic number
    InvalidMagic { magic: [u8; 7] },

    /// Invalid version
    InvalidVersion { version: u8 },

    /// File name was too long
    FileNameTooLongU32 { len: u32 },

    /// File name was too long
    FileNameTooLongUsize { len: usize },

    /// A file name was invalid.
    InvalidFileName {
        /// The error
        error: std::string::FromUtf8Error,
    },

    /// The provided file size does not match the file data's size.
    FileDataSizeMismatch { actual: u32, expected: u32 },

    /// Invalid internal state, user error
    InvalidState,
}

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            Self::InvalidMagic { magic } => write!(f, "magic number \"{magic:?}\" is invalid"),
            Self::InvalidVersion { version } => write!(f, "version \"{version}\" is invalid"),
            Self::FileNameTooLongU32 { len } => write!(
                f,
                "file name {len} is too long, max length is {MAX_FILE_NAME_LEN}"
            ),
            Self::FileNameTooLongUsize { len } => write!(
                f,
                "file name {len} is too long, max length is {MAX_FILE_NAME_LEN}"
            ),
            Self::InvalidFileName { .. } => write!(f, "invalid file name"),
            Self::FileDataSizeMismatch { actual, expected } => write!(
                f,
                "file data size mismatch, expected {expected} but got {actual}"
            ),
            Self::InvalidState => {
                write!(f, "programmer error, invalid internal state for function")
            }
        }
    }
}

impl std::error::Error for Error {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Self::InvalidFileName { error } => Some(error),
            _ => None,
        }
    }
}

/// An action that should be performed for the reader state machine, or a result.
#[derive(Debug, Copy, Clone)]
pub enum ReaderAction<T> {
    /// Read at least the given number of bytes before stepping again.
    Read(usize),

    /// Seek to the given position before stepping again.
    Seek(u64),

    /// The stepping function is done.
    Done(T),
}

impl<T> ReaderAction<T> {
    /// Returns true if this is a `Done` variant.
    pub fn is_done(&self) -> bool {
        matches!(self, Self::Done(_))
    }

    /// Map the done variant.
    fn map_done<F, O>(self, f: F) -> ReaderAction<O>
    where
        F: FnOnce(T) -> O,
    {
        match self {
            Self::Read(n) => ReaderAction::Read(n),
            Self::Seek(p) => ReaderAction::Seek(p),
            Self::Done(v) => ReaderAction::Done(f(v)),
        }
    }
}

/// An action that should be performed for the writer state machine, or a result..
#[derive(Debug, Copy, Clone)]
pub enum WriterAction<T> {
    /// The writer buffer should be emptied.
    Write,

    /// The stepping function is done.
    Done(T),
}

impl<T> WriterAction<T> {
    /// Returns true if this is a `Done` variant.
    pub fn is_done(&self) -> bool {
        matches!(self, Self::Done(_))
    }

    /// Map the done variant.
    fn map_done<F, O>(self, f: F) -> WriterAction<O>
    where
        F: FnOnce(T) -> O,
    {
        match self {
            Self::Write => WriterAction::Write,
            Self::Done(v) => WriterAction::Done(f(v)),
        }
    }
}

/// A file header
#[derive(Debug)]
pub struct FileHeader {
    /// The file name
    pub name: String,

    /// The file data size.
    pub size: u32,
}