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 retain(
128 &mut self,
129 reference: RestoreReferenceRecord,
130 ) -> Result<bool, RestoreReferenceError> {
131 if let Some(existing) = self
132 .restores
133 .iter()
134 .find(|entry| entry.journal == reference.journal)
135 {
136 if existing != &reference {
137 return Err(RestoreReferenceError::AuthorityConflict {
138 journal: reference.journal,
139 });
140 }
141 return Ok(false);
142 }
143 if self.restores.len() == MAX_RESTORE_REFERENCES {
144 return Err(RestoreReferenceError::TooManyReferences {
145 limit: MAX_RESTORE_REFERENCES,
146 });
147 }
148 self.restores.push(reference);
149 self.restores
150 .sort_by(|left, right| left.journal.cmp(&right.journal));
151 Ok(true)
152 }
153}
154
155fn read_bounded_references<'de, D: Deserializer<'de>>(
156 deserializer: D,
157) -> Result<Vec<RestoreReferenceRecord>, D::Error> {
158 struct ReferencesVisitor;
159 impl<'de> de::Visitor<'de> for ReferencesVisitor {
160 type Value = Vec<RestoreReferenceRecord>;
161 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
162 formatter.write_str("a bounded list of restore references")
163 }
164 fn visit_seq<A: de::SeqAccess<'de>>(
165 self,
166 mut sequence: A,
167 ) -> Result<Self::Value, A::Error> {
168 let mut entries = Vec::new();
169 while let Some(entry) = sequence.next_element()? {
170 if entries.len() == MAX_RESTORE_REFERENCES {
171 return Err(de::Error::custom("restore reference count exceeds limit"));
172 }
173 entries.push(entry);
174 }
175 Ok(entries)
176 }
177 }
178 deserializer.deserialize_seq(ReferencesVisitor)
179}
180
181#[derive(Debug, Error)]
183pub enum RestoreReferenceError {
184 #[error("invalid restore journal location: {journal:?}")]
186 InvalidJournal {
187 journal: PathBuf,
189 },
190 #[error(transparent)]
192 Checksum(#[from] ChecksumError),
193 #[error("unsupported restore references version {0}")]
195 UnsupportedVersion(u16),
196 #[error("duplicate restore journal location: {journal:?}")]
198 DuplicateJournal {
199 journal: PathBuf,
201 },
202 #[error("restore authority conflict at {journal:?}")]
204 AuthorityConflict {
205 journal: PathBuf,
207 },
208 #[error("restore reference count exceeds {limit}")]
210 TooManyReferences {
211 limit: usize,
213 },
214}
215
216#[cfg(test)]
217mod tests;