rust_rcs_client/
lib.rs

1// Copyright 2023 宋昊文
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15pub mod chat_bot;
16pub mod conference;
17pub mod connection;
18pub mod connectivity;
19pub mod contact;
20pub mod context;
21pub mod ffi;
22pub mod messaging;
23pub mod provisioning;
24pub mod rcs_engine;
25
26use std::ffi::{CStr, CString};
27use std::ptr::{self, null_mut, NonNull};
28use std::sync::{Arc, Mutex};
29
30// use backtrace::Backtrace;
31
32use libc::c_char;
33use messaging::cpm::MessagingSessionHandle;
34use rust_rcs_core::security::gba::GbaContext;
35use rust_rcs_core::third_gen_pp::ThreeDigit;
36use tokio::runtime::Runtime;
37
38use rust_rcs_core::ffi::log::platform_log;
39
40use chat_bot::ffi::{
41    RetrieveChatbotInfoResultCallback, RetrieveChatbotInfoResultCallbackContext,
42    RetrieveChatbotInfoResultCallbackContextWrapper, RetrieveSpecificChatbotsResultCallback,
43    RetrieveSpecificChatbotsResultCallbackContext,
44    RetrieveSpecificChatbotsResultCallbackContextWrapper, SearchChatbotResultCallback,
45    SearchChatbotResultCallbackContext, SearchChatbotResultCallbackContextWrapper,
46};
47use conference::ffi::{
48    MultiConferenceCreateResultCallback, MultiConferenceCreateResultCallbackContext,
49    MultiConferenceCreateResultCallbackContextWrapper, MultiConferenceEventListener,
50    MultiConferenceEventListenerContext, MultiConferenceEventListenerContextWrapper,
51    MultiConferenceJoinResultCallback, MultiConferenceJoinResultCallbackContext,
52    MultiConferenceJoinResultCallbackContextWrapper, MultiConferenceV1InviteHandlerContext,
53    MultiConferenceV1InviteHandlerContextWrapper, MultiConferenceV1InviteHandlerFunction,
54};
55use conference::MultiConferenceV1;
56use context::Context;
57use ffi::{
58    MessageCallback, MessageCallbackContext, MessageCallbackContextWrapper, StateChangeCallback,
59    StateChangeCallbackContext, StateChangeCallbackContextWrapper,
60};
61use messaging::ffi::{
62    get_recipient_type, DownloadFileProgressCallback, DownloadFileProgressCallbackContext,
63    DownloadFileResultCallback, DownloadFileResultCallbackContext,
64    DownloadFileResultCallbackContextWrapper, DownloadileProgressCallbackContextWrapper,
65    MessageResultCallback, MessageResultCallbackContext, MessageResultCallbackContextWrapper,
66    SendImdnReportResultCallback, SendImdnReportResultCallbackContext,
67    SendImdnReportResultCallbackContextWrapper, UploadFileProgressCallback,
68    UploadFileProgressCallbackContext, UploadFileProgressCallbackContextWrapper,
69    UploadFileResultCallback, UploadFileResultCallbackContext,
70    UploadFileResultCallbackContextWrapper,
71};
72use provisioning::device_configuration::{
73    start_auto_config, DeviceConfigurationStatus, RetryReason,
74};
75
76use rcs_engine::RcsEngine;
77
78const LOG_TAG: &str = "librust_rcs_client";
79
80pub struct RcsRuntime {
81    pub rt: Arc<Runtime>,
82    // context: Arc<Context>,
83}
84
85impl RcsRuntime {
86    pub fn new() -> Result<RcsRuntime, ()> {
87        std::panic::set_hook(Box::new(|info| {
88            platform_log(LOG_TAG, format!("panic! {}", info));
89
90            // let bt = Backtrace::new();
91
92            // platform_log(LOG_TAG, format!("{:?}", bt));
93        }));
94
95        // let context = Context::new("fs_root_dir");
96        if let Ok(rt) = Runtime::new() {
97            // return Ok(RcsRuntime { rt, context: Arc::new(context) });
98            return Ok(RcsRuntime { rt: Arc::new(rt) });
99        }
100
101        Err(())
102    }
103}
104
105pub struct RcsClient {
106    subscription_id: i32,
107    mcc: u16,
108    mnc: u16,
109    imsi: String,
110    imei: String,
111    msisdn: Option<String>,
112
113    context: Arc<Context>,
114
115    // cb: Option<StateChangeCallback>,
116    // cb_context: Arc<Mutex<StateChangeCallbackContextWrapper>>,
117    engine: RcsEngine,
118    // engine_itf: RcsEngineInterface,
119    engine_gba_context: Option<Arc<GbaContext>>,
120}
121
122impl RcsClient {
123    pub fn new(
124        subscription_id: i32,
125        mcc: u16,
126        mnc: u16,
127        imsi: &str,
128        imei: &str,
129        msisdn: Option<&str>,
130        context: Arc<Context>,
131        state_cb: Option<StateChangeCallback>,
132        state_cb_context: *mut StateChangeCallbackContext,
133        message_cb: Option<MessageCallback>,
134        message_cb_context: *mut MessageCallbackContext,
135        multi_conference_v1_invite_handler: Option<MultiConferenceV1InviteHandlerFunction>,
136        multi_conference_v1_invite_handler_context: *mut MultiConferenceV1InviteHandlerContext,
137        rt: Arc<Runtime>,
138    ) -> RcsClient {
139        let state_cb_context = Arc::new(Mutex::new(StateChangeCallbackContextWrapper(
140            NonNull::new(state_cb_context).unwrap(),
141        )));
142        let message_cb_context = Arc::new(Mutex::new(MessageCallbackContextWrapper(
143            NonNull::new(message_cb_context).unwrap(),
144        )));
145        let multi_conference_v1_invite_handler_context =
146            Arc::new(Mutex::new(MultiConferenceV1InviteHandlerContextWrapper(
147                NonNull::new(multi_conference_v1_invite_handler_context).unwrap(),
148            )));
149        let context_ = Arc::clone(&context);
150        let engine = RcsEngine::new(
151            subscription_id,
152            rt,
153            context_,
154            move |state| {
155                if let Some(state_cb) = state_cb {
156                    let state_cb_context = state_cb_context.lock().unwrap();
157                    let state_cb_context_ptr = state_cb_context.0.as_ptr();
158                    match state {
159                        rcs_engine::RcsEngineRegistrationState::NONE => {
160                            state_cb(0, state_cb_context_ptr)
161                        }
162                        rcs_engine::RcsEngineRegistrationState::AUTHENTICATED(_) => {
163                            state_cb(1, state_cb_context_ptr)
164                        }
165                        rcs_engine::RcsEngineRegistrationState::MAINTAINED(_) => {
166                            state_cb(2, state_cb_context_ptr)
167                        }
168                    }
169                }
170            },
171            move |service_type,
172                  session_handle,
173                  contact_uri,
174                  content_type,
175                  content_body,
176                  imdn_message_id,
177                  cpim_date,
178                  cpim_from| {
179                if let Some(messag_cb) = message_cb {
180                    let session_handle = match session_handle {
181                        Some(session_handle) => Some(Box::new(session_handle)),
182                        None => None,
183                    };
184                    let contact_uri = CString::new(contact_uri).unwrap();
185                    let content_type = CString::new(content_type).unwrap();
186                    let content_body = CString::new(content_body).unwrap();
187                    let imdn_message_id = CString::new(imdn_message_id).unwrap();
188                    let cpim_date = CString::new(cpim_date).unwrap();
189                    if let Some(cpim_from) = cpim_from {
190                        let cpim_from = CString::new(cpim_from).unwrap();
191                        let message_cb_context = message_cb_context.lock().unwrap();
192                        let message_cb_context_ptr = message_cb_context.0.as_ptr();
193                        messag_cb(
194                            service_type,
195                            session_handle,
196                            contact_uri.as_ptr(),
197                            content_type.as_ptr(),
198                            content_body.as_ptr(),
199                            imdn_message_id.as_ptr(),
200                            cpim_date.as_ptr(),
201                            cpim_from.as_ptr(),
202                            message_cb_context_ptr,
203                        );
204                    } else {
205                        let message_cb_context = message_cb_context.lock().unwrap();
206                        let message_cb_context_ptr = message_cb_context.0.as_ptr();
207                        messag_cb(
208                            service_type,
209                            session_handle,
210                            contact_uri.as_ptr(),
211                            content_type.as_ptr(),
212                            content_body.as_ptr(),
213                            imdn_message_id.as_ptr(),
214                            cpim_date.as_ptr(),
215                            ptr::null(),
216                            message_cb_context_ptr,
217                        );
218                    };
219                }
220            },
221            move |conference_v1, offer_sdp, response_receiver| {
222                if let Some(multi_conference_v1_invite_handler) = multi_conference_v1_invite_handler
223                {
224                    let multi_conference_v1_invite_handler_context =
225                        multi_conference_v1_invite_handler_context.lock().unwrap();
226                    let multi_conference_v1_invite_handler_context_ptr =
227                        multi_conference_v1_invite_handler_context.0.as_ptr();
228                    let offer_sdp_ptr = offer_sdp.as_ptr();
229                    let offer_sdp_len = offer_sdp.len();
230                    multi_conference_v1_invite_handler(
231                        Some(Box::new(conference_v1)),
232                        offer_sdp_ptr,
233                        offer_sdp_len,
234                        Some(Box::new(response_receiver)),
235                        multi_conference_v1_invite_handler_context_ptr,
236                    );
237                }
238            },
239        );
240        RcsClient {
241            subscription_id,
242            mcc,
243            mnc,
244            imsi: imsi.to_string(),
245            imei: imei.to_string(),
246            msisdn: match msisdn {
247                Some(msisdn) => Some(String::from(msisdn)),
248                None => None,
249            },
250            context,
251            // cb,
252            // cb_context: Arc::new(Mutex::new(StateChangeCallbackContextWrapper(
253            //     NonNull::new(cb_context).unwrap(),
254            // ))),
255            engine,
256            // engine_itf,
257            engine_gba_context: None,
258        }
259    }
260}
261
262#[no_mangle]
263pub unsafe extern "C" fn new_rcs_runtime() -> Option<Box<RcsRuntime>> {
264    if let Ok(rcs_runtime) = RcsRuntime::new() {
265        return Some(Box::new(rcs_runtime));
266    }
267    None
268}
269
270#[no_mangle]
271pub unsafe extern "C" fn new_rcs_client(
272    rcs_runtime: *mut RcsRuntime,
273    subscription_id: i32,
274    mcc: u16,
275    mnc: u16,
276    imsi: *const c_char,
277    imei: *const c_char,
278    msisdn: *const c_char,
279    fs_root_dir: *const c_char,
280    state_cb: Option<StateChangeCallback>,
281    state_cb_context: *mut StateChangeCallbackContext,
282    message_cb: Option<MessageCallback>,
283    message_cb_context: *mut MessageCallbackContext,
284    conference_v1_invite_handler: Option<MultiConferenceV1InviteHandlerFunction>,
285    conference_v1_invite_handler_context: *mut MultiConferenceV1InviteHandlerContext,
286) -> Option<Box<RcsClient>> {
287    let imsi = CStr::from_ptr(imsi).to_string_lossy().into_owned();
288    let imei = CStr::from_ptr(imei).to_string_lossy().into_owned();
289    let msisdn = if msisdn.is_null() {
290        None
291    } else {
292        Some(CStr::from_ptr(msisdn).to_string_lossy())
293    };
294    let fs_root_dir = CStr::from_ptr(fs_root_dir).to_string_lossy().into_owned();
295    if let Some(rcs_runtime) = rcs_runtime.as_ref() {
296        platform_log(LOG_TAG, "creating context");
297        let rt = Arc::clone(&rcs_runtime.rt);
298        let context = Arc::new(Context::new(&fs_root_dir, rt));
299        return Some(Box::new(RcsClient::new(
300            subscription_id,
301            mcc,
302            mnc,
303            &imsi,
304            &imei,
305            msisdn.as_deref(),
306            context,
307            state_cb,
308            state_cb_context,
309            message_cb,
310            message_cb_context,
311            conference_v1_invite_handler,
312            conference_v1_invite_handler_context,
313            Arc::clone(&rcs_runtime.rt),
314        )));
315    }
316    None
317}
318
319type AutoConfigProgressCallback =
320    extern "C" fn(status_code: i32, context: *mut AutoConfigCallbackContext);
321
322type AutoConfigResultCallback = extern "C" fn(
323    status_code: i32,
324    ims_config: *const c_char,
325    rcs_config: *const c_char,
326    extra: *const c_char,
327    // extra_host: *const c_char,
328    // extra_cookie: *const c_char,
329    context: *mut AutoConfigCallbackContext,
330);
331
332#[repr(C)]
333pub struct AutoConfigCallbackContext {
334    _data: [u8; 0],
335    _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>,
336}
337
338#[cfg(any(
339    all(feature = "android", target_os = "android"),
340    all(feature = "ohos", all(target_os = "linux", target_env = "ohos"))
341))]
342extern "C" {
343    fn auto_config_callback_context_release(context: *mut AutoConfigCallbackContext);
344}
345
346struct AutoConfigCallbackContextWrapper(NonNull<AutoConfigCallbackContext>);
347
348impl Drop for AutoConfigCallbackContextWrapper {
349    fn drop(&mut self) {
350        #[cfg(any(
351            all(feature = "android", target_os = "android"),
352            all(feature = "ohos", all(target_os = "linux", target_env = "ohos"))
353        ))]
354        let cb_context = self.0.as_ptr();
355        #[cfg(any(
356            all(feature = "android", target_os = "android"),
357            all(feature = "ohos", all(target_os = "linux", target_env = "ohos"))
358        ))]
359        unsafe {
360            auto_config_callback_context_release(cb_context);
361        }
362    }
363}
364
365unsafe impl Send for AutoConfigCallbackContextWrapper {}
366
367#[no_mangle]
368pub unsafe extern "C" fn rcs_client_start_config(
369    rcs_runtime: *mut RcsRuntime,
370    client: *const RcsClient,
371    p_cb: Option<AutoConfigProgressCallback>,
372    r_cb: Option<AutoConfigResultCallback>,
373    cb_context: *mut AutoConfigCallbackContext,
374) {
375    platform_log(LOG_TAG, "calling rcs_client_start_config()");
376
377    let cb_context = AutoConfigCallbackContextWrapper(NonNull::new(cb_context).unwrap());
378
379    if let Some(rcs_runtime) = rcs_runtime.as_ref() {
380        if let Some(client) = client.as_ref() {
381            let subscription_id = client.subscription_id;
382
383            let mcc = client.mcc;
384            let mnc = client.mnc;
385            let imsi = String::from(&client.imsi);
386            let imei = String::from(&client.imei);
387
388            let context = Arc::clone(&client.context);
389
390            let p_cb_context = Arc::new(Mutex::new(cb_context));
391            let r_cb_context: Arc<Mutex<AutoConfigCallbackContextWrapper>> =
392                Arc::clone(&p_cb_context);
393
394            // let cb = client.cb;
395            // let cb_context = client.cb_context.clone();
396            rcs_runtime.rt.spawn(async move {
397                match start_auto_config(
398                    subscription_id,
399                    mcc,
400                    mnc,
401                    &imei,
402                    &imsi,
403                    None,
404                    context,
405                    move |status| {
406                        if let Some(cb) = p_cb {
407                            let cb_context = p_cb_context.lock().unwrap();
408                            let cb_context_ptr = cb_context.0.as_ptr();
409                            cb(status, cb_context_ptr);
410                        }
411                    },
412                )
413                .await
414                {
415                    Ok((
416                        msisdn,
417                        rcs_application_characteristic,
418                        ims_application_characteristic,
419                    )) => {
420                        platform_log(
421                            LOG_TAG,
422                            format!("on configuration success with provided msisdn {:?}", msisdn),
423                        );
424
425                        if let (
426                            Ok(ims_application_characteristic_string),
427                            Ok(rcs_application_characteristic_string),
428                        ) = (
429                            ims_application_characteristic.to_c_string(),
430                            rcs_application_characteristic.to_c_string(),
431                        ) {
432                            platform_log(
433                                LOG_TAG,
434                                format!("with ims-app {:?}", ims_application_characteristic_string),
435                            );
436
437                            platform_log(
438                                LOG_TAG,
439                                format!("with rcs-app {:?}", rcs_application_characteristic_string),
440                            );
441
442                            // to-do: provide msisdn to callback
443                            if let Some(cb) = r_cb {
444                                let ims_config = ims_application_characteristic_string.as_ptr();
445                                let rcs_config = rcs_application_characteristic_string.as_ptr();
446                                let extra = ptr::null();
447                                let cb_context: std::sync::MutexGuard<
448                                    '_,
449                                    AutoConfigCallbackContextWrapper,
450                                > = r_cb_context.lock().unwrap();
451                                let cb_context_ptr = cb_context.0.as_ptr();
452                                cb(0, ims_config, rcs_config, extra, cb_context_ptr);
453                            }
454                        } else {
455                            if let Some(cb) = r_cb {
456                                let ims_config = ptr::null();
457                                let rcs_config = ptr::null();
458                                let extra = ptr::null();
459                                let cb_context = r_cb_context.lock().unwrap();
460                                let cb_context_ptr = cb_context.0.as_ptr();
461                                cb(-1, ims_config, rcs_config, extra, cb_context_ptr);
462                            }
463                        }
464                    }
465
466                    Err(status) => {
467                        platform_log(LOG_TAG, format!("on configuration error {:?}", status));
468
469                        match status {
470                            DeviceConfigurationStatus::InvalidArgument => {
471                                if let Some(cb) = r_cb {
472                                    let ims_config = ptr::null();
473                                    let rcs_config = ptr::null();
474                                    let extra = ptr::null();
475                                    let cb_context = r_cb_context.lock().unwrap();
476                                    let cb_context_ptr = cb_context.0.as_ptr();
477                                    cb(-1, ims_config, rcs_config, extra, cb_context_ptr);
478                                }
479                            }
480
481                            DeviceConfigurationStatus::Forbidden => {
482                                if let Some(cb) = r_cb {
483                                    let ims_config = ptr::null();
484                                    let rcs_config = ptr::null();
485                                    let extra = ptr::null();
486                                    let cb_context = r_cb_context.lock().unwrap();
487                                    let cb_context_ptr = cb_context.0.as_ptr();
488                                    cb(-1, ims_config, rcs_config, extra, cb_context_ptr);
489                                }
490                            }
491
492                            DeviceConfigurationStatus::Retry(reason, timeout) => {
493                                if let Some(cb) = r_cb {
494                                    let ims_config = ptr::null();
495                                    let rcs_config = ptr::null();
496                                    let extra = ptr::null();
497                                    let cb_context = r_cb_context.lock().unwrap();
498                                    let cb_context_ptr = cb_context.0.as_ptr();
499                                    cb(-1, ims_config, rcs_config, extra, cb_context_ptr);
500                                }
501                            } // DeviceConfigurationStatus::Retry(reason, timeout) => match reason {
502                              //     RetryReason::RequireOtp(port, session_host, session_cookie) => {
503                              //         if let Some(cb) = cb {
504                              //             let ims_config = ptr::null();
505                              //             let rcs_config = ptr::null();
506                              //             let session_host = session_host.as_str();
507                              //             let session_cookie = session_cookie.as_str();
508                              //             if let (Ok(extra_host), Ok(extra_cookie)) = (CString::new(session_host), CString::new(session_cookie)) {
509                              //                 let extra_host = extra_host.as_ptr();
510                              //                 let extra_cookie = extra_cookie.as_ptr();
511                              //                 let cb_context = cb_context.lock().unwrap();
512                              //                 let cb_context_ptr: *mut AutoConfigCallbackContext =
513                              //                     cb_context.0.as_ptr();
514                              //                 if port == 0 {
515                              //                     cb(2, ims_config, rcs_config, extra_host, extra_cookie, cb_context_ptr);
516                              //                 } else {
517                              //                     cb(1, ims_config, rcs_config, extra_host, extra_cookie, cb_context_ptr);
518                              //                 }
519                              //             }
520                              //         }
521                              //     }
522                              //     _ => {
523                              //         if let Some(cb) = cb {
524                              //             let ims_config = ptr::null();
525                              //             let rcs_config = ptr::null();
526                              //             let extra_host = ptr::null();
527                              //             let extra_cookie = ptr::null();
528                              //             let cb_context = cb_context.lock().unwrap();
529                              //             let cb_context_ptr = cb_context.0.as_ptr();
530                              //             cb(-1, ims_config, rcs_config, extra_host, extra_cookie, cb_context_ptr);
531                              //         }
532                              //     }
533                              // },
534                        }
535                    }
536                }
537            });
538        }
539    }
540}
541
542#[no_mangle]
543pub unsafe extern "C" fn rcs_client_input_otp(
544    rcs_runtime: *mut RcsRuntime,
545    client: *const RcsClient,
546    otp: *const c_char,
547) {
548    platform_log(LOG_TAG, "calling rcs_client_input_otp()");
549
550    if let Some(client) = client.as_ref() {
551        let otp = CStr::from_ptr(otp).to_string_lossy().into_owned();
552
553        client.context.broadcast_otp(&otp);
554    }
555}
556
557#[no_mangle]
558pub unsafe extern "C" fn rcs_client_setup(
559    rcs_runtime: *mut RcsRuntime,
560    client: *mut RcsClient,
561    ims_config: *const c_char,
562    rcs_config: *const c_char,
563) {
564    platform_log(LOG_TAG, "calling rcs_client_setup()");
565
566    if let Some(client) = client.as_mut() {
567        let ims_config = CStr::from_ptr(ims_config).to_string_lossy().into_owned();
568        let rcs_config = CStr::from_ptr(rcs_config).to_string_lossy().into_owned();
569
570        let gba_context = client.context.make_gba_context(
571            &client.imsi,
572            client.mcc,
573            client.mnc,
574            client.subscription_id,
575        );
576        let gba_context = Arc::new(gba_context);
577
578        client.engine.configure(ims_config, rcs_config);
579        client.engine_gba_context.replace(gba_context);
580    }
581}
582
583#[no_mangle]
584pub unsafe extern "C" fn rcs_client_connect(rcs_runtime: *mut RcsRuntime, client: *mut RcsClient) {
585    platform_log(LOG_TAG, "calling rcs_client_connect()");
586
587    if let Some(rcs_runtime) = rcs_runtime.as_ref() {
588        let rt = Arc::clone(&rcs_runtime.rt);
589        if let Some(client) = client.as_mut() {
590            client.engine.connect(rt);
591        }
592    }
593}
594
595#[no_mangle]
596pub unsafe extern "C" fn rcs_client_disconnect(
597    rcs_runtime: *mut RcsRuntime,
598    client: *mut RcsClient,
599) {
600    platform_log(LOG_TAG, "calling rcs_client_disconnect()");
601
602    if let Some(rcs_runtime) = rcs_runtime.as_ref() {
603        let rt = Arc::clone(&rcs_runtime.rt);
604        if let Some(client) = client.as_mut() {
605            client.engine.disconnect(rt);
606        }
607    }
608}
609
610#[no_mangle]
611pub unsafe extern "C" fn rcs_client_send_message(
612    rcs_runtime: *mut RcsRuntime,
613    client: *mut RcsClient,
614    message_type: *const c_char,
615    message_content: *const c_char,
616    recipient: *const c_char,
617    recipient_type: i8,
618    cb: Option<MessageResultCallback>,
619    cb_context: *mut MessageResultCallbackContext,
620) {
621    platform_log(LOG_TAG, "calling rcs_client_send_message()");
622
623    let cb_context = if cb_context.is_null() {
624        None
625    } else {
626        Some(MessageResultCallbackContextWrapper(
627            NonNull::new(cb_context).unwrap(),
628        ))
629    };
630
631    if let Some(rcs_runtime) = rcs_runtime.as_ref() {
632        if let Some(client) = client.as_ref() {
633            let message_type = CStr::from_ptr(message_type).to_string_lossy();
634            let message_content = CStr::from_ptr(message_content).to_string_lossy();
635            let recipient = CStr::from_ptr(recipient).to_string_lossy();
636            let recipient_type = get_recipient_type(recipient_type);
637
638            if let Some(recipient_type) = recipient_type {
639                let cb_context_ = if let Some(cb_context) = cb_context {
640                    Some(Arc::new(Mutex::new(cb_context)))
641                } else {
642                    None
643                };
644
645                client.engine.send_message(
646                    &message_type,
647                    &message_content,
648                    &recipient,
649                    recipient_type,
650                    move |status_code, reason_phrase| {
651                        if let Some(cb) = cb {
652                            let reason_phrase = CString::new(reason_phrase).unwrap();
653                            if let Some(cb_context_) = cb_context_ {
654                                let cb_context = cb_context_.lock().unwrap();
655                                cb(status_code, reason_phrase.as_ptr(), cb_context.0.as_ptr());
656                            } else {
657                                cb(status_code, reason_phrase.as_ptr(), ptr::null_mut());
658                            }
659                        }
660                    },
661                    &rcs_runtime.rt,
662                );
663
664                return;
665            }
666        }
667    }
668
669    if let Some(cb) = cb {
670        let reason_phrase = CString::new("Forbidden").unwrap();
671        cb(
672            403,
673            reason_phrase.as_ptr(),
674            if let Some(cb_context) = cb_context {
675                cb_context.0.as_ptr()
676            } else {
677                ptr::null_mut()
678            },
679        );
680    }
681}
682
683#[no_mangle]
684pub unsafe extern "C" fn rcs_client_send_imdn_report(
685    rcs_runtime: *mut RcsRuntime,
686    client: *mut RcsClient,
687    imdn_content: *const c_char,
688    sender_uri: *const c_char,
689    sender_service_type: i32,
690    sender_session_handle: *mut MessagingSessionHandle,
691    cb: Option<SendImdnReportResultCallback>,
692    cb_context: *mut SendImdnReportResultCallbackContext,
693) {
694    platform_log(LOG_TAG, "calling rcs_client_send_imdn_report()");
695
696    let cb_context = if cb_context.is_null() {
697        None
698    } else {
699        Some(SendImdnReportResultCallbackContextWrapper(
700            NonNull::new(cb_context).unwrap(),
701        ))
702    };
703
704    if let Some(rcs_runtime) = rcs_runtime.as_ref() {
705        let rt = Arc::clone(&rcs_runtime.rt);
706        if let Some(client) = client.as_ref() {
707            let cb_context_ = if let Some(cb_context) = cb_context {
708                Some(Arc::new(Mutex::new(cb_context)))
709            } else {
710                None
711            };
712
713            let imdn_content = CStr::from_ptr(imdn_content).to_string_lossy();
714            let sender_uri = CStr::from_ptr(sender_uri).to_string_lossy();
715
716            client.engine.send_imdn_report(
717                &imdn_content,
718                &sender_uri,
719                sender_service_type,
720                sender_session_handle,
721                rt,
722                move |status_code, reason_phrase| {
723                    if let Some(cb) = cb {
724                        let reason_phrase = CString::new(reason_phrase).unwrap();
725                        if let Some(cb_context_) = cb_context_ {
726                            let cb_context = cb_context_.lock().unwrap();
727                            cb(status_code, reason_phrase.as_ptr(), cb_context.0.as_ptr());
728                        } else {
729                            cb(status_code, reason_phrase.as_ptr(), ptr::null_mut());
730                        }
731                    }
732                },
733            );
734
735            return;
736        }
737    }
738
739    if let Some(cb) = cb {
740        let reason_phrase = CString::new("Forbidden").unwrap();
741        cb(
742            403,
743            reason_phrase.as_ptr(),
744            if let Some(cb_context) = cb_context {
745                cb_context.0.as_ptr()
746            } else {
747                ptr::null_mut()
748            },
749        );
750    }
751}
752
753#[no_mangle]
754pub unsafe extern "C" fn rcs_client_upload_file(
755    rcs_runtime: *mut RcsRuntime,
756    client: *mut RcsClient,
757    tid: *const c_char,
758    file_path: *const c_char,
759    file_name: *const c_char,
760    file_mime: *const c_char,
761    file_hash: *const c_char,
762    thumbnail_path: *const c_char,
763    thumbnail_name: *const c_char,
764    thumbnail_mime: *const c_char,
765    thumbnail_hash: *const c_char,
766    progress_cb: Option<UploadFileProgressCallback>,
767    progress_cb_context: *mut UploadFileProgressCallbackContext,
768    result_cb: Option<UploadFileResultCallback>,
769    result_cb_context: *mut UploadFileResultCallbackContext,
770) {
771    platform_log(LOG_TAG, "calling rcs_client_upload_file()");
772
773    let progress_cb_context = if progress_cb_context.is_null() {
774        None
775    } else {
776        Some(UploadFileProgressCallbackContextWrapper(
777            NonNull::new(progress_cb_context).unwrap(),
778        ))
779    };
780
781    let result_cb_context = if result_cb_context.is_null() {
782        None
783    } else {
784        Some(UploadFileResultCallbackContextWrapper(
785            NonNull::new(result_cb_context).unwrap(),
786        ))
787    };
788
789    if let Some(rcs_runtime) = rcs_runtime.as_ref() {
790        let rt = Arc::clone(&rcs_runtime.rt);
791        if let Some(client) = client.as_ref() {
792            let msisdn = client.msisdn.as_deref();
793
794            if let Some(gba_context) = &client.engine_gba_context {
795                let gba_context = Arc::clone(gba_context);
796                let http_client = client.context.get_http_client();
797                let security_context = client.context.get_security_context();
798
799                let tid = CStr::from_ptr(tid).to_string_lossy();
800
801                let file_path = CStr::from_ptr(file_path).to_string_lossy();
802                let file_name = CStr::from_ptr(file_name).to_string_lossy();
803                let file_mime = CStr::from_ptr(file_mime).to_string_lossy();
804                let file_hash = if file_hash.is_null() {
805                    None
806                } else {
807                    Some(CStr::from_ptr(file_hash).to_string_lossy())
808                };
809
810                let thumbnail_path = if thumbnail_path.is_null() {
811                    None
812                } else {
813                    Some(CStr::from_ptr(thumbnail_path).to_string_lossy())
814                };
815
816                let thumbnail_name = if thumbnail_name.is_null() {
817                    None
818                } else {
819                    Some(CStr::from_ptr(thumbnail_name).to_string_lossy())
820                };
821
822                let thumbnail_mime = if thumbnail_mime.is_null() {
823                    None
824                } else {
825                    Some(CStr::from_ptr(thumbnail_mime).to_string_lossy())
826                };
827
828                let thumbnail_hash = if thumbnail_hash.is_null() {
829                    None
830                } else {
831                    Some(CStr::from_ptr(thumbnail_hash).to_string_lossy())
832                };
833
834                let progress_cb_context_ = if let Some(progress_cb_context) = progress_cb_context {
835                    Some(Arc::new(Mutex::new(progress_cb_context)))
836                } else {
837                    None
838                };
839
840                let result_cb_context_ = if let Some(result_cb_context) = result_cb_context {
841                    Some(Arc::new(Mutex::new(result_cb_context)))
842                } else {
843                    None
844                };
845
846                client.engine.upload_file(
847                    &tid,
848                    &file_path,
849                    &file_name,
850                    &file_mime,
851                    file_hash.as_deref(),
852                    thumbnail_path.as_deref(),
853                    thumbnail_name.as_deref(),
854                    thumbnail_mime.as_deref(),
855                    thumbnail_hash.as_deref(),
856                    msisdn,
857                    http_client,
858                    gba_context,
859                    security_context,
860                    rt,
861                    move |current, total| {
862                        if let Some(progress_cb) = progress_cb {
863                            if let Some(progress_cb_context_) = &progress_cb_context_ {
864                                let progress_cb_context = progress_cb_context_.lock().unwrap();
865                                progress_cb(current, total, progress_cb_context.0.as_ptr());
866                            } else {
867                                progress_cb(current, total, ptr::null_mut());
868                            }
869                        }
870                    },
871                    move |status_code, reason_phrase, result_xml| {
872                        if let Some(result_cb) = result_cb {
873                            let reason_phrase = CString::new(reason_phrase).unwrap();
874                            let result_xml = if let Some(result_xml) = result_xml {
875                                let result_xml = CString::new(result_xml).unwrap();
876                                Some(result_xml)
877                            } else {
878                                None
879                            };
880                            if let Some(result_cb_context_) = result_cb_context_ {
881                                let result_cb_context = result_cb_context_.lock().unwrap();
882                                result_cb(
883                                    status_code,
884                                    reason_phrase.as_ptr(),
885                                    if let Some(result_xml) = result_xml {
886                                        result_xml.as_ptr()
887                                    } else {
888                                        ptr::null()
889                                    },
890                                    result_cb_context.0.as_ptr(),
891                                );
892                            } else {
893                                result_cb(
894                                    status_code,
895                                    reason_phrase.as_ptr(),
896                                    if let Some(result_xml) = result_xml {
897                                        result_xml.as_ptr()
898                                    } else {
899                                        ptr::null()
900                                    },
901                                    ptr::null_mut(),
902                                );
903                            }
904                        }
905                    },
906                );
907
908                return;
909            }
910        }
911    }
912
913    if let Some(result_cb) = result_cb {
914        let reason_phrase = CString::new("Forbidden").unwrap();
915        let xml = ptr::null();
916        let result_cb_context_ptr = if let Some(result_cb_context) = result_cb_context {
917            result_cb_context.0.as_ptr()
918        } else {
919            ptr::null_mut()
920        };
921        result_cb(403, reason_phrase.as_ptr(), xml, result_cb_context_ptr);
922    }
923}
924
925#[no_mangle]
926pub unsafe extern "C" fn rcs_client_download_file(
927    rcs_runtime: *mut RcsRuntime,
928    client: *mut RcsClient,
929    file_uri: *const c_char,
930    download_path: *const c_char,
931    start: u32,
932    total: i32,
933    progress_cb: Option<DownloadFileProgressCallback>,
934    progress_cb_context: *mut DownloadFileProgressCallbackContext,
935    result_cb: Option<DownloadFileResultCallback>,
936    result_cb_context: *mut DownloadFileResultCallbackContext,
937) {
938    platform_log(LOG_TAG, "calling rcs_client_download_file()");
939
940    let progress_cb_context = if progress_cb_context.is_null() {
941        None
942    } else {
943        Some(DownloadileProgressCallbackContextWrapper(
944            NonNull::new(progress_cb_context).unwrap(),
945        ))
946    };
947
948    let result_cb_context = if result_cb_context.is_null() {
949        None
950    } else {
951        Some(DownloadFileResultCallbackContextWrapper(
952            NonNull::new(result_cb_context).unwrap(),
953        ))
954    };
955
956    if let Some(rcs_runtime) = rcs_runtime.as_ref() {
957        let rt = Arc::clone(&rcs_runtime.rt);
958        if let Some(client) = client.as_ref() {
959            let msisdn = client.msisdn.as_deref();
960
961            if let Some(gba_context) = &client.engine_gba_context {
962                let gba_context = Arc::clone(gba_context);
963                let http_client = client.context.get_http_client();
964                let security_context = client.context.get_security_context();
965
966                let file_uri = CStr::from_ptr(file_uri).to_string_lossy();
967                let download_path = CStr::from_ptr(download_path).to_string_lossy();
968
969                if let Ok(start) = usize::try_from(start) {
970                    let total = if total > 0 {
971                        if let Ok(total) = usize::try_from(total) {
972                            Some(total)
973                        } else {
974                            None
975                        }
976                    } else {
977                        None
978                    };
979
980                    let progress_cb_context_ =
981                        if let Some(progress_cb_context) = progress_cb_context {
982                            Some(Arc::new(Mutex::new(progress_cb_context)))
983                        } else {
984                            None
985                        };
986
987                    let result_cb_context_ = if let Some(result_cb_context) = result_cb_context {
988                        Some(Arc::new(Mutex::new(result_cb_context)))
989                    } else {
990                        None
991                    };
992
993                    client.engine.download_file(
994                        &file_uri,
995                        &download_path,
996                        start,
997                        total,
998                        msisdn,
999                        http_client,
1000                        gba_context,
1001                        security_context,
1002                        rt,
1003                        move |current, total| {
1004                            if let Some(progress_cb) = progress_cb {
1005                                if let Some(progress_cb_context_) = &progress_cb_context_ {
1006                                    let progress_cb_context = progress_cb_context_.lock().unwrap();
1007                                    progress_cb(current, total, progress_cb_context.0.as_ptr());
1008                                } else {
1009                                    progress_cb(current, total, ptr::null_mut());
1010                                }
1011                            }
1012                        },
1013                        move |status_code, reason_phrase| {
1014                            if let Some(result_cb) = result_cb {
1015                                let reason_phrase = CString::new(reason_phrase).unwrap();
1016                                if let Some(result_cb_context_) = result_cb_context_ {
1017                                    let result_cb_context = result_cb_context_.lock().unwrap();
1018                                    result_cb(
1019                                        status_code,
1020                                        reason_phrase.as_ptr(),
1021                                        result_cb_context.0.as_ptr(),
1022                                    );
1023                                } else {
1024                                    result_cb(status_code, reason_phrase.as_ptr(), ptr::null_mut());
1025                                }
1026                            }
1027                        },
1028                    );
1029
1030                    return;
1031                }
1032            }
1033        }
1034    }
1035
1036    if let Some(cb) = result_cb {
1037        let reason_phrase = CString::new("Forbidden").unwrap();
1038        let result_cb_context_ptr = if let Some(result_cb_context) = result_cb_context {
1039            result_cb_context.0.as_ptr()
1040        } else {
1041            ptr::null_mut()
1042        };
1043        cb(403, reason_phrase.as_ptr(), result_cb_context_ptr);
1044    }
1045}
1046
1047#[no_mangle]
1048pub unsafe extern "C" fn destroy_rcs_messaging_session(
1049    session_handle: *mut MessagingSessionHandle,
1050) {
1051    let _ = Box::from_raw(session_handle);
1052}
1053
1054#[no_mangle]
1055pub unsafe extern "C" fn rcs_client_create_multi_conference_v1(
1056    rcs_runtime: *mut RcsRuntime,
1057    client: *mut RcsClient,
1058    recipients: *const c_char,
1059    offer_sdp: *const c_char,
1060    event_cb: Option<MultiConferenceEventListener>,
1061    event_cb_context: *mut MultiConferenceEventListenerContext,
1062    result_cb: Option<MultiConferenceCreateResultCallback>,
1063    result_cb_context: *mut MultiConferenceCreateResultCallbackContext,
1064) {
1065    platform_log(LOG_TAG, "calling rcs_client_create_multi_conference_v1()");
1066
1067    let event_cb_context =
1068        MultiConferenceEventListenerContextWrapper(NonNull::new(event_cb_context).unwrap());
1069
1070    let result_cb_context =
1071        MultiConferenceCreateResultCallbackContextWrapper(NonNull::new(result_cb_context).unwrap());
1072
1073    if let Some(rcs_runtime) = rcs_runtime.as_ref() {
1074        if let Some(client) = client.as_ref() {
1075            let recipients = CStr::from_ptr(recipients).to_string_lossy();
1076            let offer_sdp = CStr::from_ptr(offer_sdp).to_string_lossy();
1077
1078            let result_cb_context = Arc::new(Mutex::new(result_cb_context));
1079
1080            client.engine.create_conference_v1(
1081                &recipients,
1082                &offer_sdp,
1083                event_cb,
1084                event_cb_context,
1085                &rcs_runtime.rt,
1086                move |result| {
1087                    if let Some(cb) = result_cb {
1088                        let cb_context = result_cb_context.lock().unwrap();
1089                        let cb_context_ptr = cb_context.0.as_ptr();
1090                        if let Some((conf, sdp)) = result {
1091                            let sdp_ptr = sdp.as_ptr();
1092                            let sdp_len = sdp.len();
1093                            cb(Some(Box::new(conf)), sdp_ptr, sdp_len, cb_context_ptr);
1094                        } else {
1095                            cb(None, ptr::null(), 0, cb_context_ptr);
1096                        }
1097                    }
1098                },
1099            );
1100
1101            return;
1102        }
1103    }
1104}
1105
1106#[no_mangle]
1107pub unsafe extern "C" fn rcs_client_join_multi_conference_v1(
1108    rcs_runtime: *mut RcsRuntime,
1109    client: *mut RcsClient,
1110    conference_id: *const c_char,
1111    offer_sdp: *const c_char,
1112    event_cb: Option<MultiConferenceEventListener>,
1113    event_cb_context: *mut MultiConferenceEventListenerContext,
1114    result_cb: Option<MultiConferenceJoinResultCallback>,
1115    result_cb_context: *mut MultiConferenceJoinResultCallbackContext,
1116) {
1117    platform_log(LOG_TAG, "calling rcs_client_join_multi_conference_v1()");
1118
1119    let _ = MultiConferenceEventListenerContextWrapper(NonNull::new(event_cb_context).unwrap());
1120    let _ =
1121        MultiConferenceJoinResultCallbackContextWrapper(NonNull::new(result_cb_context).unwrap());
1122}
1123
1124// #[no_mangle]
1125// pub unsafe extern "C" fn rcs_multi_conference_register_event_listener(
1126//     rcs_runtime: *mut RcsRuntime,
1127//     conference: *mut MultiConferenceV1,
1128//     cb: Option<MultiConferenceEventCallback>,
1129//     cb_context: *mut MultiConferenceEventCallbackContext,
1130// ) {
1131//     let cb_context = MultiConferenceEventCallbackContextWrapper(NonNull::new(cb_context).unwrap());
1132
1133//     if let Some(rcs_runtime) = rcs_runtime.as_ref() {
1134//         if let Some(conference) = conference.as_mut() {
1135//             if let Some(cb) = cb {
1136//                 conference.register_event_listener(cb, cb_context);
1137//             }
1138//         }
1139//     }
1140// }
1141
1142#[no_mangle]
1143pub unsafe extern "C" fn rcs_multi_conference_v1_keep_alive(
1144    rcs_runtime: *mut RcsRuntime,
1145    conference: *mut MultiConferenceV1,
1146) {
1147    if let Some(rcs_runtime) = rcs_runtime.as_ref() {
1148        if let Some(conference) = conference.as_mut() {
1149            conference.keep_alive(&rcs_runtime.rt);
1150        }
1151    }
1152}
1153
1154#[no_mangle]
1155pub unsafe extern "C" fn rcs_client_retrieve_specific_chatbots(
1156    rcs_runtime: *mut RcsRuntime,
1157    client: *mut RcsClient,
1158    local_etag: *const c_char,
1159    cb: Option<RetrieveSpecificChatbotsResultCallback>,
1160    cb_context: *mut RetrieveSpecificChatbotsResultCallbackContext,
1161) {
1162    platform_log(LOG_TAG, "calling rcs_client_retrieve_specific_chatbots()");
1163
1164    let cb_context =
1165        RetrieveSpecificChatbotsResultCallbackContextWrapper(NonNull::new(cb_context).unwrap());
1166
1167    if let Some(rcs_runtime) = rcs_runtime.as_ref() {
1168        let rt = Arc::clone(&rcs_runtime.rt);
1169        if let Some(client) = client.as_mut() {
1170            let local_etag = if local_etag.is_null() {
1171                None
1172            } else {
1173                Some(CStr::from_ptr(local_etag).to_string_lossy())
1174            };
1175
1176            let msisdn = client.msisdn.as_deref();
1177
1178            if let Some(gba_context) = &client.engine_gba_context {
1179                let gba_context = Arc::clone(gba_context);
1180                let http_client = client.context.get_http_client();
1181                let security_context = client.context.get_security_context();
1182
1183                let cb_context_ = Arc::new(Mutex::new(cb_context));
1184
1185                client.engine.retrieve_specific_chatbots(
1186                    local_etag.as_deref(),
1187                    msisdn,
1188                    http_client,
1189                    gba_context,
1190                    security_context,
1191                    rt,
1192                    move |status_code, reason_phrase, specific_chatbots, response_etag, expiry| {
1193                        if let Some(cb) = cb {
1194                            let reason_phrase = CString::new(reason_phrase).unwrap();
1195
1196                            let specific_chatbots = match specific_chatbots {
1197                                Some(specific_chatbots) => {
1198                                    Some(CString::new(specific_chatbots).unwrap())
1199                                }
1200                                None => None,
1201                            };
1202                            let response_etag = match response_etag {
1203                                Some(response_etag) => Some(CString::new(response_etag).unwrap()),
1204                                None => None,
1205                            };
1206
1207                            let cb_context = cb_context_.lock().unwrap();
1208                            let cb_context_ptr = cb_context.0.as_ptr();
1209                            cb(
1210                                status_code,
1211                                reason_phrase.as_ptr(),
1212                                if let Some(specific_chatbots) = &specific_chatbots {
1213                                    specific_chatbots.as_ptr()
1214                                } else {
1215                                    std::ptr::null()
1216                                },
1217                                if let Some(response_etag) = &response_etag {
1218                                    response_etag.as_ptr()
1219                                } else {
1220                                    std::ptr::null()
1221                                },
1222                                expiry,
1223                                cb_context_ptr,
1224                            );
1225                        }
1226                    },
1227                );
1228
1229                return;
1230            }
1231        }
1232    }
1233
1234    if let Some(cb) = cb {
1235        let reason_phrase = CString::new("Forbidden").unwrap();
1236        let cb_context_ptr = cb_context.0.as_ptr();
1237        cb(
1238            403,
1239            reason_phrase.as_ptr(),
1240            std::ptr::null(),
1241            std::ptr::null(),
1242            0,
1243            cb_context_ptr,
1244        );
1245    }
1246}
1247
1248#[no_mangle]
1249pub unsafe extern "C" fn rcs_client_search_chatbot(
1250    rcs_runtime: *mut RcsRuntime,
1251    client: *mut RcsClient,
1252    query: *const c_char,
1253    start: u32,
1254    num: u32,
1255    cb: Option<SearchChatbotResultCallback>,
1256    cb_context: *mut SearchChatbotResultCallbackContext,
1257) {
1258    platform_log(LOG_TAG, "calling rcs_client_search_chatbot()");
1259
1260    let cb_context = SearchChatbotResultCallbackContextWrapper(NonNull::new(cb_context).unwrap());
1261
1262    if let Some(rcs_runtime) = rcs_runtime.as_ref() {
1263        let rt = Arc::clone(&rcs_runtime.rt);
1264        if let Some(client) = client.as_mut() {
1265            let query = CStr::from_ptr(query).to_string_lossy();
1266            let home_operator = format!("{}{}", client.mcc.string_repr(), client.mnc.string_repr());
1267            let msisdn = client.msisdn.as_deref();
1268
1269            if let Some(gba_context) = &client.engine_gba_context {
1270                let gba_context = Arc::clone(gba_context);
1271                let http_client = client.context.get_http_client();
1272                let security_context = client.context.get_security_context();
1273
1274                let cb_context_ = Arc::new(Mutex::new(cb_context));
1275
1276                client.engine.search_chatbot(
1277                    &query,
1278                    start,
1279                    num,
1280                    &home_operator,
1281                    msisdn,
1282                    http_client,
1283                    gba_context,
1284                    security_context,
1285                    rt,
1286                    move |status_code, reason_phrase, json| {
1287                        if let Some(cb) = cb {
1288                            let reason_phrase = CString::new(reason_phrase).unwrap();
1289                            let json = match json {
1290                                Some(json) => Some(CString::new(json).unwrap()),
1291                                None => None,
1292                            };
1293
1294                            let cb_context = cb_context_.lock().unwrap();
1295                            let cb_context_ptr = cb_context.0.as_ptr();
1296                            cb(
1297                                status_code,
1298                                reason_phrase.as_ptr(),
1299                                if let Some(json) = &json {
1300                                    json.as_ptr()
1301                                } else {
1302                                    std::ptr::null()
1303                                },
1304                                cb_context_ptr,
1305                            );
1306                        }
1307                    },
1308                );
1309
1310                return;
1311            }
1312        }
1313    }
1314
1315    if let Some(cb) = cb {
1316        let reason_phrase = CString::new("Forbidden").unwrap();
1317        let json = ptr::null();
1318        let cb_context_ptr = cb_context.0.as_ptr();
1319        cb(403, reason_phrase.as_ptr(), json, cb_context_ptr);
1320    }
1321}
1322
1323#[no_mangle]
1324pub unsafe extern "C" fn rcs_client_retrieve_chatbot_info(
1325    rcs_runtime: *mut RcsRuntime,
1326    client: *mut RcsClient,
1327    chatbot_sip_uri: *const c_char,
1328    local_etag: *const c_char,
1329    cb: Option<RetrieveChatbotInfoResultCallback>,
1330    cb_context: *mut RetrieveChatbotInfoResultCallbackContext,
1331) {
1332    platform_log(LOG_TAG, "calling rcs_client_retrieve_chatbot_info()");
1333
1334    let cb_context =
1335        RetrieveChatbotInfoResultCallbackContextWrapper(NonNull::new(cb_context).unwrap());
1336
1337    if let Some(rcs_runtime) = rcs_runtime.as_ref() {
1338        let rt = Arc::clone(&rcs_runtime.rt);
1339        if let Some(client) = client.as_mut() {
1340            let chatbot_sip_uri = CStr::from_ptr(chatbot_sip_uri).to_string_lossy();
1341            let local_etag = if local_etag.is_null() {
1342                None
1343            } else {
1344                Some(CStr::from_ptr(local_etag).to_string_lossy())
1345            };
1346            let home_operator = format!("{}{}", client.mcc.string_repr(), client.mnc.string_repr());
1347            let home_language = String::from("zh"); // to-do: might want to pass it down
1348            let msisdn = client.msisdn.as_deref();
1349
1350            if let Some(gba_context) = &client.engine_gba_context {
1351                let gba_context = Arc::clone(gba_context);
1352                let http_client = client.context.get_http_client();
1353                let security_context = client.context.get_security_context();
1354
1355                let cb_context_ = Arc::new(Mutex::new(cb_context));
1356
1357                client.engine.retrieve_chatbot_info(
1358                    &chatbot_sip_uri,
1359                    local_etag.as_deref(),
1360                    &home_operator,
1361                    &home_language,
1362                    msisdn,
1363                    http_client,
1364                    gba_context,
1365                    security_context,
1366                    rt,
1367                    move |status_code, reason_phrase, chatbot_info, response_etag, expiry| {
1368                        if let Some(cb) = cb {
1369                            let reason_phrase = CString::new(reason_phrase).unwrap();
1370
1371                            let chatbot_info = match chatbot_info {
1372                                Some(chatbot_info) => Some(CString::new(chatbot_info).unwrap()),
1373                                None => None,
1374                            };
1375                            let response_etag = match response_etag {
1376                                Some(response_etag) => Some(CString::new(response_etag).unwrap()),
1377                                None => None,
1378                            };
1379
1380                            let cb_context = cb_context_.lock().unwrap();
1381                            let cb_context_ptr = cb_context.0.as_ptr();
1382                            cb(
1383                                status_code,
1384                                reason_phrase.as_ptr(),
1385                                if let Some(chatbot_info) = &chatbot_info {
1386                                    chatbot_info.as_ptr()
1387                                } else {
1388                                    std::ptr::null()
1389                                },
1390                                if let Some(response_etag) = &response_etag {
1391                                    response_etag.as_ptr()
1392                                } else {
1393                                    std::ptr::null()
1394                                },
1395                                expiry,
1396                                cb_context_ptr,
1397                            );
1398                        }
1399                    },
1400                );
1401
1402                return;
1403            }
1404        }
1405    }
1406
1407    if let Some(cb) = cb {
1408        let reason_phrase = CString::new("Forbidden").unwrap();
1409        let cb_context_ptr = cb_context.0.as_ptr();
1410        cb(
1411            403,
1412            reason_phrase.as_ptr(),
1413            ptr::null(),
1414            ptr::null(),
1415            0,
1416            cb_context_ptr,
1417        );
1418    }
1419}
1420
1421#[no_mangle]
1422pub unsafe extern "C" fn destroy_rcs_client(client: *mut RcsClient) {
1423    let _ = Box::from_raw(client);
1424}