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
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
use std::fmt;
use std::error::Error;

pub mod tool {
    use super::fmt;
    use super::Error;

    use std::string::FromUtf8Error;

    #[derive(Debug)]
    pub enum ToolError {
        ReadVintOverflow,
        WriteVintOverflow(u64),
        WriteSignedVintOverflow(i64),
        ReadU64Overflow(Vec<u8>),
        ReadI64Overflow(Vec<u8>),
        ReadF64Mismatch(Vec<u8>),
        FromUtf8Error(Vec<u8>, FromUtf8Error)
    }

    impl fmt::Display for ToolError {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            match self {
                ToolError::ReadVintOverflow => write!(f, "Unrepresentable Vint size encountered."),
                ToolError::WriteVintOverflow(val) => write!(f, "Value too large to be written as a vint: {val}"),
                ToolError::WriteSignedVintOverflow(val) => write!(f, "Value outside range to be written as a vint: {val}"),
                ToolError::ReadU64Overflow(arr) => write!(f, "Could not read unsigned int from array: {arr:?}"),
                ToolError::ReadI64Overflow(arr) => write!(f, "Could not read int from array: {arr:?}"),
                ToolError::ReadF64Mismatch(arr) => write!(f, "Could not read float from array: {arr:?}"),
                ToolError::FromUtf8Error(arr, _source) => write!(f, "Could not read utf8 data: {arr:?}"),
            }
        }
    }

    impl Error for ToolError {
        fn source(&self) -> Option<&(dyn Error + 'static)> {
            match self {
                ToolError::FromUtf8Error(_arr, source) => Some(source),
                _ => None,
            }
        }
    }
}

pub mod tag_iterator {
    use super::fmt;
    use super::Error;
    use super::tool::ToolError;
    use std::io;

    ///
    /// Errors that indicate file data is corrupted.
    /// 
    #[derive(Debug)]
    pub enum CorruptedFileError {

        ///
        /// An error indicating the reader found an ebml tag id not defined in the current specification.
        /// 
        InvalidTagId{
            ///
            /// The position of the element.
            /// 
            position: usize, 
            
            ///
            /// The id of the tag that was found.
            /// 
            tag_id: u64, 
        },

        ///
        /// An error indicating the reader could not parse a valid tag due to corrupted tag data (size/contents).
        /// 
        InvalidTagData{
            ///
            /// The position of the element.
            /// 
            position: usize, 
            
            ///
            /// The id of the tag that was found.
            /// 
            tag_id: u64, 
        },

        ///
        /// An error indicating the reader found an element outside of its expected hierarchy.
        /// 
        HierarchyError{

            ///
            /// The id of the tag that was found.
            /// 
            found_tag_id: u64,

            ///
            /// The id of the current "master" element that contains the tag that was found.
            /// 
            current_parent_id: Option<u64>,
        },

        ///
        /// An error indicating the reader found a child element with incorrect sizing.
        /// 
        OversizedChildElement { 
            
            ///
            /// The position of the element.
            /// 
            position: usize, 
            
            ///
            /// The id of the tag that was found.
            /// 
            tag_id: u64, 
            
            ///
            /// The size of the tag that was found.
            /// 
            size: usize 
        },

        ///
        /// An error indicating the reader found a tag with an invalid size.
        /// 
        InvalidTagSize { 
            
            ///
            /// The position of the element.
            /// 
            position: usize, 
            
            ///
            /// The id of the tag that was found.
            /// 
            tag_id: u64, 
            
            ///
            /// The size of the tag that was found.
            /// 
            size: usize 
        },
    }

