oxigdal_mobile/lib.rs
1//! OxiGDAL Mobile SDK - FFI bindings for iOS and Android
2//!
3//! This crate provides C-compatible FFI bindings that enable OxiGDAL to be used
4//! from iOS (Swift/Objective-C) and Android (Kotlin/Java) applications.
5//!
6//! # Architecture
7//!
8//! The mobile SDK is organized into several layers:
9//!
10//! - **FFI Layer** (`ffi` module): C-compatible types and functions
11//! - **Platform Layer** (`ios`/`android` modules): Platform-specific utilities
12//! - **Language Bindings** (Swift/Kotlin): High-level wrappers (in `bindings/` directory)
13//!
14//! # Safety
15//!
16//! While this crate uses `unsafe` extensively due to FFI requirements, it provides
17//! safety guarantees through:
18//!
19//! - Extensive null pointer validation
20//! - Bounds checking on all array operations
21//! - UTF-8 validation on string conversions
22//! - Proper resource lifecycle management
23//! - Thread-safe error handling
24//!
25//! # Memory Management
26//!
27//! The FFI layer follows these conventions:
28//!
29//! - **Handles**: Created by `*_open` or `*_create`, freed by `*_close` or `*_free`
30//! - **Strings**: Returned strings must be freed with `oxigdal_string_free`
31//! - **Buffers**: Caller-allocated, OxiGDAL only writes to them
32//! - **Opaque Types**: Must not be dereferenced on the foreign side
33//!
34//! # Error Handling
35//!
36//! All FFI functions return `OxiGdalErrorCode`:
37//! - `Success` (0) indicates success
38//! - Non-zero values indicate specific error types
39//! - Detailed messages available via `oxigdal_get_last_error()`
40//!
41//! # Example (C API)
42//!
43//! ```c
44//! // Initialize
45//! oxigdal_init();
46//!
47//! // Open dataset
48//! OxiGdalDataset* dataset;
49//! if (oxigdal_dataset_open("/path/to/file.tif", &dataset) != Success) {
50//! char* error = oxigdal_get_last_error();
51//! printf("Error: %s\n", error);
52//! oxigdal_string_free(error);
53//! return;
54//! }
55//!
56//! // Get metadata
57//! OxiGdalMetadata metadata;
58//! oxigdal_dataset_get_metadata(dataset, &metadata);
59//! printf("Size: %d x %d\n", metadata.width, metadata.height);
60//!
61//! // Read region
62//! OxiGdalBuffer* buffer = oxigdal_buffer_alloc(256, 256, 3);
63//! oxigdal_dataset_read_region(dataset, 0, 0, 256, 256, 1, buffer);
64//!
65//! // Cleanup
66//! oxigdal_buffer_free(buffer);
67//! oxigdal_dataset_close(dataset);
68//! oxigdal_cleanup();
69//! ```
70//!
71//! # Features
72//!
73//! - `std` (default): Enable standard library support
74//! - `ios`: Enable iOS-specific bindings
75//! - `android`: Enable Android JNI bindings
76//! - `offline`: Enable offline COG reading
77//! - `filters`: Enable image enhancement filters
78//! - `tiles`: Enable map tile generation
79//!
80//! # Platform Support
81//!
82//! ## iOS
83//! - Target: `aarch64-apple-ios`, `x86_64-apple-ios` (simulator)
84//! - Swift bindings available in `bindings/ios/`
85//! - Integration: CocoaPods, Swift Package Manager
86//!
87//! ## Android
88//! - Targets: `aarch64-linux-android`, `armv7-linux-androideabi`, `x86_64-linux-android`
89//! - Kotlin bindings available in `bindings/android/`
90//! - Integration: Gradle, AAR library
91//!
92//! # COOLJAPAN Policies
93//!
94//! This crate adheres to COOLJAPAN ecosystem policies:
95//! - **Pure Rust**: No C/C++ dependencies by default
96//! - **No Unwrap**: All error cases explicitly handled
97//! - **Workspace**: Version management via workspace
98//! - **Latest Crates**: Always use latest stable dependencies
99
100// FFI code requires unsafe functions, unsafe blocks, and no_mangle symbols
101#![allow(unsafe_code)]
102#![allow(clippy::missing_safety_doc)]
103#![allow(clippy::not_unsafe_ptr_arg_deref)]
104// FFI code requires no_mangle for C symbol export
105#![allow(clippy::no_mangle_with_rust_abi)]
106// FFI code uses expect() for internal invariant checks
107#![allow(clippy::expect_used)]
108// Allow unnecessary unsafe blocks (wrapped in outer unsafe fn)
109#![allow(unused_unsafe)]
110// Allow unused variables in FFI (may be used conditionally)
111#![allow(unused_variables)]
112// Allow unused imports in platform-specific code
113#![allow(unused_imports)]
114// Allow manual div_ceil for compatibility
115#![allow(clippy::manual_div_ceil)]
116// Allow complex types in FFI interfaces
117#![allow(clippy::type_complexity)]
118// Allow match collapsing warnings - explicit matches preferred in FFI
119#![allow(clippy::collapsible_match)]
120// Allow first element access with get(0)
121#![allow(clippy::get_first)]
122// Allow too many arguments for complex FFI operations
123#![allow(clippy::too_many_arguments)]
124#![warn(missing_docs)]
125#![deny(unsafe_op_in_unsafe_fn)]
126#![cfg_attr(not(feature = "std"), no_std)]
127#![allow(unsafe_attr_outside_unsafe)]
128// Allow unexpected cfg for conditional compilation
129#![allow(unexpected_cfgs)]
130
131#[cfg(feature = "alloc")]
132extern crate alloc;
133
134pub mod common;
135pub mod ffi;
136
137#[cfg(feature = "ios")]
138pub mod ios;
139
140#[cfg(feature = "android")]
141pub mod android;
142
143// Re-export main types for convenience
144pub use ffi::types::*;
145pub use ffi::{oxigdal_cleanup, oxigdal_init};
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[test]
152 fn test_library_init() {
153 let result = oxigdal_init();
154 assert_eq!(result, OxiGdalErrorCode::Success);
155
156 let result = oxigdal_cleanup();
157 assert_eq!(result, OxiGdalErrorCode::Success);
158 }
159
160 #[test]
161 fn test_error_codes_are_unique() {
162 // Ensure error codes don't overlap
163 let codes = vec![
164 OxiGdalErrorCode::Success as i32,
165 OxiGdalErrorCode::NullPointer as i32,
166 OxiGdalErrorCode::InvalidArgument as i32,
167 OxiGdalErrorCode::FileNotFound as i32,
168 OxiGdalErrorCode::IoError as i32,
169 OxiGdalErrorCode::UnsupportedFormat as i32,
170 OxiGdalErrorCode::OutOfBounds as i32,
171 OxiGdalErrorCode::AllocationFailed as i32,
172 OxiGdalErrorCode::InvalidUtf8 as i32,
173 OxiGdalErrorCode::DriverError as i32,
174 OxiGdalErrorCode::ProjectionError as i32,
175 OxiGdalErrorCode::Unknown as i32,
176 ];
177
178 let mut unique_codes = codes.clone();
179 unique_codes.sort();
180 unique_codes.dedup();
181
182 assert_eq!(
183 codes.len(),
184 unique_codes.len(),
185 "Error codes must be unique"
186 );
187 }
188
189 #[test]
190 fn test_repr_c_sizes() {
191 use std::mem::size_of;
192
193 // Ensure FFI types are reasonably sized
194 assert!(size_of::<OxiGdalMetadata>() < 256);
195 assert!(size_of::<OxiGdalBbox>() == 32); // 4 * f64
196 assert!(size_of::<OxiGdalPoint>() == 24); // 3 * f64
197 assert!(size_of::<OxiGdalTileCoord>() == 12); // 3 * i32
198 assert!(size_of::<OxiGdalEnhanceParams>() == 32); // 4 * f64
199 }
200
201 #[test]
202 fn test_default_values() {
203 let enhance = OxiGdalEnhanceParams::default();
204 assert_eq!(enhance.brightness, 1.0);
205 assert_eq!(enhance.contrast, 1.0);
206 assert_eq!(enhance.saturation, 1.0);
207 assert_eq!(enhance.gamma, 1.0);
208
209 let resampling = OxiGdalResampling::default();
210 assert_eq!(resampling, OxiGdalResampling::Bilinear);
211 }
212}