Skip to main content

apple_cf/cf/
primitives.rs

1//! Core Foundation primitive value wrappers.
2//!
3#![allow(clippy::missing_panics_doc)]
4
5//! ```rust
6//! use apple_cf::cf::{CFData, CFDate, CFNumber, CFString, CFUUID};
7//!
8//! let string = CFString::new("hello");
9//! let number = CFNumber::from_i64(42);
10//! let data = CFData::from_bytes([1, 2, 3, 4]);
11//! let uuid = CFUUID::new();
12//!
13//! assert_eq!(string.to_string(), "hello");
14//! assert_eq!(number.to_i64(), Some(42));
15//! assert_eq!(data.to_vec(), vec![1, 2, 3, 4]);
16//! assert_eq!(uuid.bytes().len(), 16);
17//!
18//! let now = CFDate::now();
19//! assert!(now.to_system_time().is_some());
20//! ```
21
22use super::base::{impl_cf_type_wrapper, CFType};
23use crate::ffi;
24use std::ffi::CString;
25use std::fmt;
26use std::time::{Duration, SystemTime, UNIX_EPOCH};
27
28const CF_ABSOLUTE_TIME_INTERVAL_SINCE_1970: f64 = 978_307_200.0;
29
30fn to_cstring(value: &str) -> CString {
31    CString::new(value).expect("Core Foundation strings may not contain interior NUL bytes")
32}
33
34impl_cf_type_wrapper!(CFString, cf_string_get_type_id);
35impl_cf_type_wrapper!(CFNumber, cf_number_get_type_id);
36impl_cf_type_wrapper!(CFData, cf_data_get_type_id);
37impl_cf_type_wrapper!(CFDate, cf_date_get_type_id);
38impl_cf_type_wrapper!(CFUUID, cf_uuid_get_type_id);
39impl_cf_type_wrapper!(CFError, cf_error_get_type_id);
40
41impl CFString {
42    /// Create a UTF-8 `CFString`.
43    #[must_use]
44    pub fn new(value: &str) -> Self {
45        let value = to_cstring(value);
46        let ptr = unsafe { ffi::cf_string_create_with_cstring(value.as_ptr()) };
47        Self::from_raw(ptr).expect("CFStringCreateWithCString returned NULL")
48    }
49
50    /// Number of Unicode scalar values in the string.
51    #[must_use]
52    pub fn len(&self) -> usize {
53        unsafe { ffi::cf_string_get_length(self.as_ptr()) }
54    }
55
56    /// Whether the string is empty.
57    #[must_use]
58    pub fn is_empty(&self) -> bool {
59        self.len() == 0
60    }
61
62    /// Copy the string into a Rust `String`.
63    #[must_use]
64    pub fn to_string_lossy(&self) -> String {
65        let ptr = unsafe { ffi::cf_string_copy_cstring(self.as_ptr()) };
66        if ptr.is_null() {
67            return String::new();
68        }
69        let string = unsafe { std::ffi::CStr::from_ptr(ptr) }
70            .to_string_lossy()
71            .into_owned();
72        unsafe { ffi::acf_free_string(ptr) };
73        string
74    }
75}
76
77impl Default for CFString {
78    fn default() -> Self {
79        Self::new("")
80    }
81}
82
83impl fmt::Display for CFString {
84    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85        f.write_str(&self.to_string_lossy())
86    }
87}
88
89impl CFNumber {
90    /// Create an integer number.
91    #[must_use]
92    pub fn from_i64(value: i64) -> Self {
93        let ptr = unsafe { ffi::cf_number_create_i64(value) };
94        Self::from_raw(ptr).expect("CFNumberCreate returned NULL")
95    }
96
97    /// Create an unsigned integer number.
98    #[must_use]
99    pub fn from_u64(value: u64) -> Self {
100        let ptr = unsafe { ffi::cf_number_create_u64(value) };
101        Self::from_raw(ptr).expect("CFNumberCreate returned NULL")
102    }
103
104    /// Create a floating-point number.
105    #[must_use]
106    pub fn from_f64(value: f64) -> Self {
107        let ptr = unsafe { ffi::cf_number_create_f64(value) };
108        Self::from_raw(ptr).expect("CFNumberCreate returned NULL")
109    }
110
111    /// Convert to `i64` if representable.
112    #[must_use]
113    pub fn to_i64(&self) -> Option<i64> {
114        let mut out = 0_i64;
115        let ok = unsafe { ffi::cf_number_get_i64(self.as_ptr(), &mut out) };
116        ok.then_some(out)
117    }
118
119    /// Convert to `u64` if representable.
120    #[must_use]
121    pub fn to_u64(&self) -> Option<u64> {
122        let mut out = 0_u64;
123        let ok = unsafe { ffi::cf_number_get_u64(self.as_ptr(), &mut out) };
124        ok.then_some(out)
125    }
126
127    /// Convert to `f64` if representable.
128    #[must_use]
129    pub fn to_f64(&self) -> Option<f64> {
130        let mut out = 0.0_f64;
131        let ok = unsafe { ffi::cf_number_get_f64(self.as_ptr(), &mut out) };
132        ok.then_some(out)
133    }
134
135    /// Whether the number was created from a floating-point representation.
136    #[must_use]
137    pub fn is_float_type(&self) -> bool {
138        unsafe { ffi::cf_number_is_float_type(self.as_ptr()) }
139    }
140}
141
142impl From<i64> for CFNumber {
143    fn from(value: i64) -> Self {
144        Self::from_i64(value)
145    }
146}
147
148impl From<u64> for CFNumber {
149    fn from(value: u64) -> Self {
150        Self::from_u64(value)
151    }
152}
153
154impl From<f64> for CFNumber {
155    fn from(value: f64) -> Self {
156        Self::from_f64(value)
157    }
158}
159
160impl CFData {
161    /// Copy bytes into a new `CFData`.
162    #[must_use]
163    pub fn from_bytes<B: AsRef<[u8]>>(bytes: B) -> Self {
164        let bytes = bytes.as_ref();
165        let ptr = unsafe { ffi::cf_data_create(bytes.as_ptr(), bytes.len()) };
166        Self::from_raw(ptr).expect("CFDataCreate returned NULL")
167    }
168
169    /// Number of bytes stored in the data blob.
170    #[must_use]
171    pub fn len(&self) -> usize {
172        unsafe { ffi::cf_data_get_length(self.as_ptr()) }
173    }
174
175    /// Whether the data blob is empty.
176    #[must_use]
177    pub fn is_empty(&self) -> bool {
178        self.len() == 0
179    }
180
181    /// Copy the data into a Rust-owned vector.
182    #[must_use]
183    pub fn to_vec(&self) -> Vec<u8> {
184        let mut bytes = vec![0_u8; self.len()];
185        if !bytes.is_empty() {
186            unsafe { ffi::cf_data_copy_bytes(self.as_ptr(), bytes.as_mut_ptr()) };
187        }
188        bytes
189    }
190}
191
192impl CFDate {
193    /// Create a date from Core Foundation absolute time (seconds since 2001-01-01 00:00:00 UTC).
194    #[must_use]
195    pub fn from_absolute_time(absolute_time: f64) -> Self {
196        let ptr = unsafe { ffi::cf_date_create(absolute_time) };
197        Self::from_raw(ptr).expect("CFDateCreate returned NULL")
198    }
199
200    /// Current wall-clock time.
201    #[must_use]
202    pub fn now() -> Self {
203        Self::from_system_time(SystemTime::now())
204    }
205
206    /// Convert from a Rust `SystemTime`.
207    #[must_use]
208    pub fn from_system_time(time: SystemTime) -> Self {
209        let unix_seconds = match time.duration_since(UNIX_EPOCH) {
210            Ok(duration) => duration.as_secs_f64(),
211            Err(err) => -err.duration().as_secs_f64(),
212        };
213        let absolute = unix_seconds - CF_ABSOLUTE_TIME_INTERVAL_SINCE_1970;
214        Self::from_absolute_time(absolute)
215    }
216
217    /// Core Foundation absolute time.
218    #[must_use]
219    pub fn absolute_time(&self) -> f64 {
220        unsafe { ffi::cf_date_get_absolute_time(self.as_ptr()) }
221    }
222
223    /// Convert to `SystemTime` when representable.
224    #[must_use]
225    pub fn to_system_time(&self) -> Option<SystemTime> {
226        let unix_seconds = self.absolute_time() + CF_ABSOLUTE_TIME_INTERVAL_SINCE_1970;
227        if unix_seconds.is_nan() || !unix_seconds.is_finite() {
228            return None;
229        }
230        if unix_seconds >= 0.0 {
231            Some(UNIX_EPOCH + Duration::from_secs_f64(unix_seconds))
232        } else {
233            Some(UNIX_EPOCH - Duration::from_secs_f64(-unix_seconds))
234        }
235    }
236}
237
238impl CFUUID {
239    /// Generate a new random UUID.
240    #[must_use]
241    pub fn new() -> Self {
242        let ptr = unsafe { ffi::cf_uuid_create() };
243        Self::from_raw(ptr).expect("CFUUIDCreate returned NULL")
244    }
245
246    /// Parse a UUID string.
247    #[must_use]
248    pub fn parse_str(value: &str) -> Option<Self> {
249        let value = to_cstring(value);
250        let ptr = unsafe { ffi::cf_uuid_create_from_string(value.as_ptr()) };
251        Self::from_raw(ptr)
252    }
253
254    /// Canonical textual representation.
255    #[must_use]
256    pub fn string(&self) -> CFString {
257        let ptr = unsafe { ffi::cf_uuid_copy_string(self.as_ptr()) };
258        CFString::from_raw(ptr).expect("CFUUIDCreateString returned NULL")
259    }
260
261    /// UUID bytes in RFC-4122 order.
262    #[must_use]
263    pub fn bytes(&self) -> [u8; 16] {
264        let mut bytes = [0_u8; 16];
265        unsafe { ffi::cf_uuid_get_bytes(self.as_ptr(), bytes.as_mut_ptr()) };
266        bytes
267    }
268}
269
270impl Default for CFUUID {
271    fn default() -> Self {
272        Self::new()
273    }
274}
275
276impl fmt::Display for CFUUID {
277    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
278        fmt::Display::fmt(&self.string(), f)
279    }
280}
281
282impl CFError {
283    /// Create a Core Foundation error object.
284    #[must_use]
285    pub fn new(domain: &CFString, code: i64, description: Option<&str>) -> Self {
286        let description = description.map(to_cstring);
287        let description_ptr = description
288            .as_ref()
289            .map_or(std::ptr::null(), |s| s.as_ptr());
290        let ptr = unsafe { ffi::cf_error_create(domain.as_ptr(), code, description_ptr) };
291        Self::from_raw(ptr).expect("CFErrorCreate returned NULL")
292    }
293
294    /// Error domain.
295    #[must_use]
296    pub fn domain(&self) -> CFString {
297        let ptr = unsafe { ffi::cf_error_get_domain(self.as_ptr()) };
298        CFString::from_raw(ptr).expect("CFErrorGetDomain returned NULL")
299    }
300
301    /// Numeric error code.
302    #[must_use]
303    pub fn code(&self) -> i64 {
304        unsafe { ffi::cf_error_get_code(self.as_ptr()) }
305    }
306
307    /// Localized description if present.
308    #[must_use]
309    pub fn description_string(&self) -> Option<CFString> {
310        let ptr = unsafe { ffi::cf_error_copy_description(self.as_ptr()) };
311        CFString::from_raw(ptr)
312    }
313
314    /// Failure reason if present.
315    #[must_use]
316    pub fn failure_reason(&self) -> Option<CFString> {
317        let ptr = unsafe { ffi::cf_error_copy_failure_reason(self.as_ptr()) };
318        CFString::from_raw(ptr)
319    }
320}
321
322impl fmt::Display for CFError {
323    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
324        if let Some(description) = self.description_string() {
325            write!(f, "{} ({})", description, self.code())
326        } else {
327            write!(f, "{} ({})", self.domain(), self.code())
328        }
329    }
330}
331
332impl std::error::Error for CFError {}
333
334impl From<CFString> for CFType {
335    fn from(value: CFString) -> Self {
336        value.into_cf_type()
337    }
338}
339
340impl From<CFNumber> for CFType {
341    fn from(value: CFNumber) -> Self {
342        value.into_cf_type()
343    }
344}
345
346impl From<CFData> for CFType {
347    fn from(value: CFData) -> Self {
348        value.into_cf_type()
349    }
350}
351
352impl From<CFDate> for CFType {
353    fn from(value: CFDate) -> Self {
354        value.into_cf_type()
355    }
356}
357
358impl From<CFUUID> for CFType {
359    fn from(value: CFUUID) -> Self {
360        value.into_cf_type()
361    }
362}