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