ic_backup/model/restore_references/
mod.rs1use crate::model::artifacts::{ArtifactChecksumRecord, ChecksumError};
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 = ArtifactChecksumRecord::from_hash(authority)?
49 .hash()
50 .to_owned();
51 Ok(Self { journal, authority })
52 }
53
54 #[must_use]
56 pub fn journal(&self) -> &Path {
57 &self.journal
58 }
59
60 #[must_use]
62 pub fn authority(&self) -> &str {
63 &self.authority
64 }
65}
66
67#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
69#[serde(try_from = "ReferencesFields")]
70pub struct RestoreReferencesRecord {
71 version: u16,
72 restores: Vec<RestoreReferenceRecord>,
73}
74
75#[derive(Deserialize)]
76#[serde(deny_unknown_fields)]
77struct ReferencesFields {
78 version: u16,
79 #[serde(deserialize_with = "read_bounded_references")]
80 restores: Vec<RestoreReferenceRecord>,
81}
82
83impl TryFrom<ReferencesFields> for RestoreReferencesRecord {
84 type Error = RestoreReferenceError;
85 fn try_from(mut fields: ReferencesFields) -> Result<Self, Self::Error> {
86 if fields.version != 1 {
87 return Err(RestoreReferenceError::UnsupportedVersion(fields.version));
88 }
89 let mut journals = BTreeSet::new();
90 for entry in &fields.restores {
91 if !journals.insert(entry.journal()) {
92 return Err(RestoreReferenceError::DuplicateJournal {
93 journal: entry.journal.clone(),
94 });
95 }
96 }
97 fields
98 .restores
99 .sort_by(|left, right| left.journal.cmp(&right.journal));
100 Ok(Self {
101 version: 1,
102 restores: fields.restores,
103 })
104 }
105}
106
107impl RestoreReferencesRecord {
108 #[must_use]
110 pub const fn empty() -> Self {
111 Self {
112 version: 1,
113 restores: Vec::new(),
114 }
115 }
116
117 #[must_use]
119 pub fn entries(&self) -> &[RestoreReferenceRecord] {
120 &self.restores
121 }
122
123 #[must_use]
125 pub fn is_empty(&self) -> bool {
126 self.restores.is_empty()
127 }
128
129 pub(crate) fn retain(
130 &mut self,
131 reference: RestoreReferenceRecord,
132 ) -> Result<bool, RestoreReferenceError> {
133 if let Some(existing) = self
134 .restores
135 .iter()
136 .find(|entry| entry.journal == reference.journal)
137 {
138 if existing != &reference {
139 return Err(RestoreReferenceError::AuthorityConflict {
140 journal: reference.journal,
141 });
142 }
143 return Ok(false);
144 }
145 if self.restores.len() == MAX_RESTORE_REFERENCES {
146 return Err(RestoreReferenceError::TooManyReferences {
147 limit: MAX_RESTORE_REFERENCES,
148 });
149 }
150 self.restores.push(reference);
151 self.restores
152 .sort_by(|left, right| left.journal.cmp(&right.journal));
153 Ok(true)
154 }
155}
156
157fn read_bounded_references<'de, D: Deserializer<'de>>(
158 deserializer: D,
159) -> Result<Vec<RestoreReferenceRecord>, D::Error> {
160 struct ReferencesVisitor;
161 impl<'de> de::Visitor<'de> for ReferencesVisitor {
162 type Value = Vec<RestoreReferenceRecord>;
163 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
164 formatter.write_str("a bounded list of restore references")
165 }
166 fn visit_seq<A: de::SeqAccess<'de>>(
167 self,
168 mut sequence: A,
169 ) -> Result<Self::Value, A::Error> {
170 let mut entries = Vec::new();
171 while let Some(entry) = sequence.next_element()? {
172 if entries.len() == MAX_RESTORE_REFERENCES {
173 return Err(de::Error::custom("restore reference count exceeds limit"));
174 }
175 entries.push(entry);
176 }
177 Ok(entries)
178 }
179 }
180 deserializer.deserialize_seq(ReferencesVisitor)
181}
182
183#[derive(Debug, Error)]
185pub enum RestoreReferenceError {
186 #[error("invalid restore journal location: {journal:?}")]
188 InvalidJournal {
189 journal: PathBuf,
191 },
192 #[error(transparent)]
194 Checksum(#[from] ChecksumError),
195 #[error("unsupported restore references version {0}")]
197 UnsupportedVersion(u16),
198 #[error("duplicate restore journal location: {journal:?}")]
200 DuplicateJournal {
201 journal: PathBuf,
203 },
204 #[error("restore authority conflict at {journal:?}")]
206 AuthorityConflict {
207 journal: PathBuf,
209 },
210 #[error("restore reference count exceeds {limit}")]
212 TooManyReferences {
213 limit: usize,
215 },
216}
217
218#[cfg(test)]
219mod tests;