1use crate::{
14 open_owned, seal, verify, DntCodec, DntError, DntKeyProvider, DntOpenOptions, DntResult,
15 DntSealOptions, OpenedDnt,
16};
17use std::fs::{self, File, OpenOptions};
18use std::io::{Read, Write};
19use std::path::{Path, PathBuf};
20use std::sync::atomic::{AtomicU64, Ordering};
21
22static TEMP_COUNTER: AtomicU64 = AtomicU64::new(0);
24
25impl DntOpenOptions {
26 pub fn max_envelope_bytes(&self) -> DntResult<u64> {
31 self.max_payload_bytes
32 .ok_or(DntError::PayloadTooLarge)?
33 .checked_add(crate::DNT_MAX_HEADER_BYTES as u64)
34 .and_then(|length| length.checked_add(crate::DNT_MAX_ENCRYPTED_METADATA_BYTES as u64))
35 .and_then(|length| length.checked_add(20))
36 .ok_or(DntError::PayloadTooLarge)
37 }
38}
39
40pub fn write_atomic<P, C>(
42 path: impl AsRef<Path>,
43 payload: &[u8],
44 key_provider: &P,
45 codec: &C,
46 seal_options: DntSealOptions,
47 open_options: &DntOpenOptions,
48) -> DntResult<()>
49where
50 P: DntKeyProvider,
51 C: DntCodec,
52{
53 let envelope = seal(payload, key_provider, codec, seal_options)?;
54 verify(&envelope, key_provider, codec, open_options)?;
55 atomic_replace(path.as_ref(), &envelope)
56}
57
58pub fn read_verified<P, C>(
60 path: impl AsRef<Path>,
61 key_provider: &P,
62 codec: &C,
63 options: &DntOpenOptions,
64) -> DntResult<OpenedDnt>
65where
66 P: DntKeyProvider,
67 C: DntCodec,
68{
69 let bytes = read_bounded(path.as_ref(), options.max_envelope_bytes()?)?;
70 open_owned(bytes, key_provider, codec, options)
71}
72
73fn read_bounded(path: &Path, max_bytes: u64) -> DntResult<Vec<u8>> {
74 reject_symlink(path)?;
75 let file = File::open(path).map_err(|_| DntError::Io)?;
76 let metadata = file.metadata().map_err(|_| DntError::Io)?;
77 if !metadata.is_file() {
78 return Err(DntError::Io);
79 }
80 if metadata.len() > max_bytes {
81 return Err(DntError::PayloadTooLarge);
82 }
83 let capacity = usize::try_from(metadata.len()).map_err(|_| DntError::PayloadTooLarge)?;
84 let read_limit = max_bytes.checked_add(1).ok_or(DntError::PayloadTooLarge)?;
85 let mut bytes = Vec::with_capacity(capacity);
86 file.take(read_limit)
87 .read_to_end(&mut bytes)
88 .map_err(|_| DntError::Io)?;
89 if bytes.len() as u64 > max_bytes {
90 return Err(DntError::PayloadTooLarge);
91 }
92 Ok(bytes)
93}
94
95fn atomic_replace(path: &Path, bytes: &[u8]) -> DntResult<()> {
96 let parent = path.parent().unwrap_or_else(|| Path::new("."));
97 fs::create_dir_all(parent).map_err(|_| DntError::Io)?;
98 reject_symlink(path)?;
99 let temporary = temporary_path(path, parent);
100 let result = write_and_rename(&temporary, path, parent, bytes);
101 if result.is_err() {
102 let _ = fs::remove_file(temporary);
103 }
104 result
105}
106
107fn write_and_rename(
108 temporary: &Path,
109 final_path: &Path,
110 parent: &Path,
111 bytes: &[u8],
112) -> DntResult<()> {
113 let mut file = OpenOptions::new()
114 .create_new(true)
115 .write(true)
116 .open(temporary)
117 .map_err(|_| DntError::Io)?;
118 file.write_all(bytes)
119 .and_then(|_| file.sync_all())
120 .map_err(|_| DntError::Io)?;
121 fs::rename(temporary, final_path).map_err(|_| DntError::Io)?;
122 sync_parent(parent)
123}
124
125fn temporary_path(path: &Path, parent: &Path) -> PathBuf {
126 let name = path
127 .file_name()
128 .and_then(|value| value.to_str())
129 .unwrap_or("appcore-dnt");
130 parent.join(format!(
131 ".{name}.{}-{}.tmp",
132 std::process::id(),
133 TEMP_COUNTER.fetch_add(1, Ordering::Relaxed)
134 ))
135}
136
137fn reject_symlink(path: &Path) -> DntResult<()> {
138 match fs::symlink_metadata(path) {
139 Ok(metadata) if metadata.file_type().is_symlink() => Err(DntError::Io),
140 Ok(_) => Ok(()),
141 Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
142 Err(_) => Err(DntError::Io),
143 }
144}
145
146#[cfg(unix)]
147fn sync_parent(path: &Path) -> DntResult<()> {
148 fs::File::open(path)
149 .and_then(|directory| directory.sync_all())
150 .map_err(|_| DntError::Io)
151}
152
153#[cfg(not(unix))]
154fn sync_parent(_path: &Path) -> DntResult<()> {
155 Ok(())
156}