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
/*
 * Created on Tue Jul 11 2023
 *
 * Copyright (c) storycraft. Licensed under the Apache Licence 2.0.
 */

#![doc = include_str!("../README.md")]

pub mod ext;

use core::fmt;
use std::io::{self, Seek, Write};

use const_fnv1a_hash::fnv1a_hash_str_32;
use phf_generator::{generate_hash, HashState};
use sha2::{Digest, Sha256};
use spx::{crypto::SpxCipherStream, FileInfo};

#[derive(Debug)]
/// Compile-time [`FileMap`] builder
pub struct SpxBuilder<W> {
    writer: W,
    keys: Vec<String>,
    values: Vec<(u32, FileInfo)>,
}

impl<W: Write + Seek> SpxBuilder<W> {
    /// Create new builder
    pub const fn new(writer: W) -> Self {
        Self {
            writer,
            keys: Vec::new(),
            values: Vec::new(),
        }
    }

    /// Start new file entry
    pub fn start_file(&mut self, name: String) -> io::Result<SpxFileEntry<'_, W>> {
        let hash = fnv1a_hash_str_32(&name);

        let pos = self.writer.stream_position()?;

        let key: [u8; 32] = Sha256::new().chain_update(&name.as_bytes()).finalize().into();

        self.keys.push(name);
        self.values.push((hash, FileInfo::new(pos, 0)));

        Ok(SpxFileEntry {
            writer: SpxCipherStream::new(&key, hash, &mut self.writer),
            info: &mut self.values.last_mut().unwrap().1,
        })
    }

    /// Generate and return [`FileMap`] code
    pub fn build(&self) -> Display {
        let state = generate_hash(&self.keys);

        Display {
            state,
            values: &self.values,
        }
    }
}

pub struct SpxFileEntry<'a, W> {
    writer: SpxCipherStream<&'a mut W>,
    info: &'a mut FileInfo,
}

impl<W> SpxFileEntry<'_, W> {
    pub fn finish(self) -> FileInfo {
        *self.info
    }
}

impl<W: Write> Write for SpxFileEntry<'_, W> {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        let written = self.writer.write(buf)?;

        self.info.size += written as u64;

        Ok(written)
    }

    fn flush(&mut self) -> io::Result<()> {
        self.writer.flush()
    }
}

pub struct Display<'a> {
    state: HashState,
    values: &'a [(u32, FileInfo)],
}

impl core::fmt::Display for Display<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "::spx::FileMap {{ key: {}_u64, disps: &[",
            self.state.key
        )?;

        for disp in &self.state.disps {
            write!(f, "({}, {}), ", disp.0, disp.1)?;
        }
        write!(f, "], values: &[")?;

        for index in &self.state.map {
            let value = &self.values[*index];
            write!(
                f,
                "({}, ::spx::FileInfo::new({}, {})), ",
                value.0, value.1.offset, value.1.size
            )?;
        }
        write!(f, "] }}")?;

        Ok(())
    }
}