1use std::cell::Cell;
2use std::fmt;
3use std::marker::PhantomData;
4use std::os::raw::{c_char, c_void};
5use std::panic::{AssertUnwindSafe, catch_unwind};
6use std::ptr;
7use std::sync::Arc;
8
9use maplibre_native_core as maplibre_core;
10use maplibre_native_sys as sys;
11
12use crate::{HandleOperationError, Result};
13
14pub use maplibre_core::resource::{
15 ByteRange, ResourceProviderDecision, ResourceRequest, ResourceResponse,
16 ResourceTransformRequest,
17};
18
19use maplibre_core::resource::{
20 ResourceRequestHandleFns, ResourceRequestHandleState, UNKNOWN_PROVIDER_DECISION,
21};
22
23pub struct ResourceRequestHandle {
29 state: Arc<ResourceRequestHandleState>,
30 _not_sync: PhantomData<Cell<()>>,
31}
32
33impl fmt::Debug for ResourceRequestHandle {
34 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35 f.debug_struct("ResourceRequestHandle")
36 .finish_non_exhaustive()
37 }
38}
39
40impl ResourceRequestHandle {
41 fn from_state(state: Arc<ResourceRequestHandleState>) -> Self {
42 Self {
43 state,
44 _not_sync: PhantomData,
45 }
46 }
47
48 fn from_raw_with_fns(
49 handle: *mut sys::mln_resource_request_handle,
50 fns: ResourceRequestHandleFns,
51 ) -> Result<Self> {
52 unsafe { ResourceRequestHandleState::new(handle, fns) }.map(Self::from_state)
55 }
56
57 pub fn complete(
65 self,
66 response: ResourceResponse,
67 ) -> std::result::Result<(), HandleOperationError<Self>> {
68 self.state
69 .complete(&response)
70 .map_err(|error| HandleOperationError::new(error, self))
71 }
72
73 pub fn is_cancelled(&self) -> Result<bool> {
75 self.state.is_cancelled()
76 }
77
78 pub fn close(self) {
80 self.state.close();
81 }
82}
83
84impl Drop for ResourceRequestHandle {
85 fn drop(&mut self) {
86 self.state.close();
87 }
88}
89
90type ResourceProviderCallback = dyn Fn(ResourceRequest, ResourceRequestHandle) -> ResourceProviderDecision
91 + Send
92 + Sync
93 + 'static;
94
95pub(crate) struct ResourceProviderState {
96 callback: Box<ResourceProviderCallback>,
97 handle_fns: ResourceRequestHandleFns,
98}
99
100impl fmt::Debug for ResourceProviderState {
101 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102 f.debug_struct("ResourceProviderState")
103 .finish_non_exhaustive()
104 }
105}
106
107impl ResourceProviderState {
108 pub(crate) fn new<F>(callback: F) -> Box<Self>
109 where
110 F: Fn(ResourceRequest, ResourceRequestHandle) -> ResourceProviderDecision
111 + Send
112 + Sync
113 + 'static,
114 {
115 Box::new(Self {
116 callback: Box::new(callback),
117 handle_fns: ResourceRequestHandleFns::NATIVE,
118 })
119 }
120
121 pub(crate) fn descriptor(&self) -> sys::mln_resource_provider {
122 maplibre_core::resource::resource_provider_descriptor(
123 Some(resource_provider_trampoline),
124 ptr::from_ref(self).cast_mut().cast::<c_void>(),
125 )
126 }
127
128 fn invoke(
129 &self,
130 request: *const sys::mln_resource_request,
131 handle: *mut sys::mln_resource_request_handle,
132 ) -> u32 {
133 let Some(raw_request) = ptr::NonNull::new(request.cast_mut()) else {
134 return UNKNOWN_PROVIDER_DECISION;
135 };
136 let handle = match ResourceRequestHandle::from_raw_with_fns(handle, self.handle_fns) {
137 Ok(handle) => handle,
138 Err(_) => return UNKNOWN_PROVIDER_DECISION,
139 };
140 let state = Arc::clone(&handle.state);
141
142 let request =
144 match unsafe { maplibre_core::resource::copy_resource_request(raw_request.as_ref()) } {
145 Ok(request) => request,
146 Err(_) => return state.finish_provider_exception(),
147 };
148
149 match catch_unwind(AssertUnwindSafe(|| (self.callback)(request, handle))) {
150 Ok(decision) => state.finish_provider_decision(decision),
151 Err(_) => state.finish_provider_exception(),
152 }
153 }
154}
155
156unsafe extern "C" fn resource_provider_trampoline(
157 user_data: *mut c_void,
158 request: *const sys::mln_resource_request,
159 handle: *mut sys::mln_resource_request_handle,
160) -> u32 {
161 let Some(state) = ptr::NonNull::new(user_data.cast::<ResourceProviderState>()) else {
162 return UNKNOWN_PROVIDER_DECISION;
163 };
164 unsafe { state.as_ref() }.invoke(request, handle)
168}
169
170type ResourceTransformCallback =
171 dyn Fn(ResourceTransformRequest) -> Option<String> + Send + Sync + 'static;
172
173pub(crate) struct ResourceTransformState {
174 callback: Box<ResourceTransformCallback>,
175}
176
177impl fmt::Debug for ResourceTransformState {
178 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179 f.debug_struct("ResourceTransformState")
180 .finish_non_exhaustive()
181 }
182}
183
184impl ResourceTransformState {
185 pub(crate) fn new<F>(callback: F) -> Box<Self>
186 where
187 F: Fn(ResourceTransformRequest) -> Option<String> + Send + Sync + 'static,
188 {
189 Box::new(Self {
190 callback: Box::new(callback),
191 })
192 }
193
194 pub(crate) fn descriptor(&self) -> sys::mln_resource_transform {
195 maplibre_core::resource::resource_transform_descriptor(
196 Some(resource_transform_trampoline),
197 ptr::from_ref(self).cast_mut().cast::<c_void>(),
198 )
199 }
200
201 fn invoke(
202 &self,
203 raw_kind: u32,
204 url: *const c_char,
205 out_response: *mut sys::mln_resource_transform_response,
206 ) -> sys::mln_status {
207 let status = unsafe {
210 maplibre_core::resource::initialize_resource_transform_response(out_response)
211 };
212 if status != sys::MLN_STATUS_OK {
213 return status;
214 }
215
216 let request = match unsafe {
218 maplibre_core::resource::copy_resource_transform_request(raw_kind, url)
219 } {
220 Ok(request) => request,
221 Err(error) => return maplibre_core::resource::status_for_error(&error),
222 };
223
224 let replacement = match catch_unwind(AssertUnwindSafe(|| (self.callback)(request))) {
225 Ok(replacement) => replacement,
226 Err(_) => return sys::MLN_STATUS_NATIVE_ERROR,
227 };
228
229 match replacement {
230 Some(replacement) if !replacement.is_empty() => {
231 unsafe {
237 sys::mln_resource_transform_response_set_url(
238 out_response,
239 replacement.as_ptr().cast(),
240 replacement.len(),
241 )
242 }
243 }
244 _ => sys::MLN_STATUS_OK,
245 }
246 }
247}
248
249unsafe extern "C" fn resource_transform_trampoline(
250 user_data: *mut c_void,
251 kind: u32,
252 url: *const c_char,
253 out_response: *mut sys::mln_resource_transform_response,
254) -> sys::mln_status {
255 let Some(state) = ptr::NonNull::new(user_data.cast::<ResourceTransformState>()) else {
256 return sys::MLN_STATUS_INVALID_ARGUMENT;
257 };
258 unsafe { state.as_ref() }.invoke(kind, url, out_response)
262}
263
264#[cfg(test)]
265mod tests {
266 use std::ffi::CString;
267 use std::sync::Mutex as StdMutex;
268 use std::sync::atomic::{AtomicBool, AtomicI32, AtomicUsize, Ordering};
269
270 use static_assertions::{assert_impl_all, assert_not_impl_any};
271
272 use super::*;
273 use crate::{
274 ErrorKind, ResourceKind, ResourceLoadingMethod, ResourcePriority, ResourceStoragePolicy,
275 ResourceUsage,
276 };
277
278 static FAKE_HANDLE_TEST_LOCK: StdMutex<()> = StdMutex::new(());
279 static COMPLETE_COUNT: AtomicUsize = AtomicUsize::new(0);
280 static RELEASE_COUNT: AtomicUsize = AtomicUsize::new(0);
281 static CANCELLED_VALUE: AtomicBool = AtomicBool::new(false);
282 static COMPLETE_STATUS: AtomicI32 = AtomicI32::new(sys::MLN_STATUS_OK);
283
284 unsafe extern "C" fn fake_complete(
285 _handle: *mut sys::mln_resource_request_handle,
286 _response: *const sys::mln_resource_response,
287 ) -> sys::mln_status {
288 COMPLETE_COUNT.fetch_add(1, Ordering::SeqCst);
289 COMPLETE_STATUS.load(Ordering::SeqCst)
290 }
291
292 unsafe extern "C" fn fake_cancelled(
293 _handle: *const sys::mln_resource_request_handle,
294 out_cancelled: *mut bool,
295 ) -> sys::mln_status {
296 if out_cancelled.is_null() {
297 return sys::MLN_STATUS_INVALID_ARGUMENT;
298 }
299 unsafe { *out_cancelled = CANCELLED_VALUE.load(Ordering::SeqCst) };
301 sys::MLN_STATUS_OK
302 }
303
304 unsafe extern "C" fn fake_release(_handle: *mut sys::mln_resource_request_handle) {
305 RELEASE_COUNT.fetch_add(1, Ordering::SeqCst);
306 }
307
308 fn fake_fns() -> ResourceRequestHandleFns {
309 unsafe { ResourceRequestHandleFns::new(fake_complete, fake_cancelled, fake_release) }
312 }
313
314 fn reset_fake_handle_state() {
315 COMPLETE_COUNT.store(0, Ordering::SeqCst);
316 RELEASE_COUNT.store(0, Ordering::SeqCst);
317 CANCELLED_VALUE.store(false, Ordering::SeqCst);
318 COMPLETE_STATUS.store(sys::MLN_STATUS_OK, Ordering::SeqCst);
319 }
320
321 fn fake_handle() -> ResourceRequestHandle {
322 reset_fake_handle_state();
323 ResourceRequestHandle::from_raw_with_fns(
324 0x1234usize as *mut sys::mln_resource_request_handle,
325 fake_fns(),
326 )
327 .unwrap()
328 }
329
330 fn request() -> sys::mln_resource_request {
331 sys::mln_resource_request {
332 size: std::mem::size_of::<sys::mln_resource_request>() as u32,
333 url: c"https://example.test/tile.pbf".as_ptr(),
334 kind: sys::MLN_RESOURCE_KIND_TILE,
335 loading_method: sys::MLN_RESOURCE_LOADING_METHOD_NETWORK_ONLY,
336 priority: sys::MLN_RESOURCE_PRIORITY_LOW,
337 usage: sys::MLN_RESOURCE_USAGE_OFFLINE,
338 storage_policy: sys::MLN_RESOURCE_STORAGE_POLICY_VOLATILE,
339 has_range: true,
340 range_start: 7,
341 range_end: 11,
342 has_prior_modified: true,
343 prior_modified_unix_ms: 123,
344 has_prior_expires: true,
345 prior_expires_unix_ms: 456,
346 prior_etag: c"etag".as_ptr(),
347 prior_data: [1u8, 2, 3].as_ptr(),
348 prior_data_size: 3,
349 }
350 }
351
352 fn response() -> sys::mln_resource_transform_response {
353 sys::mln_resource_transform_response {
354 size: std::mem::size_of::<sys::mln_resource_transform_response>() as u32,
355 url: ptr::null(),
356 context: ptr::null_mut(),
357 }
358 }
359
360 #[test]
361 fn resource_request_handle_is_send_but_not_sync() {
362 assert_impl_all!(ResourceRequestHandle: Send);
363 assert_not_impl_any!(ResourceRequestHandle: Sync);
364 }
365
366 #[test]
367 fn provider_callback_copies_request_and_pass_through_does_not_release() {
368 let _guard = FAKE_HANDLE_TEST_LOCK.lock().unwrap();
369 reset_fake_handle_state();
370 let state = ResourceProviderState {
371 callback: Box::new(|request, handle| {
372 assert_eq!(request.url, "https://example.test/tile.pbf");
373 assert_eq!(request.kind, ResourceKind::Tile);
374 assert_eq!(request.raw_kind, sys::MLN_RESOURCE_KIND_TILE);
375 assert_eq!(request.loading_method, ResourceLoadingMethod::NetworkOnly);
376 assert_eq!(request.priority, ResourcePriority::Low);
377 assert_eq!(request.usage, ResourceUsage::Offline);
378 assert_eq!(request.storage_policy, ResourceStoragePolicy::Volatile);
379 let range = request.range.unwrap();
380 assert_eq!((range.start, range.end), (7, 11));
381 assert_eq!(request.prior_modified_unix_ms, Some(123));
382 assert_eq!(request.prior_expires_unix_ms, Some(456));
383 assert_eq!(request.prior_etag.as_deref(), Some("etag"));
384 assert_eq!(request.prior_data, vec![1, 2, 3]);
385 drop(handle);
386 ResourceProviderDecision::PassThrough
387 }),
388 handle_fns: fake_fns(),
389 };
390 let raw_request = request();
391
392 let decision = state.invoke(
393 &raw_request,
394 0x1234usize as *mut sys::mln_resource_request_handle,
395 );
396
397 assert_eq!(decision, sys::MLN_RESOURCE_PROVIDER_DECISION_PASS_THROUGH);
398 assert_eq!(RELEASE_COUNT.load(Ordering::SeqCst), 0);
399 }
400
401 #[test]
402 fn inline_completion_returns_handle_and_releases_once() {
403 let _guard = FAKE_HANDLE_TEST_LOCK.lock().unwrap();
404 reset_fake_handle_state();
405 let state = ResourceProviderState {
406 callback: Box::new(|_, handle| {
407 handle.complete(ResourceResponse::ok([1, 2, 3])).unwrap();
408 ResourceProviderDecision::PassThrough
409 }),
410 handle_fns: fake_fns(),
411 };
412 let raw_request = request();
413
414 let decision = state.invoke(
415 &raw_request,
416 0x1234usize as *mut sys::mln_resource_request_handle,
417 );
418
419 assert_eq!(decision, sys::MLN_RESOURCE_PROVIDER_DECISION_HANDLE);
420 assert_eq!(COMPLETE_COUNT.load(Ordering::SeqCst), 1);
421 assert_eq!(RELEASE_COUNT.load(Ordering::SeqCst), 1);
422 }
423
424 #[test]
425 fn deferred_completion_from_another_thread_releases_once() {
426 let _guard = FAKE_HANDLE_TEST_LOCK.lock().unwrap();
427 let handle = fake_handle();
428 assert_eq!(
429 handle
430 .state
431 .finish_provider_decision(ResourceProviderDecision::Handle),
432 sys::MLN_RESOURCE_PROVIDER_DECISION_HANDLE
433 );
434
435 let thread = std::thread::spawn(move || {
436 handle
437 .complete(ResourceResponse::ok(vec![4, 5, 6]))
438 .unwrap();
439 });
440 thread.join().unwrap();
441
442 assert_eq!(COMPLETE_COUNT.load(Ordering::SeqCst), 1);
443 assert_eq!(RELEASE_COUNT.load(Ordering::SeqCst), 1);
444 }
445
446 #[test]
447 fn failed_completion_returns_handle_for_retry() {
448 let _guard = FAKE_HANDLE_TEST_LOCK.lock().unwrap();
449 let handle = fake_handle();
450 COMPLETE_STATUS.store(sys::MLN_STATUS_INVALID_STATE, Ordering::SeqCst);
451
452 let error = handle
453 .complete(ResourceResponse::ok(Vec::new()))
454 .unwrap_err();
455 assert_eq!(error.kind(), ErrorKind::InvalidState);
456 assert_eq!(COMPLETE_COUNT.load(Ordering::SeqCst), 1);
457
458 COMPLETE_STATUS.store(sys::MLN_STATUS_OK, Ordering::SeqCst);
459 let handle = error.into_handle();
460 handle.complete(ResourceResponse::no_content()).unwrap();
461 assert_eq!(COMPLETE_COUNT.load(Ordering::SeqCst), 2);
462 }
463
464 #[test]
465 fn drop_releases_uncompleted_handled_request_once() {
466 let _guard = FAKE_HANDLE_TEST_LOCK.lock().unwrap();
467 let handle = fake_handle();
468 assert_eq!(
469 handle
470 .state
471 .finish_provider_decision(ResourceProviderDecision::Handle),
472 sys::MLN_RESOURCE_PROVIDER_DECISION_HANDLE
473 );
474 drop(handle);
475
476 assert_eq!(RELEASE_COUNT.load(Ordering::SeqCst), 1);
477 }
478
479 #[test]
480 fn close_before_handle_decision_releases_after_decision() {
481 let _guard = FAKE_HANDLE_TEST_LOCK.lock().unwrap();
482 let handle = fake_handle();
483 let state = Arc::clone(&handle.state);
484 handle.close();
485 assert_eq!(RELEASE_COUNT.load(Ordering::SeqCst), 0);
486 assert_eq!(
487 state.finish_provider_decision(ResourceProviderDecision::Handle),
488 sys::MLN_RESOURCE_PROVIDER_DECISION_HANDLE
489 );
490
491 assert_eq!(RELEASE_COUNT.load(Ordering::SeqCst), 1);
492 }
493
494 #[test]
495 fn cancelled_queries_use_native_function_until_closed() {
496 let _guard = FAKE_HANDLE_TEST_LOCK.lock().unwrap();
497 let handle = fake_handle();
498 CANCELLED_VALUE.store(true, Ordering::SeqCst);
499
500 assert!(handle.is_cancelled().unwrap());
501 handle.close();
502 }
503
504 #[test]
505 fn provider_panics_produce_unknown_decision_without_unwinding() {
506 let _guard = FAKE_HANDLE_TEST_LOCK.lock().unwrap();
507 reset_fake_handle_state();
508 let state = ResourceProviderState {
509 callback: Box::new(|_, _| panic!("boom")),
510 handle_fns: fake_fns(),
511 };
512 let raw_request = request();
513
514 let decision = state.invoke(
515 &raw_request,
516 0x1234usize as *mut sys::mln_resource_request_handle,
517 );
518
519 assert_eq!(decision, UNKNOWN_PROVIDER_DECISION);
520 assert_eq!(RELEASE_COUNT.load(Ordering::SeqCst), 0);
521 }
522
523 #[test]
524 fn transform_callback_copies_request_when_keeping_original_url() {
525 let state = ResourceTransformState::new(|request| {
526 assert_eq!(request.kind, ResourceKind::Style);
527 assert_eq!(request.raw_kind, sys::MLN_RESOURCE_KIND_STYLE);
528 assert_eq!(request.url, "https://example.test/style.json");
529 None
530 });
531 let descriptor = state.descriptor();
532 let callback = descriptor.callback.unwrap();
533 let url = CString::new("https://example.test/style.json").unwrap();
534 let mut response = response();
535
536 let status = unsafe {
537 callback(
538 descriptor.user_data,
539 sys::MLN_RESOURCE_KIND_STYLE,
540 url.as_ptr(),
541 &mut response,
542 )
543 };
544
545 assert_eq!(status, sys::MLN_STATUS_OK);
546 assert!(response.url.is_null());
547 }
548
549 #[test]
550 fn transform_callback_clears_stale_response_when_keeping_original_url() {
551 let state = ResourceTransformState::new(|_| None);
552 let descriptor = state.descriptor();
553 let callback = descriptor.callback.unwrap();
554 let url = CString::new("https://example.test/style.json").unwrap();
555 let stale = CString::new("https://stale.test/style.json").unwrap();
556 let mut response = response();
557 response.url = stale.as_ptr();
558
559 let status = unsafe {
560 callback(
561 descriptor.user_data,
562 sys::MLN_RESOURCE_KIND_STYLE,
563 url.as_ptr(),
564 &mut response,
565 )
566 };
567
568 assert_eq!(status, sys::MLN_STATUS_OK);
569 assert!(response.url.is_null());
570 }
571
572 #[test]
573 fn transform_callback_contains_panics() {
574 let state = ResourceTransformState::new(|_| panic!("boom"));
575 let descriptor = state.descriptor();
576 let callback = descriptor.callback.unwrap();
577 let url = CString::new("https://example.test/style.json").unwrap();
578 let mut response = response();
579
580 let status = unsafe {
581 callback(
582 descriptor.user_data,
583 sys::MLN_RESOURCE_KIND_STYLE,
584 url.as_ptr(),
585 &mut response,
586 )
587 };
588
589 assert_eq!(status, sys::MLN_STATUS_NATIVE_ERROR);
590 assert!(response.url.is_null());
591 }
592
593 #[test]
594 fn transform_callback_rejects_embedded_nul_replacements() {
595 let state = ResourceTransformState::new(|_| Some("https://example.test/\0bad".to_owned()));
596 let descriptor = state.descriptor();
597 let callback = descriptor.callback.unwrap();
598 let url = CString::new("https://example.test/style.json").unwrap();
599 let mut response = response();
600
601 let status = unsafe {
602 callback(
603 descriptor.user_data,
604 sys::MLN_RESOURCE_KIND_STYLE,
605 url.as_ptr(),
606 &mut response,
607 )
608 };
609
610 assert_eq!(status, sys::MLN_STATUS_INVALID_ARGUMENT);
611 assert!(response.url.is_null());
612 }
613
614 #[test]
615 fn transform_state_drops_callback_capture() {
616 let token = Arc::new(());
617 let callback_token = Arc::clone(&token);
618 let state = ResourceTransformState::new(move |_| {
619 let _ = &callback_token;
620 None
621 });
622 assert_eq!(Arc::strong_count(&token), 2);
623 drop(state);
624 assert_eq!(Arc::strong_count(&token), 1);
625 }
626
627 #[test]
628 fn callback_can_run_from_multiple_threads() {
629 let calls = Arc::new(AtomicUsize::new(0));
630 let callback_calls = Arc::clone(&calls);
631 let state = Arc::new(ResourceTransformState {
632 callback: Box::new(move |request| {
633 callback_calls.fetch_add(1, Ordering::SeqCst);
634 let _ = request;
635 None
636 }),
637 });
638
639 let handles = (0..2)
640 .map(|_| {
641 let state = Arc::clone(&state);
642 std::thread::spawn(move || {
643 let descriptor = state.descriptor();
644 let callback = descriptor.callback.unwrap();
645 let url = CString::new("https://example.test/tile").unwrap();
646 let mut response = response();
647 let status = unsafe {
648 callback(
649 descriptor.user_data,
650 sys::MLN_RESOURCE_KIND_TILE,
651 url.as_ptr(),
652 &mut response,
653 )
654 };
655 assert_eq!(status, sys::MLN_STATUS_OK);
656 assert!(response.url.is_null());
657 })
658 })
659 .collect::<Vec<_>>();
660
661 for handle in handles {
662 handle.join().unwrap();
663 }
664 assert_eq!(calls.load(Ordering::SeqCst), 2);
665 }
666}