Skip to main content

im_core/groups/
service.rs

1#[cfg(feature = "group-e2ee")]
2use crate::internal::auth::session::{AsyncSessionProvider, SessionProvider};
3#[cfg(feature = "group-e2ee")]
4use crate::internal::group_e2ee::provider::GroupMlsProvider;
5#[cfg(feature = "group-e2ee")]
6use crate::internal::transport::{AsyncAuthenticatedRpcTransport, AuthenticatedRpcTransport};
7
8pub struct GroupService<'a> {
9    client: &'a crate::core::ImClient,
10}
11
12impl<'a> GroupService<'a> {
13    pub(crate) fn new(client: &'a crate::core::ImClient) -> Self {
14        Self { client }
15    }
16
17    pub fn publish_key_package(
18        &self,
19        request: super::GroupKeyPackagePublishRequest,
20    ) -> crate::ImResult<super::GroupKeyPackagePublishResult> {
21        #[cfg(feature = "group-e2ee")]
22        {
23            self.publish_key_package_with_group_e2ee(request)
24        }
25        #[cfg(not(feature = "group-e2ee"))]
26        {
27            let _ = request;
28            Err(crate::ImError::unsupported("group-e2ee"))
29        }
30    }
31
32    pub async fn publish_key_package_async(
33        &self,
34        request: super::GroupKeyPackagePublishRequest,
35    ) -> crate::ImResult<super::GroupKeyPackagePublishResult> {
36        #[cfg(feature = "group-e2ee")]
37        {
38            self.publish_key_package_with_group_e2ee_async(request)
39                .await
40        }
41        #[cfg(not(feature = "group-e2ee"))]
42        {
43            let _ = request;
44            Err(crate::ImError::unsupported("group-e2ee"))
45        }
46    }
47
48    pub fn create(
49        &self,
50        request: super::GroupCreateRequest,
51    ) -> crate::ImResult<super::GroupReadResult> {
52        let secure_required = group_create_uses_e2ee(&request);
53        #[cfg(not(feature = "group-e2ee"))]
54        if secure_required {
55            return Err(crate::ImError::unsupported("group-e2ee"));
56        }
57        #[cfg(feature = "group-e2ee")]
58        let secure_provider = if secure_required {
59            ensure_group_e2ee_service_available(self.client, false)?;
60            Some(crate::internal::group_e2ee::storage::native_provider_for_client(self.client)?)
61        } else {
62            None
63        };
64        let mut result = crate::internal::group_runtime::lifecycle::GroupLifecycleRuntime::new(
65            self.client,
66            crate::internal::auth::session::FileSessionProvider::new(self.client),
67            crate::internal::transport::CoreHttpTransport::new(self.client),
68        )
69        .create(request, None)?;
70        crate::internal::group_runtime::projection::project_group_snapshot(self.client, &result);
71        self.refresh_group_state(&mut result, true);
72        crate::internal::group_runtime::projection::project_group_snapshot(self.client, &result);
73        #[cfg(feature = "group-e2ee")]
74        if secure_required {
75            let group =
76                group_did(&result).ok_or_else(|| crate::ImError::LocalStateUnavailable {
77                    detail: "group E2EE create requires created group DID".to_owned(),
78                })?;
79            let group_state_ref = group_state_ref_from_result(&group, &result);
80            let secure = crate::internal::group_e2ee::lifecycle::GroupE2eeLifecycleRuntime::new(
81                self.client,
82                crate::internal::auth::session::FileSessionProvider::new(self.client),
83                crate::internal::transport::CoreHttpTransport::new(self.client),
84                secure_provider.expect("secure provider initialized when secure_required"),
85            )
86            .create_secure_group(
87                crate::internal::group_e2ee::lifecycle::GroupE2eeCreateInput {
88                    group: crate::ids::GroupRef::parse(&group)?,
89                    credentials: None,
90                    service_did: None,
91                    group_state_ref,
92                },
93            )?;
94            result.warnings.extend(secure.warnings);
95        }
96        Ok(result)
97    }
98
99    pub async fn create_async(
100        &self,
101        request: super::GroupCreateRequest,
102    ) -> crate::ImResult<super::GroupReadResult> {
103        let secure_required = group_create_uses_e2ee(&request);
104        #[cfg(not(feature = "group-e2ee"))]
105        if secure_required {
106            return Err(crate::ImError::unsupported("group-e2ee"));
107        }
108        #[cfg(feature = "group-e2ee")]
109        if secure_required {
110            ensure_group_e2ee_service_available_async(self.client, false).await?;
111        }
112        let mut result = crate::internal::group_runtime::lifecycle::GroupLifecycleRuntime::new(
113            self.client,
114            crate::internal::auth::session::FileSessionProvider::new(self.client),
115            crate::internal::transport::CoreHttpTransport::new(self.client),
116        )
117        .create_async(request, None)
118        .await?;
119        let _ = crate::internal::group_runtime::projection::project_group_snapshot_async(
120            self.client,
121            &result,
122        )
123        .await;
124        self.refresh_group_state_async(&mut result, true).await;
125        let _ = crate::internal::group_runtime::projection::project_group_snapshot_async(
126            self.client,
127            &result,
128        )
129        .await;
130        #[cfg(feature = "group-e2ee")]
131        if secure_required {
132            let group =
133                group_did(&result).ok_or_else(|| crate::ImError::LocalStateUnavailable {
134                    detail: "group E2EE create requires created group DID".to_owned(),
135                })?;
136            let group_state_ref = group_state_ref_from_result(&group, &result);
137            let secure = crate::internal::group_e2ee::lifecycle::GroupE2eeLifecycleRuntime::new(
138                self.client,
139                crate::internal::auth::session::FileSessionProvider::new(self.client),
140                crate::internal::transport::CoreHttpTransport::new(self.client),
141                crate::internal::group_e2ee::storage::native_provider_for_client(self.client)?,
142            )
143            .create_secure_group_async(
144                crate::internal::group_e2ee::lifecycle::GroupE2eeCreateInput {
145                    group: crate::ids::GroupRef::parse(&group)?,
146                    credentials: None,
147                    service_did: None,
148                    group_state_ref,
149                },
150            )
151            .await?;
152            result.warnings.extend(secure.warnings);
153        }
154        Ok(result)
155    }
156
157    pub fn join(
158        &self,
159        request: super::GroupJoinRequest,
160    ) -> crate::ImResult<super::GroupReadResult> {
161        let group = request.group.as_str().to_string();
162        let mut result = crate::internal::group_runtime::lifecycle::GroupLifecycleRuntime::new(
163            self.client,
164            crate::internal::auth::session::FileSessionProvider::new(self.client),
165            crate::internal::transport::CoreHttpTransport::new(self.client),
166        )
167        .join(request, None)?;
168        crate::internal::group_runtime::projection::project_group_snapshot(self.client, &result);
169        self.refresh_group_state_for(&mut result, &group, true);
170        Ok(result)
171    }
172
173    pub async fn join_async(
174        &self,
175        request: super::GroupJoinRequest,
176    ) -> crate::ImResult<super::GroupReadResult> {
177        let group = request.group.as_str().to_string();
178        let mut result = crate::internal::group_runtime::lifecycle::GroupLifecycleRuntime::new(
179            self.client,
180            crate::internal::auth::session::FileSessionProvider::new(self.client),
181            crate::internal::transport::CoreHttpTransport::new(self.client),
182        )
183        .join_async(request, None)
184        .await?;
185        let _ = crate::internal::group_runtime::projection::project_group_snapshot_async(
186            self.client,
187            &result,
188        )
189        .await;
190        self.refresh_group_state_for_async(&mut result, &group, true)
191            .await;
192        Ok(result)
193    }
194
195    pub fn leave(
196        &self,
197        request: super::GroupLeaveRequest,
198    ) -> crate::ImResult<super::GroupReadResult> {
199        let group = request.group.as_str().to_string();
200        #[cfg(not(feature = "group-e2ee"))]
201        if request.security.required() {
202            return Err(crate::ImError::unsupported("group-e2ee"));
203        }
204        #[cfg(feature = "group-e2ee")]
205        let secure_provider = if request.security.required() {
206            ensure_group_e2ee_service_available(self.client, false)?;
207            Some(crate::internal::group_e2ee::storage::native_provider_for_client(self.client)?)
208        } else {
209            None
210        };
211        if let Ok(Some(snapshot)) =
212            crate::internal::group_runtime::cache::cached_group_snapshot(self.client, &group)
213        {
214            if crate::internal::group_runtime::cache::is_active_group_owner(&snapshot) {
215                return Err(crate::ImError::invalid_input(
216                    Some("group".to_string()),
217                    "group owner cannot leave the group",
218                ));
219            }
220            if crate::internal::group_runtime::cache::group_snapshot_uses_e2ee(&snapshot)
221                && !request.security.required()
222            {
223                return Err(crate::ImError::unsupported("group-e2ee"));
224            }
225        }
226        if request.security.required() {
227            #[cfg(feature = "group-e2ee")]
228            {
229                let secure =
230                    crate::internal::group_e2ee::lifecycle::GroupE2eeLifecycleRuntime::new(
231                        self.client,
232                        crate::internal::auth::session::FileSessionProvider::new(self.client),
233                        crate::internal::transport::CoreHttpTransport::new(self.client),
234                        secure_provider.expect("secure provider initialized when secure_required"),
235                    )
236                    .leave_secure_group(
237                        crate::internal::group_e2ee::lifecycle::GroupE2eeLeaveInput {
238                            group: request.group,
239                            reason_text: request.reason_text,
240                            owner_leave_commit: false,
241                            credentials: None,
242                        },
243                    )?;
244                return Ok(super::GroupReadResult::from_raw_response(
245                    secure.delivery,
246                    secure.warnings,
247                ));
248            }
249        }
250        let result = crate::internal::group_runtime::lifecycle::GroupLifecycleRuntime::new(
251            self.client,
252            crate::internal::auth::session::FileSessionProvider::new(self.client),
253            crate::internal::transport::CoreHttpTransport::new(self.client),
254        )
255        .leave(request, None)?;
256        crate::internal::group_runtime::projection::project_group_left(self.client, &group);
257        Ok(result)
258    }
259
260    pub async fn leave_async(
261        &self,
262        request: super::GroupLeaveRequest,
263    ) -> crate::ImResult<super::GroupReadResult> {
264        let group = request.group.as_str().to_string();
265        #[cfg(not(feature = "group-e2ee"))]
266        if request.security.required() {
267            return Err(crate::ImError::unsupported("group-e2ee"));
268        }
269        #[cfg(feature = "group-e2ee")]
270        if request.security.required() {
271            ensure_group_e2ee_service_available_async(self.client, false).await?;
272            if let Ok(Some(snapshot)) =
273                crate::internal::group_runtime::cache::cached_group_snapshot_async(
274                    self.client,
275                    &group,
276                )
277                .await
278            {
279                if crate::internal::group_runtime::cache::is_active_group_owner(&snapshot) {
280                    return Err(crate::ImError::invalid_input(
281                        Some("group".to_string()),
282                        "group owner cannot leave the group",
283                    ));
284                }
285            }
286            let session_provider =
287                crate::internal::auth::session::FileSessionProvider::new(self.client);
288            let mut transport = crate::internal::transport::CoreHttpTransport::new(self.client);
289            let secure = crate::internal::group_e2ee::lifecycle::leave_secure_group_request_async(
290                self.client,
291                &session_provider,
292                &mut transport,
293                crate::internal::group_e2ee::lifecycle::GroupE2eeLeaveInput {
294                    group: request.group,
295                    reason_text: request.reason_text,
296                    owner_leave_commit: false,
297                    credentials: None,
298                },
299            )
300            .await?;
301            return Ok(super::GroupReadResult::from_raw_response(
302                secure.delivery,
303                secure.warnings,
304            ));
305        }
306        if let Ok(Some(snapshot)) =
307            crate::internal::group_runtime::cache::cached_group_snapshot_async(self.client, &group)
308                .await
309        {
310            if crate::internal::group_runtime::cache::is_active_group_owner(&snapshot) {
311                return Err(crate::ImError::invalid_input(
312                    Some("group".to_string()),
313                    "group owner cannot leave the group",
314                ));
315            }
316            if crate::internal::group_runtime::cache::group_snapshot_uses_e2ee(&snapshot)
317                && !request.security.required()
318            {
319                return Err(crate::ImError::unsupported("group-e2ee"));
320            }
321        }
322        let result = crate::internal::group_runtime::lifecycle::GroupLifecycleRuntime::new(
323            self.client,
324            crate::internal::auth::session::FileSessionProvider::new(self.client),
325            crate::internal::transport::CoreHttpTransport::new(self.client),
326        )
327        .leave_async(request, None)
328        .await?;
329        let _ = crate::internal::group_runtime::projection::project_group_left_async(
330            self.client,
331            &group,
332        )
333        .await;
334        Ok(result)
335    }
336
337    pub fn add_member(
338        &self,
339        request: super::GroupMemberMutationRequest,
340    ) -> crate::ImResult<super::GroupReadResult> {
341        let group = request.group.as_str().to_string();
342        let resolved_member = resolve_group_member(self.client, &request.member)?;
343        #[cfg(feature = "group-e2ee")]
344        let member = resolved_member.did.as_str().to_string();
345        #[cfg(feature = "group-e2ee")]
346        let reason_text = request.reason_text.clone();
347        #[cfg(not(feature = "group-e2ee"))]
348        if request.security.required() {
349            return Err(crate::ImError::unsupported("group-e2ee"));
350        }
351        #[cfg(feature = "group-e2ee")]
352        let secure_provider = if request.security.required() {
353            ensure_group_e2ee_service_available(self.client, true)?;
354            Some(crate::internal::group_e2ee::storage::native_provider_for_client(self.client)?)
355        } else {
356            None
357        };
358        if let Ok(Some(snapshot)) = crate::internal::group_runtime::cache::cached_group_snapshot(
359            self.client,
360            request.group.as_str(),
361        ) {
362            if crate::internal::group_runtime::cache::group_snapshot_uses_e2ee(&snapshot)
363                && !request.security.required()
364            {
365                return Err(crate::ImError::unsupported("group-e2ee"));
366            }
367        }
368        let mut result = crate::internal::group_runtime::lifecycle::GroupLifecycleRuntime::new(
369            self.client,
370            crate::internal::auth::session::FileSessionProvider::new(self.client),
371            crate::internal::transport::CoreHttpTransport::new(self.client),
372        )
373        .add_member(
374            resolved_group_member_request(request, &resolved_member),
375            None,
376        )?;
377        result.resolved_member = Some(resolved_member.clone());
378        crate::internal::group_runtime::projection::project_group_snapshot(self.client, &result);
379        project_group_system_event_best_effort(self.client, &group, &mut result);
380        self.refresh_group_state_for(&mut result, &group, true);
381        #[cfg(feature = "group-e2ee")]
382        if let Some(secure_provider) = secure_provider {
383            let group_state_ref = group_state_ref_from_result(&group, &result);
384            let secure = crate::internal::group_e2ee::lifecycle::GroupE2eeLifecycleRuntime::new(
385                self.client,
386                crate::internal::auth::session::FileSessionProvider::new(self.client),
387                crate::internal::transport::CoreHttpTransport::new(self.client),
388                secure_provider,
389            )
390            .add_secure_member(
391                crate::internal::group_e2ee::lifecycle::GroupE2eeMemberMutationInput {
392                    group: crate::ids::GroupRef::parse(&group)?,
393                    member: crate::ids::Did::parse(&member)?,
394                    reason_text,
395                    leave_request_id: None,
396                    credentials: None,
397                    service_did: None,
398                    group_state_ref,
399                },
400            )?;
401            result = super::GroupReadResult::from_raw_response(secure.delivery, secure.warnings);
402            result.resolved_member = Some(resolved_member.clone());
403            project_group_system_event_best_effort(self.client, &group, &mut result);
404            self.refresh_group_state_for(&mut result, &group, true);
405        }
406        Ok(result)
407    }
408
409    pub async fn add_member_async(
410        &self,
411        request: super::GroupMemberMutationRequest,
412    ) -> crate::ImResult<super::GroupReadResult> {
413        let secure_required = request.security.required();
414        let group = request.group.as_str().to_string();
415        let resolved_member = resolve_group_member_async(self.client, &request.member).await?;
416        #[cfg(feature = "group-e2ee")]
417        let member = resolved_member.did.as_str().to_string();
418        #[cfg(feature = "group-e2ee")]
419        let reason_text = request.reason_text.clone();
420        #[cfg(not(feature = "group-e2ee"))]
421        if secure_required {
422            return Err(crate::ImError::unsupported("group-e2ee"));
423        }
424        #[cfg(feature = "group-e2ee")]
425        if secure_required {
426            ensure_group_e2ee_service_available_async(self.client, true).await?;
427        }
428        if let Ok(Some(snapshot)) =
429            crate::internal::group_runtime::cache::cached_group_snapshot_async(
430                self.client,
431                request.group.as_str(),
432            )
433            .await
434        {
435            if crate::internal::group_runtime::cache::group_snapshot_uses_e2ee(&snapshot)
436                && !secure_required
437            {
438                return Err(crate::ImError::unsupported("group-e2ee"));
439            }
440        }
441        let mut result = crate::internal::group_runtime::lifecycle::GroupLifecycleRuntime::new(
442            self.client,
443            crate::internal::auth::session::FileSessionProvider::new(self.client),
444            crate::internal::transport::CoreHttpTransport::new(self.client),
445        )
446        .add_member_async(
447            resolved_group_member_request(request, &resolved_member),
448            None,
449        )
450        .await?;
451        result.resolved_member = Some(resolved_member.clone());
452        let _ = crate::internal::group_runtime::projection::project_group_snapshot_async(
453            self.client,
454            &result,
455        )
456        .await;
457        project_group_system_event_best_effort_async(self.client, &group, &mut result).await;
458        self.refresh_group_state_for_async(&mut result, &group, true)
459            .await;
460        #[cfg(feature = "group-e2ee")]
461        if secure_required {
462            let group_state_ref = group_state_ref_from_result(&group, &result);
463            let secure = crate::internal::group_e2ee::lifecycle::GroupE2eeLifecycleRuntime::new(
464                self.client,
465                crate::internal::auth::session::FileSessionProvider::new(self.client),
466                crate::internal::transport::CoreHttpTransport::new(self.client),
467                crate::internal::group_e2ee::storage::native_provider_for_client(self.client)?,
468            )
469            .add_secure_member_async(
470                crate::internal::group_e2ee::lifecycle::GroupE2eeMemberMutationInput {
471                    group: crate::ids::GroupRef::parse(&group)?,
472                    member: crate::ids::Did::parse(&member)?,
473                    reason_text,
474                    leave_request_id: None,
475                    credentials: None,
476                    service_did: None,
477                    group_state_ref,
478                },
479            )
480            .await?;
481            result = super::GroupReadResult::from_raw_response(secure.delivery, secure.warnings);
482            result.resolved_member = Some(resolved_member.clone());
483            project_group_system_event_best_effort_async(self.client, &group, &mut result).await;
484            self.refresh_group_state_for_async(&mut result, &group, true)
485                .await;
486        }
487        Ok(result)
488    }
489
490    pub fn remove_member(
491        &self,
492        request: super::GroupMemberMutationRequest,
493    ) -> crate::ImResult<super::GroupReadResult> {
494        let group = request.group.as_str().to_string();
495        let resolved_member = resolve_group_member(self.client, &request.member)?;
496        #[cfg(not(feature = "group-e2ee"))]
497        if request.security.required() {
498            return Err(crate::ImError::unsupported("group-e2ee"));
499        }
500        #[cfg(feature = "group-e2ee")]
501        let secure_provider = if request.security.required() {
502            ensure_group_e2ee_service_available(self.client, false)?;
503            Some(crate::internal::group_e2ee::storage::native_provider_for_client(self.client)?)
504        } else {
505            None
506        };
507        if let Ok(Some(snapshot)) = crate::internal::group_runtime::cache::cached_group_snapshot(
508            self.client,
509            request.group.as_str(),
510        ) {
511            if crate::internal::group_runtime::cache::group_snapshot_uses_e2ee(&snapshot)
512                && !request.security.required()
513            {
514                return Err(crate::ImError::unsupported("group-e2ee"));
515            }
516        }
517        if request.security.required() {
518            #[cfg(feature = "group-e2ee")]
519            {
520                let group_state_ref = group_state_ref_from_current_group(self, &group);
521                let secure =
522                    crate::internal::group_e2ee::lifecycle::GroupE2eeLifecycleRuntime::new(
523                        self.client,
524                        crate::internal::auth::session::FileSessionProvider::new(self.client),
525                        crate::internal::transport::CoreHttpTransport::new(self.client),
526                        secure_provider.expect("secure provider initialized when secure_required"),
527                    )
528                    .remove_secure_member(
529                        crate::internal::group_e2ee::lifecycle::GroupE2eeMemberMutationInput {
530                            group: request.group,
531                            member: resolved_member.did.clone(),
532                            reason_text: request.reason_text,
533                            leave_request_id: request.leave_request_id,
534                            credentials: None,
535                            service_did: None,
536                            group_state_ref,
537                        },
538                    )?;
539                let mut result =
540                    super::GroupReadResult::from_raw_response(secure.delivery, secure.warnings);
541                result.resolved_member = Some(resolved_member);
542                self.refresh_group_state_for(&mut result, &group, true);
543                return Ok(result);
544            }
545        }
546        let mut result = crate::internal::group_runtime::lifecycle::GroupLifecycleRuntime::new(
547            self.client,
548            crate::internal::auth::session::FileSessionProvider::new(self.client),
549            crate::internal::transport::CoreHttpTransport::new(self.client),
550        )
551        .remove_member(
552            resolved_group_member_request(request, &resolved_member),
553            None,
554        )?;
555        result.resolved_member = Some(resolved_member.clone());
556        crate::internal::group_runtime::projection::project_group_snapshot(self.client, &result);
557        self.refresh_group_state_for(&mut result, &group, true);
558        Ok(result)
559    }
560
561    pub async fn remove_member_async(
562        &self,
563        request: super::GroupMemberMutationRequest,
564    ) -> crate::ImResult<super::GroupReadResult> {
565        let secure_required = request.security.required();
566        let group = request.group.as_str().to_string();
567        let resolved_member = resolve_group_member_async(self.client, &request.member).await?;
568        #[cfg(not(feature = "group-e2ee"))]
569        if secure_required {
570            return Err(crate::ImError::unsupported("group-e2ee"));
571        }
572        #[cfg(feature = "group-e2ee")]
573        if secure_required {
574            ensure_group_e2ee_service_available_async(self.client, false).await?;
575        }
576        if let Ok(Some(snapshot)) =
577            crate::internal::group_runtime::cache::cached_group_snapshot_async(
578                self.client,
579                request.group.as_str(),
580            )
581            .await
582        {
583            if crate::internal::group_runtime::cache::group_snapshot_uses_e2ee(&snapshot)
584                && !secure_required
585            {
586                return Err(crate::ImError::unsupported("group-e2ee"));
587            }
588        }
589        #[cfg(feature = "group-e2ee")]
590        if secure_required {
591            let group_state_ref = group_state_ref_from_current_group_async(self, &group).await;
592            let secure = crate::internal::group_e2ee::lifecycle::GroupE2eeLifecycleRuntime::new(
593                self.client,
594                crate::internal::auth::session::FileSessionProvider::new(self.client),
595                crate::internal::transport::CoreHttpTransport::new(self.client),
596                crate::internal::group_e2ee::storage::native_provider_for_client(self.client)?,
597            )
598            .remove_secure_member_async(
599                crate::internal::group_e2ee::lifecycle::GroupE2eeMemberMutationInput {
600                    group: request.group,
601                    member: resolved_member.did.clone(),
602                    reason_text: request.reason_text,
603                    leave_request_id: request.leave_request_id,
604                    credentials: None,
605                    service_did: None,
606                    group_state_ref,
607                },
608            )
609            .await?;
610            let mut result =
611                super::GroupReadResult::from_raw_response(secure.delivery, secure.warnings);
612            result.resolved_member = Some(resolved_member);
613            self.refresh_group_state_for_async(&mut result, &group, true)
614                .await;
615            return Ok(result);
616        }
617        let mut result = crate::internal::group_runtime::lifecycle::GroupLifecycleRuntime::new(
618            self.client,
619            crate::internal::auth::session::FileSessionProvider::new(self.client),
620            crate::internal::transport::CoreHttpTransport::new(self.client),
621        )
622        .remove_member_async(
623            resolved_group_member_request(request, &resolved_member),
624            None,
625        )
626        .await?;
627        result.resolved_member = Some(resolved_member.clone());
628        let _ = crate::internal::group_runtime::projection::project_group_snapshot_async(
629            self.client,
630            &result,
631        )
632        .await;
633        self.refresh_group_state_for_async(&mut result, &group, true)
634            .await;
635        Ok(result)
636    }
637
638    pub fn process_e2ee_leave_request(
639        &self,
640        request: super::GroupE2eeProcessLeaveRequest,
641    ) -> crate::ImResult<super::GroupReadResult> {
642        #[cfg(not(feature = "group-e2ee"))]
643        {
644            let _ = request;
645            return Err(crate::ImError::unsupported("group-e2ee"));
646        }
647        #[cfg(feature = "group-e2ee")]
648        {
649            ensure_group_e2ee_service_available(self.client, false)?;
650            let group = request.group.as_str().to_owned();
651            let resolved_member = resolve_group_member(self.client, &request.member)?;
652            let group_state_ref = group_state_ref_from_current_group(self, &group);
653            let secure = crate::internal::group_e2ee::lifecycle::GroupE2eeLifecycleRuntime::new(
654                self.client,
655                crate::internal::auth::session::FileSessionProvider::new(self.client),
656                crate::internal::transport::CoreHttpTransport::new(self.client),
657                crate::internal::group_e2ee::storage::native_provider_for_client(self.client)?,
658            )
659            .process_leave_request(
660                crate::internal::group_e2ee::lifecycle::GroupE2eeMemberMutationInput {
661                    group: request.group,
662                    member: resolved_member.did.clone(),
663                    reason_text: request.reason_text,
664                    leave_request_id: Some(request.leave_request_id),
665                    credentials: None,
666                    service_did: None,
667                    group_state_ref,
668                },
669            )?;
670            let mut result =
671                super::GroupReadResult::from_raw_response(secure.delivery, secure.warnings);
672            result.resolved_member = Some(resolved_member);
673            self.refresh_group_state_for(&mut result, &group, true);
674            Ok(result)
675        }
676    }
677
678    pub async fn process_e2ee_leave_request_async(
679        &self,
680        request: super::GroupE2eeProcessLeaveRequest,
681    ) -> crate::ImResult<super::GroupReadResult> {
682        #[cfg(not(feature = "group-e2ee"))]
683        {
684            let _ = request;
685            return Err(crate::ImError::unsupported("group-e2ee"));
686        }
687        #[cfg(feature = "group-e2ee")]
688        {
689            ensure_group_e2ee_service_available_async(self.client, false).await?;
690            let group = request.group.as_str().to_owned();
691            let resolved_member = resolve_group_member_async(self.client, &request.member).await?;
692            let group_state_ref = group_state_ref_from_current_group_async(self, &group).await;
693            let secure = crate::internal::group_e2ee::lifecycle::GroupE2eeLifecycleRuntime::new(
694                self.client,
695                crate::internal::auth::session::FileSessionProvider::new(self.client),
696                crate::internal::transport::CoreHttpTransport::new(self.client),
697                crate::internal::group_e2ee::storage::native_provider_for_client(self.client)?,
698            )
699            .process_leave_request_async(
700                crate::internal::group_e2ee::lifecycle::GroupE2eeMemberMutationInput {
701                    group: request.group,
702                    member: resolved_member.did.clone(),
703                    reason_text: request.reason_text,
704                    leave_request_id: Some(request.leave_request_id),
705                    credentials: None,
706                    service_did: None,
707                    group_state_ref,
708                },
709            )
710            .await?;
711            let mut result =
712                super::GroupReadResult::from_raw_response(secure.delivery, secure.warnings);
713            result.resolved_member = Some(resolved_member);
714            self.refresh_group_state_for_async(&mut result, &group, true)
715                .await;
716            Ok(result)
717        }
718    }
719
720    pub fn update_member_key(
721        &self,
722        request: super::GroupE2eeUpdateKeyRequest,
723    ) -> crate::ImResult<super::GroupReadResult> {
724        #[cfg(not(feature = "group-e2ee"))]
725        {
726            let _ = request;
727            return Err(crate::ImError::unsupported("group-e2ee"));
728        }
729        #[cfg(feature = "group-e2ee")]
730        {
731            ensure_group_e2ee_service_available(self.client, true)?;
732            let group = request.group.as_str().to_owned();
733            let resolved_member = resolve_group_member(self.client, &request.member)?;
734            let secure = crate::internal::group_e2ee::lifecycle::GroupE2eeLifecycleRuntime::new(
735                self.client,
736                crate::internal::auth::session::FileSessionProvider::new(self.client),
737                crate::internal::transport::CoreHttpTransport::new(self.client),
738                crate::internal::group_e2ee::storage::native_provider_for_client(self.client)?,
739            )
740            .update_member_key(
741                crate::internal::group_e2ee::lifecycle::GroupE2eeKeyReplacementInput {
742                    group: request.group,
743                    member: resolved_member.did.clone(),
744                    device_id: request.device_id.unwrap_or_default(),
745                    credentials: None,
746                    service_did: None,
747                },
748            )?;
749            let mut result =
750                super::GroupReadResult::from_raw_response(secure.delivery, secure.warnings);
751            result.resolved_member = Some(resolved_member);
752            self.refresh_group_state_for(&mut result, &group, true);
753            Ok(result)
754        }
755    }
756
757    pub async fn update_member_key_async(
758        &self,
759        request: super::GroupE2eeUpdateKeyRequest,
760    ) -> crate::ImResult<super::GroupReadResult> {
761        #[cfg(not(feature = "group-e2ee"))]
762        {
763            let _ = request;
764            return Err(crate::ImError::unsupported("group-e2ee"));
765        }
766        #[cfg(feature = "group-e2ee")]
767        {
768            ensure_group_e2ee_service_available_async(self.client, true).await?;
769            let group = request.group.as_str().to_owned();
770            let resolved_member = resolve_group_member_async(self.client, &request.member).await?;
771            let secure = crate::internal::group_e2ee::lifecycle::GroupE2eeLifecycleRuntime::new(
772                self.client,
773                crate::internal::auth::session::FileSessionProvider::new(self.client),
774                crate::internal::transport::CoreHttpTransport::new(self.client),
775                crate::internal::group_e2ee::storage::native_provider_for_client(self.client)?,
776            )
777            .update_member_key_async(
778                crate::internal::group_e2ee::lifecycle::GroupE2eeKeyReplacementInput {
779                    group: request.group,
780                    member: resolved_member.did.clone(),
781                    device_id: request.device_id.unwrap_or_default(),
782                    credentials: None,
783                    service_did: None,
784                },
785            )
786            .await?;
787            let mut result =
788                super::GroupReadResult::from_raw_response(secure.delivery, secure.warnings);
789            result.resolved_member = Some(resolved_member);
790            self.refresh_group_state_for_async(&mut result, &group, true)
791                .await;
792            Ok(result)
793        }
794    }
795
796    pub fn recover_member(
797        &self,
798        request: super::GroupE2eeRecoverMemberRequest,
799    ) -> crate::ImResult<super::GroupReadResult> {
800        #[cfg(not(feature = "group-e2ee"))]
801        {
802            let _ = request;
803            return Err(crate::ImError::unsupported("group-e2ee"));
804        }
805        #[cfg(feature = "group-e2ee")]
806        {
807            ensure_group_e2ee_service_available(self.client, true)?;
808            let group = request.group.as_str().to_owned();
809            let resolved_member = resolve_group_member(self.client, &request.member)?;
810            let secure = crate::internal::group_e2ee::lifecycle::GroupE2eeLifecycleRuntime::new(
811                self.client,
812                crate::internal::auth::session::FileSessionProvider::new(self.client),
813                crate::internal::transport::CoreHttpTransport::new(self.client),
814                crate::internal::group_e2ee::storage::native_provider_for_client(self.client)?,
815            )
816            .recover_member(
817                crate::internal::group_e2ee::lifecycle::GroupE2eeKeyReplacementInput {
818                    group: request.group,
819                    member: resolved_member.did.clone(),
820                    device_id: request.device_id.unwrap_or_default(),
821                    credentials: None,
822                    service_did: None,
823                },
824            )?;
825            let mut result =
826                super::GroupReadResult::from_raw_response(secure.delivery, secure.warnings);
827            result.resolved_member = Some(resolved_member);
828            self.refresh_group_state_for(&mut result, &group, true);
829            Ok(result)
830        }
831    }
832
833    pub async fn recover_member_async(
834        &self,
835        request: super::GroupE2eeRecoverMemberRequest,
836    ) -> crate::ImResult<super::GroupReadResult> {
837        #[cfg(not(feature = "group-e2ee"))]
838        {
839            let _ = request;
840            return Err(crate::ImError::unsupported("group-e2ee"));
841        }
842        #[cfg(feature = "group-e2ee")]
843        {
844            ensure_group_e2ee_service_available_async(self.client, true).await?;
845            let group = request.group.as_str().to_owned();
846            let resolved_member = resolve_group_member_async(self.client, &request.member).await?;
847            let secure = crate::internal::group_e2ee::lifecycle::GroupE2eeLifecycleRuntime::new(
848                self.client,
849                crate::internal::auth::session::FileSessionProvider::new(self.client),
850                crate::internal::transport::CoreHttpTransport::new(self.client),
851                crate::internal::group_e2ee::storage::native_provider_for_client(self.client)?,
852            )
853            .recover_member_async(
854                crate::internal::group_e2ee::lifecycle::GroupE2eeKeyReplacementInput {
855                    group: request.group,
856                    member: resolved_member.did.clone(),
857                    device_id: request.device_id.unwrap_or_default(),
858                    credentials: None,
859                    service_did: None,
860                },
861            )
862            .await?;
863            let mut result =
864                super::GroupReadResult::from_raw_response(secure.delivery, secure.warnings);
865            result.resolved_member = Some(resolved_member);
866            self.refresh_group_state_for_async(&mut result, &group, true)
867                .await;
868            Ok(result)
869        }
870    }
871
872    pub fn update_profile(
873        &self,
874        request: super::GroupUpdateProfileRequest,
875    ) -> crate::ImResult<super::GroupReadResult> {
876        let group = request.group.as_str().to_string();
877        if let Ok(Some(snapshot)) = crate::internal::group_runtime::cache::cached_group_snapshot(
878            self.client,
879            request.group.as_str(),
880        ) {
881            if crate::internal::group_runtime::cache::group_snapshot_uses_e2ee(&snapshot) {
882                return Err(crate::ImError::unsupported("group-e2ee"));
883            }
884        }
885        let mut result = crate::internal::group_runtime::lifecycle::GroupLifecycleRuntime::new(
886            self.client,
887            crate::internal::auth::session::FileSessionProvider::new(self.client),
888            crate::internal::transport::CoreHttpTransport::new(self.client),
889        )
890        .update_profile(request, None)?;
891        crate::internal::group_runtime::projection::project_group_snapshot(self.client, &result);
892        self.refresh_group_state_for(&mut result, &group, false);
893        Ok(result)
894    }
895
896    pub async fn update_profile_async(
897        &self,
898        request: super::GroupUpdateProfileRequest,
899    ) -> crate::ImResult<super::GroupReadResult> {
900        let group = request.group.as_str().to_string();
901        if let Ok(Some(snapshot)) =
902            crate::internal::group_runtime::cache::cached_group_snapshot_async(
903                self.client,
904                request.group.as_str(),
905            )
906            .await
907        {
908            if crate::internal::group_runtime::cache::group_snapshot_uses_e2ee(&snapshot) {
909                return Err(crate::ImError::unsupported("group-e2ee"));
910            }
911        }
912        let mut result = crate::internal::group_runtime::lifecycle::GroupLifecycleRuntime::new(
913            self.client,
914            crate::internal::auth::session::FileSessionProvider::new(self.client),
915            crate::internal::transport::CoreHttpTransport::new(self.client),
916        )
917        .update_profile_async(request, None)
918        .await?;
919        let _ = crate::internal::group_runtime::projection::project_group_snapshot_async(
920            self.client,
921            &result,
922        )
923        .await;
924        self.refresh_group_state_for_async(&mut result, &group, false)
925            .await;
926        Ok(result)
927    }
928
929    pub fn update_policy(
930        &self,
931        request: super::GroupUpdatePolicyRequest,
932    ) -> crate::ImResult<super::GroupReadResult> {
933        let group = request.group.as_str().to_string();
934        if let Ok(Some(snapshot)) = crate::internal::group_runtime::cache::cached_group_snapshot(
935            self.client,
936            request.group.as_str(),
937        ) {
938            if crate::internal::group_runtime::cache::group_snapshot_uses_e2ee(&snapshot) {
939                return Err(crate::ImError::unsupported("group-e2ee"));
940            }
941        }
942        let mut result = crate::internal::group_runtime::lifecycle::GroupLifecycleRuntime::new(
943            self.client,
944            crate::internal::auth::session::FileSessionProvider::new(self.client),
945            crate::internal::transport::CoreHttpTransport::new(self.client),
946        )
947        .update_policy(request, None)?;
948        crate::internal::group_runtime::projection::project_group_snapshot(self.client, &result);
949        self.refresh_group_state_for(&mut result, &group, false);
950        Ok(result)
951    }
952
953    pub async fn update_policy_async(
954        &self,
955        request: super::GroupUpdatePolicyRequest,
956    ) -> crate::ImResult<super::GroupReadResult> {
957        let group = request.group.as_str().to_string();
958        if let Ok(Some(snapshot)) =
959            crate::internal::group_runtime::cache::cached_group_snapshot_async(
960                self.client,
961                request.group.as_str(),
962            )
963            .await
964        {
965            if crate::internal::group_runtime::cache::group_snapshot_uses_e2ee(&snapshot) {
966                return Err(crate::ImError::unsupported("group-e2ee"));
967            }
968        }
969        let mut result = crate::internal::group_runtime::lifecycle::GroupLifecycleRuntime::new(
970            self.client,
971            crate::internal::auth::session::FileSessionProvider::new(self.client),
972            crate::internal::transport::CoreHttpTransport::new(self.client),
973        )
974        .update_policy_async(request, None)
975        .await?;
976        let _ = crate::internal::group_runtime::projection::project_group_snapshot_async(
977            self.client,
978            &result,
979        )
980        .await;
981        self.refresh_group_state_for_async(&mut result, &group, false)
982            .await;
983        Ok(result)
984    }
985
986    pub fn update(
987        &self,
988        request: super::GroupUpdateRequest,
989    ) -> crate::ImResult<super::GroupUpdateResult> {
990        if request.profile_patch == super::GroupProfilePatch::default()
991            && request.policy_patch == super::GroupPolicyPatch::default()
992        {
993            return Err(crate::ImError::invalid_input(
994                Some("group_update".to_string()),
995                "group update requires at least one mutable field",
996            ));
997        }
998        if let Ok(Some(snapshot)) = crate::internal::group_runtime::cache::cached_group_snapshot(
999            self.client,
1000            request.group.as_str(),
1001        ) {
1002            if crate::internal::group_runtime::cache::group_snapshot_uses_e2ee(&snapshot) {
1003                return Err(crate::ImError::unsupported("group-e2ee"));
1004            }
1005        }
1006        let group = request.group;
1007        let mut deliveries = Vec::new();
1008        let mut warnings = Vec::new();
1009        if request.profile_patch != super::GroupProfilePatch::default() {
1010            let result = crate::internal::group_runtime::lifecycle::GroupLifecycleRuntime::new(
1011                self.client,
1012                crate::internal::auth::session::FileSessionProvider::new(self.client),
1013                crate::internal::transport::CoreHttpTransport::new(self.client),
1014            )
1015            .update_profile(
1016                super::GroupUpdateProfileRequest {
1017                    group: group.clone(),
1018                    patch: request.profile_patch,
1019                },
1020                None,
1021            )?;
1022            crate::internal::group_runtime::projection::project_group_snapshot(
1023                self.client,
1024                &result,
1025            );
1026            warnings.extend(result.warnings.iter().cloned());
1027            deliveries.push(result);
1028        }
1029        if request.policy_patch != super::GroupPolicyPatch::default() {
1030            let result = crate::internal::group_runtime::lifecycle::GroupLifecycleRuntime::new(
1031                self.client,
1032                crate::internal::auth::session::FileSessionProvider::new(self.client),
1033                crate::internal::transport::CoreHttpTransport::new(self.client),
1034            )
1035            .update_policy(
1036                super::GroupUpdatePolicyRequest {
1037                    group: group.clone(),
1038                    patch: request.policy_patch,
1039                },
1040                None,
1041            )?;
1042            crate::internal::group_runtime::projection::project_group_snapshot(
1043                self.client,
1044                &result,
1045            );
1046            warnings.extend(result.warnings.iter().cloned());
1047            deliveries.push(result);
1048        }
1049        let mut refreshed = match self.get(group.clone()) {
1050            Ok(result) => Some(result),
1051            Err(err) => {
1052                warnings.push(format!("Failed to refresh group snapshot: {err}"));
1053                None
1054            }
1055        };
1056        if let Some(ref mut refreshed) = refreshed {
1057            warnings.extend(refreshed.warnings.iter().cloned());
1058        }
1059        Ok(super::GroupUpdateResult {
1060            deliveries,
1061            refreshed,
1062            warnings,
1063        })
1064    }
1065
1066    pub async fn update_async(
1067        &self,
1068        request: super::GroupUpdateRequest,
1069    ) -> crate::ImResult<super::GroupUpdateResult> {
1070        if request.profile_patch == super::GroupProfilePatch::default()
1071            && request.policy_patch == super::GroupPolicyPatch::default()
1072        {
1073            return Err(crate::ImError::invalid_input(
1074                Some("group_update".to_string()),
1075                "group update requires at least one mutable field",
1076            ));
1077        }
1078        if let Ok(Some(snapshot)) =
1079            crate::internal::group_runtime::cache::cached_group_snapshot_async(
1080                self.client,
1081                request.group.as_str(),
1082            )
1083            .await
1084        {
1085            if crate::internal::group_runtime::cache::group_snapshot_uses_e2ee(&snapshot) {
1086                return Err(crate::ImError::unsupported("group-e2ee"));
1087            }
1088        }
1089        let group = request.group;
1090        let mut deliveries = Vec::new();
1091        let mut warnings = Vec::new();
1092        if request.profile_patch != super::GroupProfilePatch::default() {
1093            let result = crate::internal::group_runtime::lifecycle::GroupLifecycleRuntime::new(
1094                self.client,
1095                crate::internal::auth::session::FileSessionProvider::new(self.client),
1096                crate::internal::transport::CoreHttpTransport::new(self.client),
1097            )
1098            .update_profile_async(
1099                super::GroupUpdateProfileRequest {
1100                    group: group.clone(),
1101                    patch: request.profile_patch,
1102                },
1103                None,
1104            )
1105            .await?;
1106            let _ = crate::internal::group_runtime::projection::project_group_snapshot_async(
1107                self.client,
1108                &result,
1109            )
1110            .await;
1111            warnings.extend(result.warnings.iter().cloned());
1112            deliveries.push(result);
1113        }
1114        if request.policy_patch != super::GroupPolicyPatch::default() {
1115            let result = crate::internal::group_runtime::lifecycle::GroupLifecycleRuntime::new(
1116                self.client,
1117                crate::internal::auth::session::FileSessionProvider::new(self.client),
1118                crate::internal::transport::CoreHttpTransport::new(self.client),
1119            )
1120            .update_policy_async(
1121                super::GroupUpdatePolicyRequest {
1122                    group: group.clone(),
1123                    patch: request.policy_patch,
1124                },
1125                None,
1126            )
1127            .await?;
1128            let _ = crate::internal::group_runtime::projection::project_group_snapshot_async(
1129                self.client,
1130                &result,
1131            )
1132            .await;
1133            warnings.extend(result.warnings.iter().cloned());
1134            deliveries.push(result);
1135        }
1136        let mut refreshed = match self.get_async(group.clone()).await {
1137            Ok(result) => Some(result),
1138            Err(err) => {
1139                warnings.push(format!("Failed to refresh group snapshot: {err}"));
1140                None
1141            }
1142        };
1143        if let Some(ref mut refreshed) = refreshed {
1144            warnings.extend(refreshed.warnings.iter().cloned());
1145        }
1146        Ok(super::GroupUpdateResult {
1147            deliveries,
1148            refreshed,
1149            warnings,
1150        })
1151    }
1152
1153    pub fn get(&self, group: crate::ids::GroupRef) -> crate::ImResult<super::GroupReadResult> {
1154        let result = crate::internal::group_runtime::read::GroupReadRuntime::new(
1155            self.client,
1156            crate::internal::auth::session::FileSessionProvider::new(self.client),
1157            crate::internal::transport::CoreHttpTransport::new(self.client),
1158        )
1159        .get(group)?;
1160        crate::internal::group_runtime::projection::project_group_snapshot(self.client, &result);
1161        Ok(result)
1162    }
1163
1164    pub async fn get_async(
1165        &self,
1166        group: crate::ids::GroupRef,
1167    ) -> crate::ImResult<super::GroupReadResult> {
1168        let result = crate::internal::group_runtime::read::GroupReadRuntime::new(
1169            self.client,
1170            crate::internal::auth::session::FileSessionProvider::new(self.client),
1171            crate::internal::transport::CoreHttpTransport::new(self.client),
1172        )
1173        .get_async(group)
1174        .await?;
1175        let _ = crate::internal::group_runtime::projection::project_group_snapshot_async(
1176            self.client,
1177            &result,
1178        )
1179        .await;
1180        Ok(result)
1181    }
1182
1183    pub fn list(
1184        &self,
1185        request: super::GroupListRequest,
1186    ) -> crate::ImResult<super::GroupReadResult> {
1187        let result = crate::internal::group_runtime::read::GroupReadRuntime::new(
1188            self.client,
1189            crate::internal::auth::session::FileSessionProvider::new(self.client),
1190            crate::internal::transport::CoreHttpTransport::new(self.client),
1191        )
1192        .list(request)?;
1193        crate::internal::group_runtime::projection::project_group_summaries(self.client, &result);
1194        Ok(result)
1195    }
1196
1197    pub async fn list_async(
1198        &self,
1199        request: super::GroupListRequest,
1200    ) -> crate::ImResult<super::GroupReadResult> {
1201        let result = crate::internal::group_runtime::read::GroupReadRuntime::new(
1202            self.client,
1203            crate::internal::auth::session::FileSessionProvider::new(self.client),
1204            crate::internal::transport::CoreHttpTransport::new(self.client),
1205        )
1206        .list_async(request)
1207        .await?;
1208        let _ = crate::internal::group_runtime::projection::project_group_summaries_async(
1209            self.client,
1210            &result,
1211        )
1212        .await;
1213        Ok(result)
1214    }
1215
1216    pub fn members(
1217        &self,
1218        request: super::GroupMembersRequest,
1219    ) -> crate::ImResult<super::GroupReadResult> {
1220        let group = request.group.as_str().to_string();
1221        let result = crate::internal::group_runtime::read::GroupReadRuntime::new(
1222            self.client,
1223            crate::internal::auth::session::FileSessionProvider::new(self.client),
1224            crate::internal::transport::CoreHttpTransport::new(self.client),
1225        )
1226        .members(request)?;
1227        crate::internal::group_runtime::projection::project_group_members(
1228            self.client,
1229            &group,
1230            &result,
1231        );
1232        Ok(result)
1233    }
1234
1235    pub async fn members_async(
1236        &self,
1237        request: super::GroupMembersRequest,
1238    ) -> crate::ImResult<super::GroupReadResult> {
1239        let group = request.group.as_str().to_string();
1240        let result = crate::internal::group_runtime::read::GroupReadRuntime::new(
1241            self.client,
1242            crate::internal::auth::session::FileSessionProvider::new(self.client),
1243            crate::internal::transport::CoreHttpTransport::new(self.client),
1244        )
1245        .members_async(request)
1246        .await?;
1247        let _ = crate::internal::group_runtime::projection::project_group_members_async(
1248            self.client,
1249            &group,
1250            &result,
1251        )
1252        .await;
1253        Ok(result)
1254    }
1255
1256    pub fn messages(
1257        &self,
1258        request: super::GroupMessagesRequest,
1259    ) -> crate::ImResult<super::GroupReadResult> {
1260        let limit = request.limit;
1261        let group = request.group.as_str().to_string();
1262        let mut result = crate::internal::group_runtime::read::GroupReadRuntime::new(
1263            self.client,
1264            crate::internal::auth::session::FileSessionProvider::new(self.client),
1265            crate::internal::transport::CoreHttpTransport::new(self.client),
1266        )
1267        .messages(request)?;
1268        crate::internal::group_runtime::projection::project_group_messages(
1269            self.client,
1270            &group,
1271            &result,
1272        );
1273        let _ = crate::internal::message_runtime::local_projection::persist_messages(
1274            self.client,
1275            &result.messages.items,
1276        );
1277        result.replace_messages(
1278            crate::internal::message_runtime::read::merge_group_local_projection_best_effort(
1279                self.client,
1280                result.messages.clone(),
1281                &crate::ids::GroupRef::parse(&group)?,
1282                limit,
1283            ),
1284        );
1285        Ok(result)
1286    }
1287
1288    pub async fn messages_async(
1289        &self,
1290        request: super::GroupMessagesRequest,
1291    ) -> crate::ImResult<super::GroupReadResult> {
1292        let group = request.group.as_str().to_string();
1293        let limit = request.limit;
1294        let mut result = crate::internal::group_runtime::read::GroupReadRuntime::new(
1295            self.client,
1296            crate::internal::auth::session::FileSessionProvider::new(self.client),
1297            crate::internal::transport::CoreHttpTransport::new(self.client),
1298        )
1299        .messages_async(request)
1300        .await?;
1301        let _ = crate::internal::group_runtime::projection::project_group_messages_async(
1302            self.client,
1303            &group,
1304            &result,
1305        )
1306        .await;
1307        let _ = crate::internal::message_runtime::local_projection::persist_messages_async(
1308            self.client,
1309            &result.messages.items,
1310        )
1311        .await;
1312        let merged =
1313            crate::internal::message_runtime::read::merge_group_local_projection_best_effort_async(
1314                self.client,
1315                result.messages.clone(),
1316                &crate::ids::GroupRef::parse(&group)?,
1317                limit,
1318            )
1319            .await;
1320        result.replace_messages(merged);
1321        Ok(result)
1322    }
1323
1324    fn refresh_group_state(&self, result: &mut super::GroupReadResult, include_members: bool) {
1325        let Some(group) = group_did(result) else {
1326            return;
1327        };
1328        self.refresh_group_state_for(result, &group, include_members);
1329    }
1330
1331    fn refresh_group_state_for(
1332        &self,
1333        result: &mut super::GroupReadResult,
1334        group: &str,
1335        include_members: bool,
1336    ) {
1337        let group_ref = match crate::ids::GroupRef::parse(group) {
1338            Ok(group_ref) => group_ref,
1339            Err(err) => {
1340                result.push_warning(format!("Failed to refresh group snapshot: {err}"));
1341                return;
1342            }
1343        };
1344        match self.get(group_ref.clone()) {
1345            Ok(snapshot) => {
1346                crate::internal::group_runtime::projection::project_group_snapshot(
1347                    self.client,
1348                    &snapshot,
1349                );
1350                result.merge_group_snapshot_from(&snapshot);
1351            }
1352            Err(err) => {
1353                result.push_warning(format!("Failed to refresh group snapshot: {err}"));
1354                return;
1355            }
1356        }
1357        if !include_members {
1358            return;
1359        }
1360        match self.members(super::GroupMembersRequest {
1361            group: group_ref,
1362            limit: crate::ids::PageLimit(100),
1363        }) {
1364            Ok(members) => result.merge_group_members_from(&members),
1365            Err(err) => result.push_warning(format!("Failed to refresh group members: {err}")),
1366        }
1367    }
1368
1369    async fn refresh_group_state_async(
1370        &self,
1371        result: &mut super::GroupReadResult,
1372        include_members: bool,
1373    ) {
1374        let Some(group) = group_did(result) else {
1375            return;
1376        };
1377        self.refresh_group_state_for_async(result, &group, include_members)
1378            .await;
1379    }
1380
1381    async fn refresh_group_state_for_async(
1382        &self,
1383        result: &mut super::GroupReadResult,
1384        group: &str,
1385        include_members: bool,
1386    ) {
1387        let group_ref = match crate::ids::GroupRef::parse(group) {
1388            Ok(group_ref) => group_ref,
1389            Err(err) => {
1390                result.push_warning(format!("Failed to refresh group snapshot: {err}"));
1391                return;
1392            }
1393        };
1394        match self.get_async(group_ref.clone()).await {
1395            Ok(snapshot) => {
1396                let _ = crate::internal::group_runtime::projection::project_group_snapshot_async(
1397                    self.client,
1398                    &snapshot,
1399                )
1400                .await;
1401                result.merge_group_snapshot_from(&snapshot);
1402            }
1403            Err(err) => {
1404                result.push_warning(format!("Failed to refresh group snapshot: {err}"));
1405                return;
1406            }
1407        }
1408        if !include_members {
1409            return;
1410        }
1411        match self
1412            .members_async(super::GroupMembersRequest {
1413                group: group_ref,
1414                limit: crate::ids::PageLimit(100),
1415            })
1416            .await
1417        {
1418            Ok(members) => result.merge_group_members_from(&members),
1419            Err(err) => result.push_warning(format!("Failed to refresh group members: {err}")),
1420        }
1421    }
1422
1423    #[cfg(feature = "group-e2ee")]
1424    fn publish_key_package_with_group_e2ee(
1425        &self,
1426        request: super::GroupKeyPackagePublishRequest,
1427    ) -> crate::ImResult<super::GroupKeyPackagePublishResult> {
1428        let session_provider =
1429            crate::internal::auth::session::FileSessionProvider::new(self.client);
1430        <crate::internal::auth::session::FileSessionProvider<'_> as SessionProvider>::ensure_session(
1431            &session_provider,
1432            crate::auth::AuthScope::GroupMessaging,
1433        )?;
1434        let credentials = crate::internal::message_runtime::group::load_credentials(self.client)?;
1435        let service_did = group_e2ee_service_did(self.client)?;
1436        let provider =
1437            crate::internal::group_e2ee::storage::native_provider_for_client(self.client)?;
1438        let prepared = prepare_group_key_package_publish(
1439            self.client,
1440            &request,
1441            &credentials,
1442            &service_did,
1443            &provider,
1444        )?;
1445        let mut transport = crate::internal::transport::CoreHttpTransport::new(self.client);
1446        let raw_response =
1447            <crate::internal::transport::CoreHttpTransport<'_> as AuthenticatedRpcTransport>::authenticated_rpc(
1448                &mut transport,
1449                crate::internal::message_runtime::group::MESSAGE_RPC_ENDPOINT,
1450                "group.e2ee.publish_key_package",
1451                prepared.params.clone(),
1452            )?;
1453        Ok(group_key_package_publish_result(
1454            self.client,
1455            request,
1456            prepared,
1457            raw_response,
1458        ))
1459    }
1460
1461    #[cfg(feature = "group-e2ee")]
1462    async fn publish_key_package_with_group_e2ee_async(
1463        &self,
1464        request: super::GroupKeyPackagePublishRequest,
1465    ) -> crate::ImResult<super::GroupKeyPackagePublishResult> {
1466        let session_provider =
1467            crate::internal::auth::session::FileSessionProvider::new(self.client);
1468        <crate::internal::auth::session::FileSessionProvider<'_> as AsyncSessionProvider>::ensure_session(
1469            &session_provider,
1470            crate::auth::AuthScope::GroupMessaging,
1471        )
1472            .await?;
1473        let credentials =
1474            crate::internal::message_runtime::group::load_credentials_async(self.client).await?;
1475        let service_did = group_e2ee_service_did(self.client)?;
1476        let provider =
1477            crate::internal::group_e2ee::storage::native_provider_for_client(self.client)?;
1478        let transport = crate::internal::transport::CoreHttpTransport::new(self.client);
1479        publish_group_key_package_with_components(
1480            self.client,
1481            request,
1482            credentials,
1483            service_did,
1484            provider,
1485            transport,
1486        )
1487        .await
1488    }
1489}
1490
1491#[cfg(feature = "group-e2ee")]
1492struct PreparedGroupKeyPackagePublish {
1493    device_id: String,
1494    key_package_id: String,
1495    params: serde_json::Value,
1496}
1497
1498#[cfg(feature = "group-e2ee")]
1499fn prepare_group_key_package_publish<P>(
1500    client: &crate::core::ImClient,
1501    request: &super::GroupKeyPackagePublishRequest,
1502    credentials: &crate::internal::message_runtime::group::GroupTextCredentials,
1503    service_did: &crate::ids::Did,
1504    provider: &P,
1505) -> crate::ImResult<PreparedGroupKeyPackagePublish>
1506where
1507    P: GroupMlsProvider + ?Sized,
1508{
1509    let operation_id = format!(
1510        "op-{}",
1511        crate::internal::wire::common::generate_operation_id()
1512    );
1513    let device_id = request
1514        .device_id
1515        .as_deref()
1516        .map(str::trim)
1517        .filter(|value| !value.is_empty())
1518        .unwrap_or(crate::internal::group_e2ee::DEFAULT_GROUP_MLS_DEVICE_ID)
1519        .to_owned();
1520    let mut group_key_package = provider
1521        .generate_key_package(anp::group_e2ee::operations::GenerateKeyPackageInput {
1522            owner_did: client.did().as_str().to_owned(),
1523            device_id: device_id.clone(),
1524            operation_id: operation_id.clone(),
1525            request_id: format!("group-e2ee-key-package-{operation_id}"),
1526            key_package_id: request
1527                .key_package_id
1528                .as_deref()
1529                .map(str::trim)
1530                .filter(|value| !value.is_empty())
1531                .map(str::to_owned),
1532            purpose: Some(request.purpose.as_str().to_owned()),
1533            group_did: request
1534                .group
1535                .as_ref()
1536                .map(|group| group.as_str().to_owned()),
1537        })?
1538        .group_key_package;
1539    group_key_package.did_wba_binding = sign_group_key_package_binding(
1540        credentials,
1541        client.did().as_str(),
1542        group_key_package.did_wba_binding,
1543    )?;
1544    let key_package_id = group_key_package.key_package_id.clone();
1545    let params =
1546        crate::internal::group_e2ee::wire::build_group_e2ee_publish_key_package_rpc_params(
1547            credentials,
1548            client.did().as_str(),
1549            service_did.as_str(),
1550            &group_key_package,
1551            &operation_id,
1552        )?;
1553    Ok(PreparedGroupKeyPackagePublish {
1554        device_id,
1555        key_package_id,
1556        params,
1557    })
1558}
1559
1560#[cfg(feature = "group-e2ee")]
1561fn group_key_package_publish_result(
1562    client: &crate::core::ImClient,
1563    request: super::GroupKeyPackagePublishRequest,
1564    prepared: PreparedGroupKeyPackagePublish,
1565    raw_response: serde_json::Value,
1566) -> super::GroupKeyPackagePublishResult {
1567    super::GroupKeyPackagePublishResult {
1568        owner_did: client.did().clone(),
1569        device_id: prepared.device_id,
1570        key_package_id: prepared.key_package_id,
1571        purpose: request.purpose,
1572        group: request.group,
1573        raw_response,
1574        warnings: Vec::new(),
1575    }
1576}
1577
1578#[cfg(feature = "group-e2ee")]
1579async fn publish_group_key_package_with_components<P, T>(
1580    client: &crate::core::ImClient,
1581    request: super::GroupKeyPackagePublishRequest,
1582    credentials: crate::internal::message_runtime::group::GroupTextCredentials,
1583    service_did: crate::ids::Did,
1584    provider: P,
1585    mut transport: T,
1586) -> crate::ImResult<super::GroupKeyPackagePublishResult>
1587where
1588    P: GroupMlsProvider + Send + 'static,
1589    T: AsyncAuthenticatedRpcTransport,
1590{
1591    let client_did = client.did().as_str().to_owned();
1592    let prepared = prepare_group_key_package_publish_async(
1593        client_did.as_str(),
1594        &request,
1595        &credentials,
1596        &service_did,
1597        provider,
1598    )
1599    .await?;
1600    let raw_response = transport
1601        .authenticated_rpc(
1602            crate::internal::message_runtime::group::MESSAGE_RPC_ENDPOINT,
1603            "group.e2ee.publish_key_package",
1604            prepared.params.clone(),
1605        )
1606        .await?;
1607    Ok(group_key_package_publish_result(
1608        client,
1609        request,
1610        prepared,
1611        raw_response,
1612    ))
1613}
1614
1615#[cfg(feature = "group-e2ee")]
1616async fn prepare_group_key_package_publish_async<P>(
1617    owner_did: &str,
1618    request: &super::GroupKeyPackagePublishRequest,
1619    credentials: &crate::internal::message_runtime::group::GroupTextCredentials,
1620    service_did: &crate::ids::Did,
1621    provider: P,
1622) -> crate::ImResult<PreparedGroupKeyPackagePublish>
1623where
1624    P: GroupMlsProvider + Send + 'static,
1625{
1626    let operation_id = format!(
1627        "op-{}",
1628        crate::internal::wire::common::generate_operation_id()
1629    );
1630    let device_id = request
1631        .device_id
1632        .as_deref()
1633        .map(str::trim)
1634        .filter(|value| !value.is_empty())
1635        .unwrap_or(crate::internal::group_e2ee::DEFAULT_GROUP_MLS_DEVICE_ID)
1636        .to_owned();
1637    let input = anp::group_e2ee::operations::GenerateKeyPackageInput {
1638        owner_did: owner_did.to_owned(),
1639        device_id: device_id.clone(),
1640        operation_id: operation_id.clone(),
1641        request_id: format!("group-e2ee-key-package-{operation_id}"),
1642        key_package_id: request
1643            .key_package_id
1644            .as_deref()
1645            .map(str::trim)
1646            .filter(|value| !value.is_empty())
1647            .map(str::to_owned),
1648        purpose: Some(request.purpose.as_str().to_owned()),
1649        group_did: request
1650            .group
1651            .as_ref()
1652            .map(|group| group.as_str().to_owned()),
1653    };
1654    let mut group_key_package = crate::internal::runtime::worker::run_blocking(move || {
1655        provider
1656            .generate_key_package(input)
1657            .map(|output| output.group_key_package)
1658    })
1659    .await
1660    .map_err(|err| crate::ImError::Internal {
1661        message: format!("group E2EE key package generation worker failed: {err}"),
1662    })??;
1663    group_key_package.did_wba_binding =
1664        sign_group_key_package_binding(credentials, owner_did, group_key_package.did_wba_binding)?;
1665    let key_package_id = group_key_package.key_package_id.clone();
1666    let params =
1667        crate::internal::group_e2ee::wire::build_group_e2ee_publish_key_package_rpc_params(
1668            credentials,
1669            owner_did,
1670            service_did.as_str(),
1671            &group_key_package,
1672            &operation_id,
1673        )?;
1674    Ok(PreparedGroupKeyPackagePublish {
1675        device_id,
1676        key_package_id,
1677        params,
1678    })
1679}
1680
1681fn group_did(result: &super::GroupReadResult) -> Option<String> {
1682    result
1683        .group
1684        .as_ref()
1685        .map(|group| group.did.as_str().to_string())
1686        .or_else(|| {
1687            result
1688                .response_json()
1689                .and_then(|raw| raw.get("group_did").or_else(|| raw.get("did")))
1690                .and_then(serde_json::Value::as_str)
1691                .map(str::trim)
1692                .filter(|value| !value.is_empty())
1693                .map(str::to_string)
1694        })
1695}
1696
1697#[cfg(feature = "group-e2ee")]
1698fn group_state_ref_from_result(
1699    group_did: &str,
1700    result: &super::GroupReadResult,
1701) -> Option<anp::group_e2ee::GroupStateRef> {
1702    result.raw_response().and_then(|raw| {
1703        crate::internal::group_e2ee::state_ref::group_state_ref_from_group_response(group_did, raw)
1704    })
1705}
1706
1707#[cfg(feature = "group-e2ee")]
1708fn group_state_ref_from_current_group(
1709    service: &GroupService<'_>,
1710    group_did: &str,
1711) -> Option<anp::group_e2ee::GroupStateRef> {
1712    let group = crate::ids::GroupRef::parse(group_did).ok()?;
1713    service
1714        .get(group)
1715        .ok()
1716        .and_then(|result| group_state_ref_from_result(group_did, &result))
1717}
1718
1719#[cfg(feature = "group-e2ee")]
1720async fn group_state_ref_from_current_group_async(
1721    service: &GroupService<'_>,
1722    group_did: &str,
1723) -> Option<anp::group_e2ee::GroupStateRef> {
1724    let group = crate::ids::GroupRef::parse(group_did).ok()?;
1725    service
1726        .get_async(group)
1727        .await
1728        .ok()
1729        .and_then(|result| group_state_ref_from_result(group_did, &result))
1730}
1731
1732fn resolve_group_member(
1733    client: &crate::core::ImClient,
1734    member: &super::GroupMemberRef,
1735) -> crate::ImResult<super::GroupMemberResolution> {
1736    if member.is_did() {
1737        return Ok(super::GroupMemberResolution {
1738            did: member.as_did()?,
1739            handle: None,
1740        });
1741    }
1742    let handle = crate::ids::Handle::parse(member.as_str(), "")?;
1743    let lookup = client.directory().lookup_handle(handle)?;
1744    Ok(super::GroupMemberResolution {
1745        did: lookup.did,
1746        handle: Some(lookup.handle),
1747    })
1748}
1749
1750async fn resolve_group_member_async(
1751    client: &crate::core::ImClient,
1752    member: &super::GroupMemberRef,
1753) -> crate::ImResult<super::GroupMemberResolution> {
1754    if member.is_did() {
1755        return Ok(super::GroupMemberResolution {
1756            did: member.as_did()?,
1757            handle: None,
1758        });
1759    }
1760    let handle = crate::ids::Handle::parse(member.as_str(), "")?;
1761    let lookup = client.directory().lookup_handle_async(handle).await?;
1762    Ok(super::GroupMemberResolution {
1763        did: lookup.did,
1764        handle: Some(lookup.handle),
1765    })
1766}
1767
1768fn resolved_group_member_request(
1769    request: super::GroupMemberMutationRequest,
1770    member: &super::GroupMemberResolution,
1771) -> super::GroupMemberMutationRequest {
1772    super::GroupMemberMutationRequest {
1773        group: request.group,
1774        member: super::GroupMemberRef::from(member.did.clone()),
1775        role: request.role,
1776        reason_text: request.reason_text,
1777        leave_request_id: request.leave_request_id,
1778        security: request.security,
1779    }
1780}
1781
1782fn project_group_system_event_best_effort(
1783    client: &crate::core::ImClient,
1784    group: &str,
1785    result: &mut super::GroupReadResult,
1786) {
1787    if let Err(error) =
1788        crate::internal::group_system_events::persist_group_read_result(client, group, result)
1789    {
1790        result.push_warning(format!("group_system_event_projection_failed:{error}"));
1791    }
1792}
1793
1794async fn project_group_system_event_best_effort_async(
1795    client: &crate::core::ImClient,
1796    group: &str,
1797    result: &mut super::GroupReadResult,
1798) {
1799    if let Err(error) =
1800        crate::internal::group_system_events::persist_group_read_result_async(client, group, result)
1801            .await
1802    {
1803        result.push_warning(format!("group_system_event_projection_failed:{error}"));
1804    }
1805}
1806
1807fn group_create_uses_e2ee(request: &super::GroupCreateRequest) -> bool {
1808    request.security.required()
1809        || request.e2ee
1810        || matches!(
1811            request.message_security_profile,
1812            Some(super::GroupMessageSecurityProfile::GroupE2ee)
1813        )
1814        || matches!(
1815            request.message_security_profile.as_ref(),
1816            Some(super::GroupMessageSecurityProfile::Custom(value))
1817                if value.trim() == "group-e2ee"
1818        )
1819}
1820
1821#[cfg(feature = "group-e2ee")]
1822fn sign_group_key_package_binding(
1823    credentials: &crate::internal::message_runtime::group::GroupTextCredentials,
1824    owner_did: &str,
1825    binding: serde_json::Value,
1826) -> crate::ImResult<serde_json::Value> {
1827    let leaf_signature_key_b64u = binding
1828        .get("leaf_signature_key_b64u")
1829        .and_then(serde_json::Value::as_str)
1830        .filter(|value| !value.trim().is_empty())
1831        .ok_or_else(|| crate::ImError::Serialization {
1832            detail: "group KeyPackage binding is missing leaf_signature_key_b64u".to_owned(),
1833        })?;
1834    let issued_at = binding
1835        .get("issued_at")
1836        .and_then(serde_json::Value::as_str)
1837        .filter(|value| !value.trim().is_empty())
1838        .ok_or_else(|| crate::ImError::Serialization {
1839            detail: "group KeyPackage binding is missing issued_at".to_owned(),
1840        })?;
1841    let expires_at = binding
1842        .get("expires_at")
1843        .and_then(serde_json::Value::as_str)
1844        .filter(|value| !value.trim().is_empty())
1845        .ok_or_else(|| crate::ImError::Serialization {
1846            detail: "group KeyPackage binding is missing expires_at".to_owned(),
1847        })?;
1848    let did_document =
1849        credentials
1850            .did_document
1851            .as_ref()
1852            .ok_or_else(|| crate::ImError::Serialization {
1853                detail: "identity is missing a DID document for group KeyPackage binding"
1854                    .to_owned(),
1855            })?;
1856    let verification_method = assertion_verification_method_id_from_document(did_document)
1857        .ok_or_else(|| crate::ImError::Serialization {
1858            detail: "identity is missing an assertion verification method".to_owned(),
1859        })?;
1860    let private_key =
1861        crate::internal::proof::origin::load_private_key_material(&credentials.key1_private_pem)?;
1862    anp::proof::generate_did_wba_binding(
1863        owner_did,
1864        &verification_method,
1865        leaf_signature_key_b64u,
1866        &private_key,
1867        issued_at,
1868        expires_at,
1869        Some(issued_at.to_owned()),
1870    )
1871    .map_err(|err| crate::ImError::Serialization {
1872        detail: format!("generate group KeyPackage DID WBA binding proof: {err}"),
1873    })
1874}
1875
1876#[cfg(feature = "group-e2ee")]
1877fn assertion_verification_method_id_from_document(
1878    did_document: &serde_json::Value,
1879) -> Option<String> {
1880    did_document
1881        .get("assertionMethod")
1882        .and_then(serde_json::Value::as_array)
1883        .and_then(|methods| methods.first())
1884        .and_then(|method| {
1885            method
1886                .as_str()
1887                .or_else(|| method.get("id").and_then(serde_json::Value::as_str))
1888        })
1889        .map(str::to_owned)
1890        .or_else(|| {
1891            crate::internal::proof::origin::verification_method_id_from_document(did_document)
1892        })
1893}
1894
1895#[cfg(all(test, feature = "group-e2ee"))]
1896fn verify_group_key_package_binding_contract(
1897    binding: &serde_json::Value,
1898    did_document: &serde_json::Value,
1899) -> crate::ImResult<()> {
1900    anp::proof::verify_did_wba_binding(
1901        binding,
1902        did_document,
1903        anp::proof::DidWbaBindingVerificationOptions {
1904            expected_leaf_signature_key_b64u: binding
1905                .get("leaf_signature_key_b64u")
1906                .and_then(serde_json::Value::as_str)
1907                .map(str::to_owned),
1908            expected_credential_identity: binding
1909                .get("agent_did")
1910                .and_then(serde_json::Value::as_str)
1911                .map(str::to_owned),
1912            ..Default::default()
1913        },
1914    )
1915    .map_err(|err| crate::ImError::Serialization {
1916        detail: format!("verify group KeyPackage DID WBA binding proof: {err}"),
1917    })
1918}
1919
1920#[cfg(feature = "group-e2ee")]
1921fn ensure_group_e2ee_service_available(
1922    client: &crate::core::ImClient,
1923    check_key_package: bool,
1924) -> crate::ImResult<()> {
1925    let session_provider = crate::internal::auth::session::FileSessionProvider::new(client);
1926    let mut transport = crate::internal::transport::CoreHttpTransport::new(client);
1927    crate::internal::group_e2ee::lifecycle::ensure_group_e2ee_service_available(
1928        client,
1929        &session_provider,
1930        &mut transport,
1931        crate::internal::group_e2ee::lifecycle::GroupE2eeServiceAvailabilityInput {
1932            credentials: None,
1933            service_did: None,
1934            check_key_package,
1935        },
1936    )
1937}
1938
1939#[cfg(feature = "group-e2ee")]
1940async fn ensure_group_e2ee_service_available_async(
1941    client: &crate::core::ImClient,
1942    check_key_package: bool,
1943) -> crate::ImResult<()> {
1944    let session_provider = crate::internal::auth::session::FileSessionProvider::new(client);
1945    let mut transport = crate::internal::transport::CoreHttpTransport::new(client);
1946    crate::internal::group_e2ee::lifecycle::ensure_group_e2ee_service_available_async(
1947        client,
1948        &session_provider,
1949        &mut transport,
1950        crate::internal::group_e2ee::lifecycle::GroupE2eeServiceAvailabilityInput {
1951            credentials: None,
1952            service_did: None,
1953            check_key_package,
1954        },
1955    )
1956    .await
1957}
1958
1959#[cfg(feature = "group-e2ee")]
1960fn group_e2ee_service_did(client: &crate::core::ImClient) -> crate::ImResult<crate::ids::Did> {
1961    client
1962        .core_inner()
1963        .sdk_config()
1964        .anp_service_did
1965        .clone()
1966        .ok_or_else(|| {
1967            crate::ImError::invalid_input(
1968                Some("anp_service_did".to_owned()),
1969                "group E2EE key package publish requires ImCoreConfig.anp_service_did",
1970            )
1971        })
1972}
1973
1974#[cfg(all(test, feature = "group-e2ee"))]
1975mod tests;