/// Contact that was looked up. /// /// IMPORTANT: Existing contacts which have been revoked will be presented as [`ContactResult::Invalid`] /// (unlike [`ContactProvider::get`] which will yield an existing contact as-is)! #[derive(Clone)] pub(crate) enum ContactResult { /// The contact is the user itself.
User,
/// The contact already exists and is valid (not revoked).
ExistingContact(Contact),
/// The contact does not yet exist and is not valid (not revoked).
NewContact(ContactInit),
/// The contact is invalid (has been revoked or never existed).
Invalid(ThreemaId),
} impl ContactResult { pub(crate) fn update_from_work_contact(
&mutself,
work_contact: work_directory::WorkContact,
) -> Result<(), ContactUpdateError> { // Apply update (if possible) matchself {
ContactResult::User | ContactResult::Invalid(_) => Ok(()),
ContactResult::ExistingContact(contact) | ContactResult::NewContact(contact) => {
work_contact.update(contact)
},
}
}
}
/// 1. If `work_directory_request` is present in a non-Work flavored context, log an error, discard the task /// and abort these steps. /// 2. Run the HTTPS requests as defined by [`HttpsRequest`] in parallel and let `responses` be the result. /// The requests should be independent from one another (i.e. if one request fails, it should not impact /// the other). /// 3. Provide `responses` to the associated task as a [`ContactsLookupResponse`] and poll again. pubstruct ContactsLookupInstruction { /// The directory request to be made. pub directory_request: HttpsRequest,
/// The work directory request to be made, if any. pub work_directory_request: Option<HttpsRequest>,
}
/// Possible responses to an [`ContactsLookupInstruction`]. #[derive(Name)] pubstruct ContactsLookupResponse { /// Result for the directory request. pub directory_result: HttpsResult,
/// Result for the work directory request, if one was needed. pub work_directory_result: Option<HttpsResult>,
}
pub(crate) type ContactsLookupLoop = TaskLoop<ContactsLookupInstruction, HashMap<ThreemaId, ContactResult>>;
/// Cache for contact lookups at the directory. Entries expire after 10 minutes. pub(crate) type ContactLookupCache = TimedCache<ThreemaId, CachedContactResult, 600>;
#[derive(DebugVariantNames, VariantNames)] enum State {
Error(CspE2eProtocolError),
Init(InitState),
RequestIdentities(RequestIdentitiesState),
Done,
} impl State { fn poll_init(
context: &mut CspE2eProtocolContext,
state: InitState,
) -> Result<(Self, ContactsLookupLoop), CspE2eProtocolError> { letmut contacts = LookupResult::new(state.identities.len()); for identity in state.identities { // Check if identity is the user itself if identity == context.csp_e2e.user_identity { let _ = contacts.known.insert(identity, ContactResult::User); continue;
}
// Skip lookup if identity is a _Special Contact_ // // Note: Unlike other _Predefined Contact_s, _Special Contact_s will not be added to the // set of contacts and therefore would create a lookup on the directory server every // time which we avoid this way. iflet Some(predefined_contact) = context.config.predefined_contacts.get(&identity)
&& predefined_contact.special
{ let _ = contacts.known.insert(
identity,
ContactResult::NewContact(ContactInit::from(predefined_contact)),
); continue;
}
// Lookup existing contact (and map correctly if revoked) iflet Some(existing_contact) = context.contacts.borrow().get(identity)? { if existing_contact.activity_state == protobuf_contact::ActivityState::Invalid { let _ = contacts
.known
.insert(identity, ContactResult::Invalid(existing_contact.identity));
} else { let _ = contacts
.known
.insert(identity, ContactResult::ExistingContact(existing_contact));
} continue;
}
// Lookup from cache if state.cache_policy == CacheLookupPolicy::Allow
&& let Some(cached_contact) = context.contact_lookup_cache.get_mut(identity)
{ let _ = contacts
.known
.insert(identity, ContactResult::from(cached_contact.clone())); continue;
}
// Couldn't find the contact
contacts.unknown.push(identity);
}
// Check if we have found all contacts already if contacts.unknown.is_empty() { return Ok((Self::Done, ContactsLookupLoop::Done(contacts.known)));
}
// Request the unknown identities from the directory (and the work directory, if needed).
info!(identities = ?contacts.unknown, "Fetching identities"); let instruction = ContactsLookupInstruction {
directory_request: directory::request_identities(
&context.client_info,
&context.config.directory_server_url,
&context.csp_e2e.flavor,
&contacts.unknown,
),
work_directory_request: match &context.csp_e2e.flavor {
Flavor::Consumer => None,
Flavor::Work(work_context) => Some(work_directory::request_contacts(
&context.client_info,
&context.config.work_server_url,
work_context,
&contacts.unknown,
)),
},
};
Ok(( Self::RequestIdentities(RequestIdentitiesState {
contacts,
response: None,
}),
ContactsLookupLoop::Instruction(instruction),
))
}
fn poll_request(
context: &mut CspE2eProtocolContext, mut state: RequestIdentitiesState,
) -> Result<(Self, ContactsLookupLoop), CspE2eProtocolError> { // Ensure the caller provided the response let Some(response) = state.response else { return Err(CspE2eProtocolError::InvalidState(formatcp!( "{} result was not provided for '{}' state",
ContactsLookupResponse::NAME,
State::REQUEST_IDENTITIES,
)));
};
// Add all determined contact inits let directory_result = directory::handle_identities_result(response.directory_result)?; formut contact in directory_result { // Ensure the contact was requested if !unknown.remove(&contact.identity) {
warn!(identity = ?contact.identity, "Discarding identity not requested"); continue;
}
// If the contact is a _Predefined Contact_, update it iflet Some(predefined_contact) = context.config.predefined_contacts.get(&contact.identity)
&& let Err(error) = predefined_contact.update(&mut contact)
{
error!(identity = ?contact.identity, ?error, "Unable to update contact from predefined contact"); return Err(CspE2eProtocolError::InternalError(
format!("Unable to update contact from predefined contact: {error}").into(),
));
}
// Add the contact let Entry::Vacant(entry) = state.contacts.known.entry(contact.identity) else {
error!(identity = ?contact.identity, "Accounting error, identity already known"); continue;
}; let _ = entry.insert(ContactResult::NewContact(contact.clone()));
}
// The directory server only yields us known and unrevoked identities, so we'll need to // add the remaining ones as invalid for identity in unknown { match state.contacts.known.entry(identity) {
Entry::Occupied(_) => {
error!(?identity, "Accounting error, identity already known");
},
Entry::Vacant(entry) => { let _ = entry.insert(ContactResult::Invalid(identity));
},
}
}
}
// Update all contacts that could be identified as work contacts of the same subscription match (&context.csp_e2e.flavor, response.work_directory_result) {
(Flavor::Consumer, None) => {},
(Flavor::Work(_), Some(work_directory_result)) => { let work_directory_result = work_directory::handle_contacts_result(work_directory_result)?; for work_contact in work_directory_result { let identity = work_contact.identity;
// Update the contact let Entry::Occupied(mut entry) = state.contacts.known.entry(work_contact.identity) else {
warn!(?identity, "Discarding work contact not requested"); continue;
}; iflet Err(error) = entry.get_mut().update_from_work_contact(work_contact) {
error!(
?identity,
?error, "Unable to update contact result from work contact"
); return Err(CspE2eProtocolError::ServerError(format!( "Unable to update contact result from work contact: {error}"
)));
}
}
},
(Flavor::Consumer, Some(_)) => {
error!("Discarding unexpected work directory result for consumer flavor");
},
(Flavor::Work(_), None) => { let message = "Missing work directory result for work flavor";
error!(message); return Err(CspE2eProtocolError::InternalError(message.into()));
},
}
// TODO(LIB-41): Run the contact import flow that is still TBD to update the verification // level if a matching phone number / email was found and set sync state accordingly.
// Add all determined contacts to the cache for contact in state.contacts.known.values() { iflet Some((identity, cached_contact)) = contact.clone().into() { let _ = context.contact_lookup_cache.insert(identity, cached_contact);
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.