    impl fmt::Display for CorruptedFileError {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            match self {
                CorruptedFileError::InvalidTagId {
                    position, 
                    tag_id
                } => write!(f, "Encountered invalid tag id [0x{tag_id:x?}] at position {position}"),
                CorruptedFileError::InvalidTagData {
                    position, 
                    tag_id 
                } => write!(f, "Encountered invalid tag data for tag id [0x{tag_id:x?}] at position {position}"),
                CorruptedFileError::HierarchyError {
                    found_tag_id,
                    current_parent_id,
                } => write!(f, "Found child tag [{found_tag_id:x?}] when processing parent [{current_parent_id:x?}]"),
                CorruptedFileError::OversizedChildElement { 
                    position, 
                    tag_id, 
                    size : _
                } => write!(f, "Found an oversized tag [0x{tag_id:x?}] at position {position}"),
                CorruptedFileError::InvalidTagSize { 
                    position, 
                    tag_id, 
                    size,
                } => write!(f, "Found an oversized tag [0x{tag_id:x?}] at position {position} with size {size}.  Max supported size is 8GB."),
            }
        }
    }

    ///
    /// Errors that can occur when reading ebml data.
    ///
    #[derive(Debug)]
    pub enum TagIteratorError {

        ///
        /// An error indicating that data in the file being read is not valid.
        ///
        CorruptedFileData(CorruptedFileError),

        ///
        /// An error indicating that the iterator reached the end of the input stream unexpectedly while reading a tag.
        /// 
        /// This error will occur if the iterator is expecting more data (either due to expecting a size after reading a tag id or based on a tag size) but nothing is available in the input stream.
        /// 
        UnexpectedEOF {

            ///
            /// The start position of the tag that was being read when EOF was reached.
            /// 
            tag_start: usize,

            ///
            /// The id of the partially read tag, if available.
            /// 
            tag_id: Option<u64>,

            ///
            /// The size of the partially read tag, if available.
            /// 
            tag_size: Option<usize>,

            ///
            /// Any available data that was read for the tag before reaching EOF.
            /// 
            partial_data: Option<Vec<u8>>,
        },

        ///
        /// An error indicating that tag data appears to be corrupted.
        ///
        /// This error typically occurs if tag data cannot be read as its expected data type (e.g. trying to read `[32,42,8]` as float data, since floats require either 4 or 8 bytes).
        ///
        CorruptedTagData {

            ///
            /// The id of the corrupted tag.
            ///
            tag_id: u64,

            ///
            /// An error describing why the data is corrupted.
            ///
            problem: ToolError,
        },

        ///
        /// An error that wraps an IO error when reading from the underlying source.
        ///
        ReadError {

            ///
            /// The [`io::Error`] that caused this problem.
            ///
            source: io::Error,
        },
    }
    
    impl fmt::Display for TagIteratorError {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            match self {
                TagIteratorError::CorruptedFileData(err) => write!(f, "Encountered corrupted data.  Message: {err}"),
                TagIteratorError::UnexpectedEOF { 
                    tag_start, 
                    tag_id, 
                    tag_size, 
                    partial_data: _ 
                } => write!(f, "Reached EOF unexpectedly. Partial tag data: {{tag offset:{tag_start}}} {{id:{tag_id:x?}}} {{size:{tag_size:?}}}"),
                TagIteratorError::CorruptedTagData {
                    tag_id,
                    problem,
                } => write!(f, "Error reading data for tag id (0x{tag_id:x?}). {problem}"),
                TagIteratorError::ReadError { source: _ } => write!(f, "Error reading from source."),
            }
        }
    }
    
    impl Error for TagIteratorError {
        fn source(&self) -> Option<&(dyn Error + 'static)> {
            match self {
                TagIteratorError::CorruptedFileData(_) => None,
                TagIteratorError::UnexpectedEOF { tag_start: _, tag_id: _, tag_size: _, partial_data: _ } => None,
                TagIteratorError::CorruptedTagData { tag_id: _, problem } => problem.source(),
                TagIteratorError::ReadError { source } => Some(source),
            }
        }
    }
}

pub mod tag_writer {
    use super::fmt;
    use super::Error;
    use std::io;

    ///
    /// Errors that can occur when writing ebml data.
    ///
    #[derive(Debug)]
    pub enum TagWriterError {

        ///
        /// An error indicating the tag to be written doesn't conform to the current specification.
        /// 
        /// This error occurs if you attempt to write a tag outside of a valid document path.  See the [EBML RFC](https://www.rfc-editor.org/rfc/rfc8794.html#section-11.1.6.2) for details on element paths.
        /// 
        UnexpectedTag {
            tag_id: u64,
            current_path: Vec<u64>,
        },

        ///
        /// An error with a tag id.
        /// 
        /// This error should only occur if writing "RawTag" variants, and only if the input id is not a valid vint.
        /// 
        TagIdError(u64),

        ///
        /// An error with the size of a tag.
        ///
        /// Can occur if the tag size overflows the max value representable by a vint (`2^57 - 1`, or `144,115,188,075,855,871`).
        /// 
        /// This can also occur if a non-[`Master`][`crate::specs::TagDataType::Master`] tag is sent to be written with an unknown size.
        ///
        TagSizeError(String),

        ///
        /// An error indicating a tag was closed unexpectedly.
        ///
        /// Can occur if a [`Master::End`][`crate::specs::Master::End`] variant is passed to the [`TagWriter`][`crate::TagWriter`] but the id doesn't match the currently open tag.
        ///
        UnexpectedClosingTag {

            ///
            /// The id of the tag being closed.
            ///
            tag_id: u64,

            ///
            /// The id of the currently open tag.
            ///
            expected_id: Option<u64>,
        },

        ///
        /// An error that wraps an IO error when writing to the underlying destination.
        ///
        WriteError {
            source: io::Error,
        },
    }

    impl fmt::Display for TagWriterError {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            match self {
                TagWriterError::UnexpectedTag { tag_id, current_path } => write!(f, "Unexpected tag 0x{tag_id:x?} when writing to {current_path:x?}"),
                TagWriterError::TagIdError(id) => write!(f, "Tag id 0x{id:x?} is not a valid vint"),
                TagWriterError::TagSizeError(message) => write!(f, "Problem writing data tag size. {message}"),
                TagWriterError::UnexpectedClosingTag { tag_id, expected_id } => match expected_id {
                    Some(expected) => write!(f, "Unexpected closing tag 0x'{tag_id:x?}'. Expected 0x'{expected:x?}'"),
                    None => write!(f, "Unexpected closing tag 0x'{tag_id:x?}'"),
                },
                TagWriterError::WriteError { source: _ } => write!(f, "Error writing to destination."),
            }
        }
    }
    
    impl Error for TagWriterError {
        fn source(&self) -> Option<&(dyn Error + 'static)> {
            match self {
                TagWriterError::UnexpectedTag { tag_id: _, current_path: _ } => None,
                TagWriterError::TagIdError(_) => None,
                TagWriterError::TagSizeError(_) => None,
                TagWriterError::UnexpectedClosingTag { tag_id: _, expected_id: _ } => None,
                TagWriterError::WriteError { source } => Some(source),
            }
        }
    }
}