1use super::*;
2
3#[derive(Debug, Clone, PartialEq, Eq)]
4pub struct VerifiedStoreDeviceOperations {
5 proposals: Vec<(
6 RetainedStoreDeviceExclusionProposal,
7 StoreDeviceExclusionProposal,
8 )>,
9 outcomes: Vec<VerifiedStoreDeviceExclusionOutcome>,
10}
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13enum VerifiedStoreDeviceExclusionOutcome {
14 Excluded {
15 source: RetainedStoreDeviceExclusionOutcome,
16 accepted_cut: StoreHistoryCut,
17 },
18 Cancelled(RetainedStoreDeviceExclusionOutcome),
19}
20
21#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
22#[serde(deny_unknown_fields)]
23pub struct RetainedStoreDeviceRegistrationActivations {
24 registrations: Vec<RetainedStoreDeviceRegistrationActivation>,
25}
26
27#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
28#[serde(deny_unknown_fields)]
29struct RetainedStoreDeviceRegistrationActivation {
30 canonical_registration: Vec<u8>,
31 authority: StoreDeviceRegistrationActivation,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
35#[serde(deny_unknown_fields)]
36pub struct RetainedStoreDeviceOperations {
37 proposals: Vec<RetainedStoreDeviceExclusionProposal>,
38 outcomes: Vec<RetainedStoreDeviceExclusionOutcome>,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
42#[serde(deny_unknown_fields)]
43pub struct RetainedStoreDeviceExclusionProposal {
44 reference: StoreDeviceExclusionProposalRef,
45 canonical_proposal: Vec<u8>,
46 canonical_target_registration: Vec<u8>,
47 canonical_owner_registration: Vec<u8>,
48}
49
50#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
51#[serde(rename_all = "snake_case", deny_unknown_fields)]
52pub enum RetainedStoreDeviceExclusionOutcome {
53 Excluded {
54 reference: StoreDeviceExclusionRef,
55 canonical_outcome: Vec<u8>,
56 proposal: RetainedStoreDeviceExclusionProposal,
57 canonical_owner_registration: Vec<u8>,
58 },
59 Cancelled {
60 reference: StoreDeviceExclusionCancellationRef,
61 canonical_outcome: Vec<u8>,
62 proposal: RetainedStoreDeviceExclusionProposal,
63 canonical_owner_registration: Vec<u8>,
64 },
65}
66
67impl VerifiedStoreDeviceOperations {
68 pub fn proposals(
69 &self,
70 ) -> impl ExactSizeIterator<
71 Item = (
72 &StoreDeviceExclusionProposalRef,
73 &StoreDeviceExclusionProposal,
74 ),
75 > {
76 self.proposals
77 .iter()
78 .map(|(source, proposal)| (&source.reference, proposal))
79 }
80
81 pub fn exclusions(&self) -> impl Iterator<Item = (&StoreDeviceExclusionRef, &StoreHistoryCut)> {
82 self.outcomes.iter().filter_map(|outcome| match outcome {
83 VerifiedStoreDeviceExclusionOutcome::Excluded {
84 source,
85 accepted_cut,
86 } => Some((source.exclusion_reference(), accepted_cut)),
87 VerifiedStoreDeviceExclusionOutcome::Cancelled(_) => None,
88 })
89 }
90
91 pub(crate) fn from_retained_sources(
92 root: &StoreRootRef,
93 commit: &StoreBatchCommit,
94 proposals: Vec<RetainedStoreDeviceExclusionProposal>,
95 outcomes: Vec<RetainedStoreDeviceExclusionOutcome>,
96 ) -> Result<Self, StoreProtocolError> {
97 let proposal_refs = proposals
98 .iter()
99 .map(|source| source.reference.clone())
100 .collect::<Vec<_>>();
101 let outcome_refs = outcomes
102 .iter()
103 .map(RetainedStoreDeviceExclusionOutcome::wire_reference)
104 .collect::<Vec<_>>();
105 if proposal_refs.as_slice() != commit.device_exclusion_proposals()
106 || outcome_refs.as_slice() != commit.device_exclusion_outcomes()
107 {
108 return Err(StoreProtocolError::DeviceStateMismatch);
109 }
110 let retained = RetainedStoreDeviceOperations {
111 proposals: proposals.clone(),
112 outcomes: outcomes.clone(),
113 };
114 let proposals = proposals
115 .into_iter()
116 .map(|source| {
117 let proposal = source.verify(root)?;
118 if proposal.frozen_device_state != commit.device_state {
119 return Err(StoreProtocolError::DeviceStateMismatch);
120 }
121 Ok((source, proposal))
122 })
123 .collect::<Result<Vec<_>, StoreProtocolError>>()?;
124 let outcomes = outcomes
125 .into_iter()
126 .map(|source| source.verify(root))
127 .collect::<Result<Vec<_>, StoreProtocolError>>()?;
128 let verified = Self {
129 proposals,
130 outcomes,
131 };
132 if verified.to_retained() != retained {
133 return Err(StoreProtocolError::DeviceStateMismatch);
134 }
135 Ok(verified)
136 }
137
138 pub fn without_exclusions(commit: &StoreBatchCommit) -> Result<Self, StoreProtocolError> {
139 if !commit.device_exclusion_proposals().is_empty()
140 || !commit.device_exclusion_outcomes().is_empty()
141 {
142 return Err(StoreProtocolError::DeviceStateMismatch);
143 }
144 Ok(Self {
145 proposals: Vec::new(),
146 outcomes: Vec::new(),
147 })
148 }
149
150 pub fn to_retained(&self) -> RetainedStoreDeviceOperations {
151 RetainedStoreDeviceOperations {
152 proposals: self
153 .proposals
154 .iter()
155 .map(|(source, _)| source.clone())
156 .collect(),
157 outcomes: self
158 .outcomes
159 .iter()
160 .map(VerifiedStoreDeviceExclusionOutcome::source)
161 .cloned()
162 .collect(),
163 }
164 }
165
166 pub fn apply_to(
167 &self,
168 predecessor: ResolvedStoreDeviceState,
169 predecessor_ref: &StoreDeviceStateRef,
170 ) -> Result<ResolvedStoreDeviceState, StoreProtocolError> {
171 let mut state = predecessor;
172 for (source, proposal) in &self.proposals {
173 state = state.propose_exclusion(source.reference.clone(), proposal, predecessor_ref)?;
174 }
175 for outcome in &self.outcomes {
176 state = match outcome {
177 VerifiedStoreDeviceExclusionOutcome::Excluded {
178 source,
179 accepted_cut,
180 } => state.exclude(source.exclusion_reference().clone(), accepted_cut.clone())?,
181 VerifiedStoreDeviceExclusionOutcome::Cancelled(source) => {
182 state.cancel_exclusion(source.cancellation_reference().clone())?
183 }
184 };
185 }
186 Ok(state)
187 }
188}
189
190impl RetainedStoreDeviceRegistrationActivations {
191 pub fn from_verified(
192 root: &StoreRootRef,
193 commit: &StoreBatchCommit,
194 registrations: &[ActivatedStoreDeviceRegistration],
195 ) -> Result<Self, StoreProtocolError> {
196 if registrations.len() != commit.device_registrations().len() {
197 return Err(StoreProtocolError::DeviceStateMismatch);
198 }
199 let retained = Self {
200 registrations: registrations
201 .iter()
202 .map(|registration| RetainedStoreDeviceRegistrationActivation {
203 canonical_registration: registration.value().to_bytes(),
204 authority: registration.activation().clone(),
205 })
206 .collect(),
207 };
208 retained.verify_for(root, commit)?;
209 Ok(retained)
210 }
211
212 pub fn verify_for(
213 &self,
214 root: &StoreRootRef,
215 commit: &StoreBatchCommit,
216 ) -> Result<Vec<ActivatedStoreDeviceRegistration>, StoreProtocolError> {
217 if self.registrations.len() != commit.device_registrations().len() {
218 return Err(StoreProtocolError::DeviceStateMismatch);
219 }
220 commit
221 .device_registrations()
222 .iter()
223 .zip(&self.registrations)
224 .map(|(activated, retained)| retained.verify(root, activated))
225 .collect()
226 }
227}
228
229impl RetainedStoreDeviceRegistrationActivation {
230 fn verify(
231 &self,
232 root: &StoreRootRef,
233 activated: &ActivatedStoreDeviceRegistrationRef,
234 ) -> Result<ActivatedStoreDeviceRegistration, StoreProtocolError> {
235 let registration = verify_retained_registration(
236 root,
237 &activated.registration,
238 &self.canonical_registration,
239 )?;
240 let registration = ReferencedStoreDeviceRegistration::verified(
241 activated.registration.clone(),
242 registration,
243 )?;
244 let registration =
245 ActivatedStoreDeviceRegistration::verified(registration, self.authority.clone())?;
246 registration.verify_reference(activated)?;
247 Ok(registration)
248 }
249}
250
251impl RetainedStoreDeviceOperations {
252 pub fn from_sources(
253 proposals: Vec<RetainedStoreDeviceExclusionProposal>,
254 outcomes: Vec<RetainedStoreDeviceExclusionOutcome>,
255 ) -> Self {
256 Self {
257 proposals,
258 outcomes,
259 }
260 }
261
262 pub fn verify_for(
263 &self,
264 root: &StoreRootRef,
265 commit: &StoreBatchCommit,
266 ) -> Result<VerifiedStoreDeviceOperations, StoreProtocolError> {
267 VerifiedStoreDeviceOperations::from_retained_sources(
268 root,
269 commit,
270 self.proposals.clone(),
271 self.outcomes.clone(),
272 )
273 }
274}
275
276impl RetainedStoreDeviceExclusionProposal {
277 pub fn from_exact(
278 reference: StoreDeviceExclusionProposalRef,
279 proposal: &StoreDeviceExclusionProposal,
280 target: &StoreDeviceRegistration,
281 owner: &StoreDeviceRegistration,
282 ) -> Result<Self, StoreProtocolError> {
283 let retained = Self {
284 reference,
285 canonical_proposal: proposal.to_bytes(),
286 canonical_target_registration: target.to_bytes(),
287 canonical_owner_registration: owner.to_bytes(),
288 };
289 let opened = retained.verify_with_registrations(&target.store_root)?;
290 if opened.object.value != *proposal || opened.target != *target || opened.owner != *owner {
291 return Err(StoreProtocolError::DeviceStateMismatch);
292 }
293 Ok(retained)
294 }
295
296 pub fn from_verified(proposal: &VerifiedDeviceExclusionProposal) -> Self {
297 Self {
298 reference: proposal.reference.clone(),
299 canonical_proposal: proposal.object.bytes.clone(),
300 canonical_target_registration: proposal.target.to_bytes(),
301 canonical_owner_registration: proposal.owner.to_bytes(),
302 }
303 }
304
305 pub fn reference(&self) -> &StoreDeviceExclusionProposalRef {
306 &self.reference
307 }
308
309 fn verify(
310 &self,
311 root: &StoreRootRef,
312 ) -> Result<StoreDeviceExclusionProposal, StoreProtocolError> {
313 self.verify_with_registrations(root)
314 .map(|proposal| proposal.object.value)
315 }
316
317 fn verify_with_registrations(
318 &self,
319 root: &StoreRootRef,
320 ) -> Result<VerifiedDeviceExclusionProposal, StoreProtocolError> {
321 self.reference.object.verify(&self.canonical_proposal)?;
322 let unverified: StoreDeviceExclusionProposal =
323 serde_json::from_slice(&self.canonical_proposal)?;
324 if unverified.to_bytes() != self.canonical_proposal {
325 return Err(StoreProtocolError::Malformed(
326 "retained Store device exclusion proposal is not canonically encoded".to_string(),
327 ));
328 }
329 let target = verify_retained_registration(
330 root,
331 &unverified.target,
332 &self.canonical_target_registration,
333 )?;
334 let owner = verify_retained_registration(
335 root,
336 &unverified.owner_registration,
337 &self.canonical_owner_registration,
338 )?;
339 let proposal = StoreDeviceExclusionProposal::parse_at(
340 &self.canonical_proposal,
341 &self.reference,
342 &target,
343 &owner,
344 )?;
345 Ok(VerifiedDeviceExclusionProposal {
346 reference: self.reference.clone(),
347 object: crate::objects::VerifiedObject {
348 value: proposal,
349 bytes: self.canonical_proposal.clone(),
350 semantic_hash: self.reference.proposal_hash,
351 object: self.reference.object.clone(),
352 },
353 target,
354 owner,
355 })
356 }
357}
358
359impl RetainedStoreDeviceExclusionOutcome {
360 pub fn from_exact(
361 reference: &StoreDeviceExclusionOutcomeRef,
362 proposal: RetainedStoreDeviceExclusionProposal,
363 outcome: &StoreDeviceExclusionOutcome,
364 owner: &StoreDeviceRegistration,
365 ) -> Result<Self, StoreProtocolError> {
366 if reference.proposal() != outcome.proposal()
367 || reference.outcome_hash() != outcome.outcome_hash()
368 {
369 return Err(StoreProtocolError::DeviceStateMismatch);
370 }
371 let canonical_outcome = outcome.to_bytes();
372 reference.object().verify(&canonical_outcome)?;
373 Ok(match (reference, outcome) {
374 (
375 StoreDeviceExclusionOutcomeRef::Excluded(reference),
376 StoreDeviceExclusionOutcome::Excluded(_),
377 ) => Self::Excluded {
378 reference: reference.clone(),
379 canonical_outcome,
380 proposal,
381 canonical_owner_registration: owner.to_bytes(),
382 },
383 (
384 StoreDeviceExclusionOutcomeRef::Cancelled(reference),
385 StoreDeviceExclusionOutcome::Cancelled(_),
386 ) => Self::Cancelled {
387 reference: reference.clone(),
388 canonical_outcome,
389 proposal,
390 canonical_owner_registration: owner.to_bytes(),
391 },
392 _ => return Err(StoreProtocolError::DeviceStateMismatch),
393 })
394 }
395
396 pub fn from_verified(
397 reference: &StoreDeviceExclusionOutcomeRef,
398 proposal: RetainedStoreDeviceExclusionProposal,
399 outcome: &VerifiedDeviceExclusionOutcome,
400 ) -> Result<Self, StoreProtocolError> {
401 match (reference, &outcome.object.value) {
402 (
403 StoreDeviceExclusionOutcomeRef::Excluded(reference),
404 StoreDeviceExclusionOutcome::Excluded(_),
405 ) => Ok(Self::Excluded {
406 reference: reference.clone(),
407 canonical_outcome: outcome.object.bytes.clone(),
408 proposal,
409 canonical_owner_registration: outcome.owner.to_bytes(),
410 }),
411 (
412 StoreDeviceExclusionOutcomeRef::Cancelled(reference),
413 StoreDeviceExclusionOutcome::Cancelled(_),
414 ) => Ok(Self::Cancelled {
415 reference: reference.clone(),
416 canonical_outcome: outcome.object.bytes.clone(),
417 proposal,
418 canonical_owner_registration: outcome.owner.to_bytes(),
419 }),
420 _ => Err(StoreProtocolError::DeviceStateMismatch),
421 }
422 }
423
424 pub fn wire_reference(&self) -> StoreDeviceExclusionOutcomeRef {
425 match self {
426 Self::Excluded { reference, .. } => {
427 StoreDeviceExclusionOutcomeRef::Excluded(reference.clone())
428 }
429 Self::Cancelled { reference, .. } => {
430 StoreDeviceExclusionOutcomeRef::Cancelled(reference.clone())
431 }
432 }
433 }
434
435 fn exclusion_reference(&self) -> &StoreDeviceExclusionRef {
436 match self {
437 Self::Excluded { reference, .. } => reference,
438 Self::Cancelled { .. } => unreachable!("verified exclusion changed variant"),
439 }
440 }
441
442 fn cancellation_reference(&self) -> &StoreDeviceExclusionCancellationRef {
443 match self {
444 Self::Cancelled { reference, .. } => reference,
445 Self::Excluded { .. } => unreachable!("verified cancellation changed variant"),
446 }
447 }
448
449 fn verify(
450 self,
451 root: &StoreRootRef,
452 ) -> Result<VerifiedStoreDeviceExclusionOutcome, StoreProtocolError> {
453 let (reference, canonical_outcome, proposal_source, canonical_owner_registration) =
454 match &self {
455 Self::Excluded {
456 reference,
457 canonical_outcome,
458 proposal,
459 canonical_owner_registration,
460 } => (
461 StoreDeviceExclusionOutcomeRef::Excluded(reference.clone()),
462 canonical_outcome,
463 proposal,
464 canonical_owner_registration,
465 ),
466 Self::Cancelled {
467 reference,
468 canonical_outcome,
469 proposal,
470 canonical_owner_registration,
471 } => (
472 StoreDeviceExclusionOutcomeRef::Cancelled(reference.clone()),
473 canonical_outcome,
474 proposal,
475 canonical_owner_registration,
476 ),
477 };
478 reference.object().verify(canonical_outcome)?;
479 let proposal = proposal_source.verify_with_registrations(root)?;
480 let unverified: StoreDeviceExclusionOutcome = serde_json::from_slice(canonical_outcome)?;
481 if unverified.to_bytes() != *canonical_outcome {
482 return Err(StoreProtocolError::Malformed(
483 "retained Store device exclusion outcome is not canonically encoded".to_string(),
484 ));
485 }
486 let owner_reference = match &unverified {
487 StoreDeviceExclusionOutcome::Excluded(exclusion) => &exclusion.owner_registration,
488 StoreDeviceExclusionOutcome::Cancelled(cancellation) => {
489 &cancellation.owner_registration
490 }
491 };
492 let owner =
493 verify_retained_registration(root, owner_reference, canonical_owner_registration)?;
494 let outcome = StoreDeviceExclusionOutcome::parse_at(
495 canonical_outcome,
496 &reference,
497 &proposal.object.value,
498 &proposal.target,
499 &owner,
500 )?;
501 match (&self, outcome) {
502 (Self::Excluded { .. }, StoreDeviceExclusionOutcome::Excluded(exclusion)) => {
503 if exclusion.proof.frozen_device_state != proposal.object.value.frozen_device_state
504 {
505 return Err(StoreProtocolError::DeviceStateMismatch);
506 }
507 Ok(VerifiedStoreDeviceExclusionOutcome::Excluded {
508 source: self,
509 accepted_cut: exclusion.proof.cutoff.clone(),
510 })
511 }
512 (Self::Cancelled { .. }, StoreDeviceExclusionOutcome::Cancelled(_)) => {
513 Ok(VerifiedStoreDeviceExclusionOutcome::Cancelled(self))
514 }
515 _ => Err(StoreProtocolError::DeviceStateMismatch),
516 }
517 }
518}
519
520fn verify_retained_registration(
521 root: &StoreRootRef,
522 reference: &StoreDeviceRegistrationRef,
523 canonical_registration: &[u8],
524) -> Result<StoreDeviceRegistration, StoreProtocolError> {
525 reference.object.verify(canonical_registration)?;
526 let registration =
527 StoreDeviceRegistration::parse_at(canonical_registration, root, reference.device_id)?;
528 if registration.to_bytes() != canonical_registration {
529 return Err(StoreProtocolError::Malformed(
530 "retained Store device registration is not canonically encoded".to_string(),
531 ));
532 }
533 reference.verify_registration(®istration)?;
534 Ok(registration)
535}
536
537impl VerifiedStoreDeviceExclusionOutcome {
538 fn source(&self) -> &RetainedStoreDeviceExclusionOutcome {
539 match self {
540 Self::Excluded { source, .. } | Self::Cancelled(source) => source,
541 }
542 }
543}