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
use crate::error::{Converter, WRONG_OFFSET};
use crate::paged_reader::PagedReader;
use crate::paged_writer::PagedWriter;
use crate::{Error, Result};
use roxmltree::Node;
use std::io::{copy, Read, Seek, Write};

/// Describes a binary data blob stored inside an E57 file.
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct Blob {
    /// Physical file offset of the binary blob section in the E57 file.
    pub offset: u64,
    /// The logical size of the associated binary blob in bytes.
    pub length: u64,
}

impl Blob {
    /// Creates a blob instance manually from offset and length.
    /// WARNING: This constructor is NOT required for standard E57 functionality!
    /// In normal cases, like when reading images, the library will provide all Blob instances.
    /// This is only needed for use cases with E57 extensions to read custom binary data.
    /// In this case the offset and length values must be extracted manually from the XML data.
    pub fn new(offset: u64, length: u64) -> Self {
        Blob { offset, length }
    }

    pub(crate) fn from_node(node: &Node) -> Result<Self> {
        if Some("Blob") != node.attribute("type") {
            Error::invalid("The supplided tag is not a blob")?
        }

        let offset = node
            .attribute("fileOffset")
            .invalid_err("Failed to find 'fileOffset' attribute in blob tag")?;
        let offset = offset
            .parse::<u64>()
            .invalid_err("Unable to parse offset as u64")?;

        let length = node
            .attribute("length")
            .invalid_err("Failed to find 'length' attribute in blob tag")?;
        let length = length
            .parse::<u64>()
            .invalid_err("Unable to parse length as u64")?;

        Ok(Self { offset, length })
    }

    pub(crate) fn from_parent_node(tag_name: &str, parent_node: &Node) -> Result<Option<Self>> {
        if let Some(node) = &parent_node.children().find(|n| n.has_tag_name(tag_name)) {
            Ok(Some(Self::from_node(node)?))
        } else {
            Ok(None)
        }
    }

    pub(crate) fn xml_string(&self, tag_name: &str) -> String {
        format!(
            "<{tag_name} type=\"Blob\" fileOffset=\"{}\" length=\"{}\"/>\n",
            self.offset, self.length
        )
    }

    pub(crate) fn read<T: Read + Seek>(
        &self,
        reader: &mut PagedReader<T>,
        writer: &mut dyn Write,
    ) -> Result<u64> {
        reader
            .seek_physical(self.offset)
            .read_err("Failed to seek to start offset of blob")?;
        let header = BlobSectionHeader::from_reader(reader)?;
        if self.length > header.section_length + 16 {
            Error::invalid("Blob XML length and blob section header mismatch")?
        }

        let mut limited = reader.take(self.length);
        copy(&mut limited, writer).read_err("Failed to read binary blob data")
    }

    pub(crate) fn write<T: Read + Write + Seek>(
        writer: &mut PagedWriter<T>,
        reader: &mut dyn Read,
    ) -> Result<Self> {
        // Write temporary section header with invalid zero length
        let start_offset = writer.physical_position()?;
        let mut section_header = BlobSectionHeader { section_length: 0 };
        section_header.to_writer(writer)?;

        // Write blob data
        let length = std::io::copy(reader, writer).write_err("Failed to write blob data")?;

        // Update blob section header with actual lenght
        let end_offset = writer.physical_position()?;
        section_header.section_length = length;
        writer.physical_seek(start_offset)?;
        section_header.to_writer(writer)?;
        writer.physical_seek(end_offset)?;

        writer
            .align()
            .write_err("Failed to align writer on next 4-byte offset after writing blob section")?;

        Ok(Self {
            offset: start_offset,
            length,
        })
    }
}

struct BlobSectionHeader {
    section_length: u64,
}

impl BlobSectionHeader {
    fn from_array(buffer: &[u8; 16]) -> Result<Self> {
        let section_id = buffer[0];
        if section_id != 0 {
            Error::invalid("Section ID of the blob section header is not 0")?
        }
        Ok(Self {
            section_length: u64::from_le_bytes(
                buffer[8..16].try_into().internal_err(WRONG_OFFSET)?,
            ),
        })
    }

    fn from_reader<T: Read + Seek>(reader: &mut PagedReader<T>) -> Result<BlobSectionHeader> {
        let mut buffer = [0_u8; 16];
        reader
            .read_exact(&mut buffer)
            .read_err("Failed to read compressed vector section header")?;
        BlobSectionHeader::from_array(&buffer)
    }

    fn to_writer<T: Read + Write + Seek>(&self, writer: &mut PagedWriter<T>) -> Result<()> {
        let mut bytes: [u8; 16] = [0; 16];
        let length_bytes = u64::to_le_bytes(self.section_length);
        bytes[8..16].copy_from_slice(&length_bytes);
        writer
            .write_all(&bytes)
            .write_err("Failed to write blob section header")
    }
}