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::path::{Path, PathBuf};
9use thiserror::Error;
10
11pub const MAX_RESTORE_REFERENCES: usize = 1024;
13pub const MAX_JOURNAL_PATH_BYTES: usize = 4096;
15
16#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
18#[serde(try_from = "ReferenceFields")]
19pub struct RestoreReferenceRecord {
20 journal: PathBuf,
21 authority: String,
22}
23
24#[derive(Deserialize)]
25#[serde(deny_unknown_fields)]
26struct ReferenceFields {
27 journal: PathBuf,
28 authority: String,
29}
30
31impl TryFrom<ReferenceFields> for RestoreReferenceRecord {
32 type Error = RestoreReferenceError;
33 fn try_from(fields: ReferenceFields) -> Result<Self, Self::Error> {
34 Self::new(fields.journal, &fields.authority)
35 }
36}
37
38impl RestoreReferenceRecord {
39 pub fn new(journal: PathBuf, authority: &str) -> Result<Self, RestoreReferenceError> {
44 if !super::journal_path::is_canonical(&journal, MAX_JOURNAL_PATH_BYTES) {
45 return Err(RestoreReferenceError::InvalidJournal { journal });
46 }
47 let authority = canonical_hash(authority)?;
48 Ok(Self { journal, authority })
49 }
50
51 #[must_use]
53 pub fn journal(&self) -> &Path {
54 &self.journal
55 }
56
57 #[must_use]
59 pub fn authority(&self) -> &str {
60 &self.authority
61 }
62}
63
64#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
66#[serde(try_from = "ReferencesFields")]
67pub struct RestoreReferencesRecord {
68 version: u16,
69 restores: Vec<RestoreReferenceRecord>,
70}
71
72#[derive(Deserialize)]
73#[serde(deny_unknown_fields)]
74struct ReferencesFields {
75 version: u16,
76 #[serde(deserialize_with = "read_bounded_references")]
77 restores: Vec<RestoreReferenceRecord>,
78}
79
80impl TryFrom<ReferencesFields> for RestoreReferencesRecord {
81 type Error = RestoreReferenceError;
82 fn try_from(mut fields: ReferencesFields) -> Result<Self, Self::Error> {
83 if fields.version != 1 {
84 return Err(RestoreReferenceError::UnsupportedVersion(fields.version));
85 }
86 fields
87 .restores
88 .sort_by(|left, right| left.journal.cmp(&right.journal));
89 if let Some(pair) = fields
90 .restores
91 .windows(2)
92 .find(|pair| pair[0].journal == pair[1].journal)
93 {
94 return Err(RestoreReferenceError::DuplicateJournal {
95 journal: pair[0].journal.clone(),
96 });
97 }
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 Ok(index) = self
132 .restores
133 .binary_search_by(|entry| entry.journal.cmp(&reference.journal))
134 else {
135 return Ok(false);
136 };
137 if self.restores[index] != *reference {
138 return Err(RestoreReferenceError::AuthorityConflict {
139 journal: reference.journal.clone(),
140 });
141 }
142 self.restores.remove(index);
143 Ok(true)
144 }
145
146 pub(crate) fn retain(
147 &mut self,
148 reference: RestoreReferenceRecord,
149 ) -> Result<bool, RestoreReferenceError> {
150 let index = match self
151 .restores
152 .binary_search_by(|entry| entry.journal.cmp(&reference.journal))
153 {
154 Ok(index) => {
155 if self.restores[index] != reference {
156 return Err(RestoreReferenceError::AuthorityConflict {
157 journal: reference.journal,
158 });
159 }
160 return Ok(false);
161 }
162 Err(index) => index,
163 };
164 if self.restores.len() == MAX_RESTORE_REFERENCES {
165 return Err(RestoreReferenceError::TooManyReferences {
166 limit: MAX_RESTORE_REFERENCES,
167 });
168 }
169 self.restores.insert(index, reference);
170 Ok(true)
171 }
172}
173
174fn read_bounded_references<'de, D: Deserializer<'de>>(
175 deserializer: D,
176) -> Result<Vec<RestoreReferenceRecord>, D::Error> {
177 bounded_sequence::deserialize::<_, RestoreReferenceRecord, MAX_RESTORE_REFERENCES>(
178 deserializer,
179 "a bounded list of restore references",
180 "restore reference count exceeds limit",
181 )
182}
183
184#[derive(Debug, Error)]
186pub enum RestoreReferenceError {
187 #[error("invalid restore journal location: {journal:?}")]
189 InvalidJournal {
190 journal: PathBuf,
192 },
193 #[error(transparent)]
195 Checksum(#[from] ChecksumError),
196 #[error("unsupported restore references version {0}")]
198 UnsupportedVersion(u16),
199 #[error("duplicate restore journal location: {journal:?}")]
201 DuplicateJournal {
202 journal: PathBuf,
204 },
205 #[error("restore authority conflict at {journal:?}")]
207 AuthorityConflict {
208 journal: PathBuf,
210 },
211 #[error("restore reference count exceeds {limit}")]
213 TooManyReferences {
214 limit: usize,
216 },
217}
218
219#[cfg(test)]
220mod tests;