ic_backup/model/restore_references/
mod.rs1use crate::model::{
4 artifacts::{ChecksumError, canonical_hash},
5 bounded_sequence,
6};
7use serde::{Deserialize, Deserializer, Serialize};
8use std::{
9 collections::BTreeSet,
10 path::{Path, PathBuf},
11};
12use thiserror::Error;
13
14pub const MAX_RESTORE_REFERENCES: usize = 1024;
16pub const MAX_JOURNAL_PATH_BYTES: usize = 4096;
18
19#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
21#[serde(try_from = "ReferenceFields")]
22pub struct RestoreReferenceRecord {
23 journal: PathBuf,
24 authority: String,
25}
26
27#[derive(Deserialize)]
28#[serde(deny_unknown_fields)]
29struct ReferenceFields {
30 journal: PathBuf,
31 authority: String,
32}
33
34impl TryFrom<ReferenceFields> for RestoreReferenceRecord {
35 type Error = RestoreReferenceError;
36 fn try_from(fields: ReferenceFields) -> Result<Self, Self::Error> {
37 Self::new(fields.journal, &fields.authority)
38 }
39}
40
41impl RestoreReferenceRecord {
42 pub fn new(journal: PathBuf, authority: &str) -> Result<Self, RestoreReferenceError> {
47 if !super::journal_path::is_canonical(&journal, MAX_JOURNAL_PATH_BYTES) {
48 return Err(RestoreReferenceError::InvalidJournal { journal });
49 }
50 let authority = canonical_hash(authority)?;
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 release(
130 &mut self,
131 reference: &RestoreReferenceRecord,
132 ) -> Result<bool, RestoreReferenceError> {
133 let Some(index) = self
134 .restores
135 .iter()
136 .position(|entry| entry.journal == reference.journal)
137 else {
138 return Ok(false);
139 };
140 if self.restores[index] != *reference {
141 return Err(RestoreReferenceError::AuthorityConflict {
142 journal: reference.journal.clone(),
143 });
144 }
145 self.restores.remove(index);
146 Ok(true)
147 }
148
149 pub(crate) fn retain(
150 &mut self,
151 reference: RestoreReferenceRecord,
152 ) -> Result<bool, RestoreReferenceError> {
153 if let Some(existing) = self
154 .restores
155 .iter()
156 .find(|entry| entry.journal == reference.journal)
157 {
158 if existing != &reference {
159 return Err(RestoreReferenceError::AuthorityConflict {
160 journal: reference.journal,
161 });
162 }
163 return Ok(false);
164 }
165 if self.restores.len() == MAX_RESTORE_REFERENCES {
166 return Err(RestoreReferenceError::TooManyReferences {
167 limit: MAX_RESTORE_REFERENCES,
168 });
169 }
170 self.restores.push(reference);
171 self.restores
172 .sort_by(|left, right| left.journal.cmp(&right.journal));
173 Ok(true)
174 }
175}
176
177fn read_bounded_references<'de, D: Deserializer<'de>>(
178 deserializer: D,
179) -> Result<Vec<RestoreReferenceRecord>, D::Error> {
180 bounded_sequence::deserialize::<_, RestoreReferenceRecord, MAX_RESTORE_REFERENCES>(
181 deserializer,
182 "a bounded list of restore references",
183 "restore reference count exceeds limit",
184 )
185}
186
187#[derive(Debug, Error)]
189pub enum RestoreReferenceError {
190 #[error("invalid restore journal location: {journal:?}")]
192 InvalidJournal {
193 journal: PathBuf,
195 },
196 #[error(transparent)]
198 Checksum(#[from] ChecksumError),
199 #[error("unsupported restore references version {0}")]
201 UnsupportedVersion(u16),
202 #[error("duplicate restore journal location: {journal:?}")]
204 DuplicateJournal {
205 journal: PathBuf,
207 },
208 #[error("restore authority conflict at {journal:?}")]
210 AuthorityConflict {
211 journal: PathBuf,
213 },
214 #[error("restore reference count exceeds {limit}")]
216 TooManyReferences {
217 limit: usize,
219 },
220}
221
222#[cfg(test)]
223mod tests;