ic_backup/model/restore_references/
mod.rs1use crate::model::artifacts::{ChecksumError, canonical_hash};
4use serde::{Deserialize, Deserializer, Serialize, de};
5use std::{
6 collections::BTreeSet,
7 fmt,
8 path::{Path, PathBuf},
9};
10use thiserror::Error;
11
12pub const MAX_RESTORE_REFERENCES: usize = 1024;
14pub const MAX_JOURNAL_PATH_BYTES: usize = 4096;
16
17#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
19#[serde(try_from = "ReferenceFields")]
20pub struct RestoreReferenceRecord {
21 journal: PathBuf,
22 authority: String,
23}
24
25#[derive(Deserialize)]
26#[serde(deny_unknown_fields)]
27struct ReferenceFields {
28 journal: PathBuf,
29 authority: String,
30}
31
32impl TryFrom<ReferenceFields> for RestoreReferenceRecord {
33 type Error = RestoreReferenceError;
34 fn try_from(fields: ReferenceFields) -> Result<Self, Self::Error> {
35 Self::new(fields.journal, &fields.authority)
36 }
37}
38
39impl RestoreReferenceRecord {
40 pub fn new(journal: PathBuf, authority: &str) -> Result<Self, RestoreReferenceError> {
45 if !super::journal_path::is_canonical(&journal, MAX_JOURNAL_PATH_BYTES) {
46 return Err(RestoreReferenceError::InvalidJournal { journal });
47 }
48 let authority = canonical_hash(authority)?;
49 Ok(Self { journal, authority })
50 }
51
52 #[must_use]
54 pub fn journal(&self) -> &Path {
55 &self.journal
56 }
57
58 #[must_use]
60 pub fn authority(&self) -> &str {
61 &self.authority
62 }
63}
64
65#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
67#[serde(try_from = "ReferencesFields")]
68pub struct RestoreReferencesRecord {
69 version: u16,
70 restores: Vec<RestoreReferenceRecord>,
71}
72
73#[derive(Deserialize)]
74#[serde(deny_unknown_fields)]
75struct ReferencesFields {
76 version: u16,
77 #[serde(deserialize_with = "read_bounded_references")]
78 restores: Vec<RestoreReferenceRecord>,
79}
80
81impl TryFrom<ReferencesFields> for RestoreReferencesRecord {
82 type Error = RestoreReferenceError;
83 fn try_from(mut fields: ReferencesFields) -> Result<Self, Self::Error> {
84 if fields.version != 1 {
85 return Err(RestoreReferenceError::UnsupportedVersion(fields.version));
86 }
87 let mut journals = BTreeSet::new();
88 for entry in &fields.restores {
89 if !journals.insert(entry.journal()) {
90 return Err(RestoreReferenceError::DuplicateJournal {
91 journal: entry.journal.clone(),
92 });
93 }
94 }
95 fields
96 .restores
97 .sort_by(|left, right| left.journal.cmp(&right.journal));
98 Ok(Self {
99 version: 1,
100 restores: fields.restores,
101 })
102 }
103}
104
105impl RestoreReferencesRecord {
106 #[must_use]
108 pub const fn empty() -> Self {
109 Self {
110 version: 1,
111 restores: Vec::new(),
112 }
113 }
114
115 #[must_use]
117 pub fn entries(&self) -> &[RestoreReferenceRecord] {
118 &self.restores
119 }
120
121 #[must_use]
123 pub fn is_empty(&self) -> bool {
124 self.restores.is_empty()
125 }
126
127 pub(crate) fn release(
128 &mut self,
129 reference: &RestoreReferenceRecord,
130 ) -> Result<bool, RestoreReferenceError> {
131 let Some(index) = self
132 .restores
133 .iter()
134 .position(|entry| entry.journal == reference.journal)
135 else {
136 return Ok(false);
137 };
138 if self.restores[index] != *reference {
139 return Err(RestoreReferenceError::AuthorityConflict {
140 journal: reference.journal.clone(),
141 });
142 }
143 self.restores.remove(index);
144 Ok(true)
145 }
146
147 pub(crate) fn retain(
148 &mut self,
149 reference: RestoreReferenceRecord,
150 ) -> Result<bool, RestoreReferenceError> {
151 if let Some(existing) = self
152 .restores
153 .iter()
154 .find(|entry| entry.journal == reference.journal)
155 {
156 if existing != &reference {
157 return Err(RestoreReferenceError::AuthorityConflict {
158 journal: reference.journal,
159 });
160 }
161 return Ok(false);
162 }
163 if self.restores.len() == MAX_RESTORE_REFERENCES {
164 return Err(RestoreReferenceError::TooManyReferences {
165 limit: MAX_RESTORE_REFERENCES,
166 });
167 }
168 self.restores.push(reference);
169 self.restores
170 .sort_by(|left, right| left.journal.cmp(&right.journal));
171 Ok(true)
172 }
173}
174
175fn read_bounded_references<'de, D: Deserializer<'de>>(
176 deserializer: D,
177) -> Result<Vec<RestoreReferenceRecord>, D::Error> {
178 struct ReferencesVisitor;
179 impl<'de> de::Visitor<'de> for ReferencesVisitor {
180 type Value = Vec<RestoreReferenceRecord>;
181 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
182 formatter.write_str("a bounded list of restore references")
183 }
184 fn visit_seq<A: de::SeqAccess<'de>>(
185 self,
186 mut sequence: A,
187 ) -> Result<Self::Value, A::Error> {
188 let mut entries = Vec::new();
189 while let Some(entry) = sequence.next_element()? {
190 if entries.len() == MAX_RESTORE_REFERENCES {
191 return Err(de::Error::custom("restore reference count exceeds limit"));
192 }
193 entries.push(entry);
194 }
195 Ok(entries)
196 }
197 }
198 deserializer.deserialize_seq(ReferencesVisitor)
199}
200
201#[derive(Debug, Error)]
203pub enum RestoreReferenceError {
204 #[error("invalid restore journal location: {journal:?}")]
206 InvalidJournal {
207 journal: PathBuf,
209 },
210 #[error(transparent)]
212 Checksum(#[from] ChecksumError),
213 #[error("unsupported restore references version {0}")]
215 UnsupportedVersion(u16),
216 #[error("duplicate restore journal location: {journal:?}")]
218 DuplicateJournal {
219 journal: PathBuf,
221 },
222 #[error("restore authority conflict at {journal:?}")]
224 AuthorityConflict {
225 journal: PathBuf,
227 },
228 #[error("restore reference count exceeds {limit}")]
230 TooManyReferences {
231 limit: usize,
233 },
234}
235
236#[cfg(test)]
237mod tests;