OXYGENHUB
# 6f06d9f912b4 oxygen / vendor / crc-fast / src / ffi.rs
32.2 KiB · Rust · last changed Strata: the cloud stores send through ureq, so every release target links 1 month ago source blame history raw
1
// Copyright 2025 Don MacAskill. Licensed under MIT or Apache-2.0.
2
3
//! FFI bindings for the Rust library
4
//!
5
//! This module provides a C-compatible interface for the Rust library, allowing
6
//! C programs to use the library's functionality.
7
8
#![cfg(all(
9
    feature = "ffi",
10
    any(target_arch = "aarch64", target_arch = "x86_64", target_arch = "x86")
11
))]
12
13
use crate::CrcAlgorithm;
14
use crate::CrcParams;
15
use crate::{get_calculator_target, Digest};
16
use std::collections::HashMap;
17
use std::collections::HashSet;
18
use std::ffi::CStr;
19
use std::os::raw::c_char;
20
use std::slice;
21
use std::sync::{Mutex, OnceLock};
22
23
static STRING_CACHE: OnceLock<Mutex<HashSet<&'static str>>> = OnceLock::new();
24
25
/// Error codes for FFI operations
26
#[repr(C)]
27
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28
pub enum CrcFastError {
29
    /// Operation completed successfully
30
    Success = 0,
31
    /// Lock was poisoned (thread panicked while holding lock)
32
    LockPoisoned = 1,
33
    /// Null pointer was passed where non-null required
34
    NullPointer = 2,
35
    /// Invalid key count for CRC parameters
36
    InvalidKeyCount = 3,
37
    /// Unsupported CRC width (must be 32 or 64)
38
    UnsupportedWidth = 4,
39
    /// Invalid UTF-8 string
40
    InvalidUtf8 = 5,
41
    /// File I/O error
42
    IoError = 6,
43
    /// Internal string conversion error
44
    StringConversionError = 7,
45
}
46
47
impl CrcFastError {
48
    /// Returns a static string describing the error
49
    fn message(&self) -> &'static str {
50
        match self {
51
            CrcFastError::Success => "Operation completed successfully",
52
            CrcFastError::LockPoisoned => "Lock was poisoned (thread panicked while holding lock)",
53
            CrcFastError::NullPointer => "Null pointer was passed where non-null required",
54
            CrcFastError::InvalidKeyCount => "Invalid key count for CRC parameters",
55
            CrcFastError::UnsupportedWidth => "Unsupported CRC width (must be 32 or 64)",
56
            CrcFastError::InvalidUtf8 => "Invalid UTF-8 string",
57
            CrcFastError::IoError => "File I/O error",
58
            CrcFastError::StringConversionError => "Internal string conversion error",
59
        }
60
    }
61
}
62
63
// Thread-local storage for the last error that occurred
64
thread_local! {
65
    static LAST_ERROR: std::cell::Cell<CrcFastError> = const { std::cell::Cell::new(CrcFastError::Success) };
66
}
67
68
/// Sets the thread-local last error
69
fn set_last_error(error: CrcFastError) {
70
    LAST_ERROR.with(|e| e.set(error));
71
}
72
73
/// Clears the thread-local last error (sets it to Success)
74
fn clear_last_error() {
75
    LAST_ERROR.with(|e| e.set(CrcFastError::Success));
76
}
77
78
// Global storage for stable key pointers to ensure they remain valid across FFI boundary
79
static STABLE_KEY_STORAGE: OnceLock<Mutex<HashMap<u64, Box<[u64]>>>> = OnceLock::new();
80
81
/// Creates a stable pointer to the keys for FFI usage.
82
/// The keys are stored in global memory to ensure the pointer remains valid.
83
/// Returns (pointer, count) on success, or (null, 0) on error.
84
/// Sets CrcFastError::LockPoisoned on lock failure.
85
fn create_stable_key_pointer(keys: &crate::CrcKeysStorage) -> (*const u64, u32) {
86
    let storage = STABLE_KEY_STORAGE.get_or_init(|| Mutex::new(HashMap::new()));
87
88
    // Create a unique hash for this key set to avoid duplicates
89
    let key_hash = match keys {
90
        crate::CrcKeysStorage::KeysFold256(keys) => {
91
            let mut hasher = std::collections::hash_map::DefaultHasher::new();
92
            use std::hash::{Hash, Hasher};
93
            keys.hash(&mut hasher);
94
            hasher.finish()
95
        }
96
        crate::CrcKeysStorage::KeysFutureTest(keys) => {
97
            let mut hasher = std::collections::hash_map::DefaultHasher::new();
98
            use std::hash::{Hash, Hasher};
99
            keys.hash(&mut hasher);
100
            hasher.finish()
101
        }
102
    };
103
104
    let mut storage_map = match storage.lock() {
105
        Ok(guard) => guard,
106
        Err(_) => {
107
            set_last_error(CrcFastError::LockPoisoned);
108
            return (std::ptr::null(), 0);
109
        }
110
    };
111
112
    // Check if we already have this key set stored
113
    if let Some(stored_keys) = storage_map.get(&key_hash) {
114
        return (stored_keys.as_ptr(), stored_keys.len() as u32);
115
    }
116
117
    // Store the keys in stable memory
118
    let key_vec: Vec<u64> = match keys {
119
        crate::CrcKeysStorage::KeysFold256(keys) => keys.to_vec(),
120
        crate::CrcKeysStorage::KeysFutureTest(keys) => keys.to_vec(),
121
    };
122
123
    let boxed_keys = key_vec.into_boxed_slice();
124
    let count = boxed_keys.len() as u32;
125
126
    storage_map.insert(key_hash, boxed_keys);
127
128
    let ptr = storage_map.get(&key_hash).expect("just inserted").as_ptr();
129
130
    (ptr, count)
131
}
132
133
/// A handle to the Digest object
134
#[repr(C)]
135
pub struct CrcFastDigestHandle(*mut Digest);
136
137
/// The supported CRC algorithms
138
#[repr(C)]
139
#[derive(Clone, Copy)]
140
pub enum CrcFastAlgorithm {
141
    // CrcCustom works with any supported widths (16, 32, 64)
142
    CrcCustom,
143
    Crc16Arc,
144
    Crc16Cdma2000,
145
    Crc16Cms,
146
    Crc16Dds110,
147
    Crc16DectR,
148
    Crc16DectX,
149
    Crc16Dnp,
150
    Crc16En13757,
151
    Crc16Genibus,
152
    Crc16Gsm,
153
    Crc16Ibm3740,
154
    Crc16IbmSdlc,
155
    Crc16IsoIec144433A,
156
    Crc16Kermit,
157
    Crc16Lj1200,
158
    Crc16M17,
159
    Crc16MaximDow,
160
    Crc16Mcrf4xx,
161
    Crc16Modbus,
162
    Crc16Nrsc5,
163
    Crc16OpensafetyA,
164
    Crc16OpensafetyB,
165
    Crc16Profibus,
166
    Crc16Riello,
167
    Crc16SpiFujitsu,
168
    Crc16T10Dif,
169
    Crc16Teledisk,
170
    Crc16Tms37157,
171
    Crc16Umts,
172
    Crc16Usb,
173
    Crc16Xmodem,
174
    Crc32Aixm,
175
    Crc32Autosar,
176
    Crc32Base91D,
177
    Crc32Bzip2,
178
    Crc32CdRomEdc,
179
    Crc32Cksum,
180
    Crc32Custom,
181
    Crc32Iscsi,
182
    Crc32IsoHdlc,
183
    Crc32Jamcrc,
184
    Crc32Mef,
185
    Crc32Mpeg2,
186
    Crc32Xfer,
187
    Crc64Custom,
188
    Crc64Ecma182,
189
    Crc64GoIso,
190
    Crc64Ms,
191
    Crc64Nvme,
192
    Crc64Redis,
193
    Crc64We,
194
    Crc64Xz,
195
}
196
197
// Convert from FFI enum to internal enum
198
#[allow(deprecated)]
199
impl From<CrcFastAlgorithm> for CrcAlgorithm {
200
    fn from(value: CrcFastAlgorithm) -> Self {
201
        match value {
202
            CrcFastAlgorithm::Crc16Arc => CrcAlgorithm::Crc16Arc,
203
            CrcFastAlgorithm::Crc16Cdma2000 => CrcAlgorithm::Crc16Cdma2000,
204
            CrcFastAlgorithm::Crc16Cms => CrcAlgorithm::Crc16Cms,
205
            CrcFastAlgorithm::Crc16Dds110 => CrcAlgorithm::Crc16Dds110,
206
            CrcFastAlgorithm::Crc16DectR => CrcAlgorithm::Crc16DectR,
207
            CrcFastAlgorithm::Crc16DectX => CrcAlgorithm::Crc16DectX,
208
            CrcFastAlgorithm::Crc16Dnp => CrcAlgorithm::Crc16Dnp,
209
            CrcFastAlgorithm::Crc16En13757 => CrcAlgorithm::Crc16En13757,
210
            CrcFastAlgorithm::Crc16Genibus => CrcAlgorithm::Crc16Genibus,
211
            CrcFastAlgorithm::Crc16Gsm => CrcAlgorithm::Crc16Gsm,
212
            CrcFastAlgorithm::Crc16Ibm3740 => CrcAlgorithm::Crc16Ibm3740,
213
            CrcFastAlgorithm::Crc16IbmSdlc => CrcAlgorithm::Crc16IbmSdlc,
214
            CrcFastAlgorithm::Crc16IsoIec144433A => CrcAlgorithm::Crc16IsoIec144433A,
215
            CrcFastAlgorithm::Crc16Kermit => CrcAlgorithm::Crc16Kermit,
216
            CrcFastAlgorithm::Crc16Lj1200 => CrcAlgorithm::Crc16Lj1200,
217
            CrcFastAlgorithm::Crc16M17 => CrcAlgorithm::Crc16M17,
218
            CrcFastAlgorithm::Crc16MaximDow => CrcAlgorithm::Crc16MaximDow,
219
            CrcFastAlgorithm::Crc16Mcrf4xx => CrcAlgorithm::Crc16Mcrf4xx,
220
            CrcFastAlgorithm::Crc16Modbus => CrcAlgorithm::Crc16Modbus,
221
            CrcFastAlgorithm::Crc16Nrsc5 => CrcAlgorithm::Crc16Nrsc5,
222
            CrcFastAlgorithm::Crc16OpensafetyA => CrcAlgorithm::Crc16OpensafetyA,
223
            CrcFastAlgorithm::Crc16OpensafetyB => CrcAlgorithm::Crc16OpensafetyB,
224
            CrcFastAlgorithm::Crc16Profibus => CrcAlgorithm::Crc16Profibus,
225
            CrcFastAlgorithm::Crc16Riello => CrcAlgorithm::Crc16Riello,
226
            CrcFastAlgorithm::Crc16SpiFujitsu => CrcAlgorithm::Crc16SpiFujitsu,
227
            CrcFastAlgorithm::Crc16T10Dif => CrcAlgorithm::Crc16T10Dif,
228
            CrcFastAlgorithm::Crc16Teledisk => CrcAlgorithm::Crc16Teledisk,
229
            CrcFastAlgorithm::Crc16Tms37157 => CrcAlgorithm::Crc16Tms37157,
230
            CrcFastAlgorithm::Crc16Umts => CrcAlgorithm::Crc16Umts,
231
            CrcFastAlgorithm::Crc16Usb => CrcAlgorithm::Crc16Usb,
232
            CrcFastAlgorithm::Crc16Xmodem => CrcAlgorithm::Crc16Xmodem,
233
            CrcFastAlgorithm::Crc32Aixm => CrcAlgorithm::Crc32Aixm,
234
            CrcFastAlgorithm::Crc32Autosar => CrcAlgorithm::Crc32Autosar,
235
            CrcFastAlgorithm::Crc32Base91D => CrcAlgorithm::Crc32Base91D,
236
            CrcFastAlgorithm::Crc32Bzip2 => CrcAlgorithm::Crc32Bzip2,
237
            CrcFastAlgorithm::Crc32CdRomEdc => CrcAlgorithm::Crc32CdRomEdc,
238
            CrcFastAlgorithm::Crc32Cksum => CrcAlgorithm::Crc32Cksum,
239
            CrcFastAlgorithm::Crc32Custom => CrcAlgorithm::Crc32Custom,
240
            CrcFastAlgorithm::Crc32Iscsi => CrcAlgorithm::Crc32Iscsi,
241
            CrcFastAlgorithm::Crc32IsoHdlc => CrcAlgorithm::Crc32IsoHdlc,
242
            CrcFastAlgorithm::Crc32Jamcrc => CrcAlgorithm::Crc32Jamcrc,
243
            CrcFastAlgorithm::Crc32Mef => CrcAlgorithm::Crc32Mef,
244
            CrcFastAlgorithm::Crc32Mpeg2 => CrcAlgorithm::Crc32Mpeg2,
245
            CrcFastAlgorithm::Crc32Xfer => CrcAlgorithm::Crc32Xfer,
246
            CrcFastAlgorithm::CrcCustom => CrcAlgorithm::CrcCustom,
247
            CrcFastAlgorithm::Crc64Custom => CrcAlgorithm::Crc64Custom,
248
            CrcFastAlgorithm::Crc64Ecma182 => CrcAlgorithm::Crc64Ecma182,
249
            CrcFastAlgorithm::Crc64GoIso => CrcAlgorithm::Crc64GoIso,
250
            CrcFastAlgorithm::Crc64Ms => CrcAlgorithm::Crc64Ms,
251
            CrcFastAlgorithm::Crc64Nvme => CrcAlgorithm::Crc64Nvme,
252
            CrcFastAlgorithm::Crc64Redis => CrcAlgorithm::Crc64Redis,
253
            CrcFastAlgorithm::Crc64We => CrcAlgorithm::Crc64We,
254
            CrcFastAlgorithm::Crc64Xz => CrcAlgorithm::Crc64Xz,
255
        }
256
    }
257
}
258
259
/// Gets the last error that occurred in the current thread
260
/// Returns CrcFastError::Success if no error has occurred
261
#[no_mangle]
262
pub extern "C" fn crc_fast_get_last_error() -> CrcFastError {
263
    LAST_ERROR.with(|e| e.get())
264
}
265
266
/// Clears the last error for the current thread
267
#[no_mangle]
268
pub extern "C" fn crc_fast_clear_error() {
269
    clear_last_error();
270
}
271
272
/// Gets a human-readable error message for the given error code
273
/// Returns a pointer to a static string (do not free)
274
#[no_mangle]
275
pub extern "C" fn crc_fast_error_message(error: CrcFastError) -> *const c_char {
276
    let message = error.message();
277
    // These are static strings, so we can safely return them as C strings
278
    // The strings are guaranteed to be valid UTF-8 and null-terminated
279
    match std::ffi::CString::new(message) {
280
        Ok(c_str) => {
281
            // Leak the string so it remains valid for the lifetime of the program
282
            // This is safe because error messages are static and small
283
            Box::leak(Box::new(c_str)).as_ptr()
284
        }
285
        Err(_) => std::ptr::null(),
286
    }
287
}
288
289
/// Custom CRC parameters
290
#[repr(C)]
291
pub struct CrcFastParams {
292
    pub algorithm: CrcFastAlgorithm,
293
    pub width: u8,
294
    pub poly: u64,
295
    pub init: u64,
296
    pub refin: bool,
297
    pub refout: bool,
298
    pub xorout: u64,
299
    pub check: u64,
300
    pub key_count: u32,
301
    pub keys: *const u64,
302
}
303
304
/// Fallible conversion from FFI struct to internal struct
305
/// Returns None if the parameters are invalid (unsupported key count)
306
fn try_params_from_ffi(value: &CrcFastParams) -> Option<CrcParams> {
307
    // Validate key pointer
308
    if value.keys.is_null() {
309
        return None;
310
    }
311
312
    // Convert C array back to appropriate CrcKeysStorage
313
    let keys = unsafe { std::slice::from_raw_parts(value.keys, value.key_count as usize) };
314
315
    let storage = match value.key_count {
316
        23 => match keys.try_into() {
317
            Ok(arr) => crate::CrcKeysStorage::from_keys_fold_256(arr),
318
            Err(_) => return None,
319
        },
320
        25 => match keys.try_into() {
321
            Ok(arr) => crate::CrcKeysStorage::from_keys_fold_future_test(arr),
322
            Err(_) => return None,
323
        },
324
        _ => return None, // Unsupported key count
325
    };
326
327
    // For reflected CRC-16, bit-reverse the init value for the SIMD algorithm
328
    let init_algorithm = if value.width == 16 && value.refin {
329
        (value.init as u16).reverse_bits() as u64
330
    } else {
331
        value.init
332
    };
333
334
    Some(CrcParams {
335
        algorithm: value.algorithm.into(),
336
        name: "custom", // C interface doesn't need the name field
337
        width: value.width,
338
        poly: value.poly,
339
        init: value.init,
340
        init_algorithm,
341
        refin: value.refin,
342
        refout: value.refout,
343
        xorout: value.xorout,
344
        check: value.check,
345
        keys: storage,
346
    })
347
}
348
349
// Convert from FFI struct to internal struct (legacy, may panic)
350
// For backwards compatibility, but prefer try_params_from_ffi
351
impl From<CrcFastParams> for CrcParams {
352
    fn from(value: CrcFastParams) -> Self {
353
        try_params_from_ffi(&value)
354
            .expect("Invalid CRC parameters: unsupported key count or null pointer")
355
    }
356
}
357
358
// Convert from internal struct to FFI struct
359
#[allow(deprecated)]
360
impl From<CrcParams> for CrcFastParams {
361
    fn from(params: CrcParams) -> Self {
362
        // Create stable key pointer for FFI usage
363
        let (keys_ptr, key_count) = create_stable_key_pointer(&params.keys);
364
365
        CrcFastParams {
366
            algorithm: match params.algorithm {
367
                CrcAlgorithm::Crc16Arc => CrcFastAlgorithm::Crc16Arc,
368
                CrcAlgorithm::Crc16Cdma2000 => CrcFastAlgorithm::Crc16Cdma2000,
369
                CrcAlgorithm::Crc16Cms => CrcFastAlgorithm::Crc16Cms,
370
                CrcAlgorithm::Crc16Dds110 => CrcFastAlgorithm::Crc16Dds110,
371
                CrcAlgorithm::Crc16DectR => CrcFastAlgorithm::Crc16DectR,
372
                CrcAlgorithm::Crc16DectX => CrcFastAlgorithm::Crc16DectX,
373
                CrcAlgorithm::Crc16Dnp => CrcFastAlgorithm::Crc16Dnp,
374
                CrcAlgorithm::Crc16En13757 => CrcFastAlgorithm::Crc16En13757,
375
                CrcAlgorithm::Crc16Genibus => CrcFastAlgorithm::Crc16Genibus,
376
                CrcAlgorithm::Crc16Gsm => CrcFastAlgorithm::Crc16Gsm,
377
                CrcAlgorithm::Crc16Ibm3740 => CrcFastAlgorithm::Crc16Ibm3740,
378
                CrcAlgorithm::Crc16IbmSdlc => CrcFastAlgorithm::Crc16IbmSdlc,
379
                CrcAlgorithm::Crc16IsoIec144433A => CrcFastAlgorithm::Crc16IsoIec144433A,
380
                CrcAlgorithm::Crc16Kermit => CrcFastAlgorithm::Crc16Kermit,
381
                CrcAlgorithm::Crc16Lj1200 => CrcFastAlgorithm::Crc16Lj1200,
382
                CrcAlgorithm::Crc16M17 => CrcFastAlgorithm::Crc16M17,
383
                CrcAlgorithm::Crc16MaximDow => CrcFastAlgorithm::Crc16MaximDow,
384
                CrcAlgorithm::Crc16Mcrf4xx => CrcFastAlgorithm::Crc16Mcrf4xx,
385
                CrcAlgorithm::Crc16Modbus => CrcFastAlgorithm::Crc16Modbus,
386
                CrcAlgorithm::Crc16Nrsc5 => CrcFastAlgorithm::Crc16Nrsc5,
387
                CrcAlgorithm::Crc16OpensafetyA => CrcFastAlgorithm::Crc16OpensafetyA,
388
                CrcAlgorithm::Crc16OpensafetyB => CrcFastAlgorithm::Crc16OpensafetyB,
389
                CrcAlgorithm::Crc16Profibus => CrcFastAlgorithm::Crc16Profibus,
390
                CrcAlgorithm::Crc16Riello => CrcFastAlgorithm::Crc16Riello,
391
                CrcAlgorithm::Crc16SpiFujitsu => CrcFastAlgorithm::Crc16SpiFujitsu,
392
                CrcAlgorithm::Crc16T10Dif => CrcFastAlgorithm::Crc16T10Dif,
393
                CrcAlgorithm::Crc16Teledisk => CrcFastAlgorithm::Crc16Teledisk,
394
                CrcAlgorithm::Crc16Tms37157 => CrcFastAlgorithm::Crc16Tms37157,
395
                CrcAlgorithm::Crc16Umts => CrcFastAlgorithm::Crc16Umts,
396
                CrcAlgorithm::Crc16Usb => CrcFastAlgorithm::Crc16Usb,
397
                CrcAlgorithm::Crc16Xmodem => CrcFastAlgorithm::Crc16Xmodem,
398
                CrcAlgorithm::Crc32Aixm => CrcFastAlgorithm::Crc32Aixm,
399
                CrcAlgorithm::Crc32Autosar => CrcFastAlgorithm::Crc32Autosar,
400
                CrcAlgorithm::Crc32Base91D => CrcFastAlgorithm::Crc32Base91D,
401
                CrcAlgorithm::Crc32Bzip2 => CrcFastAlgorithm::Crc32Bzip2,
402
                CrcAlgorithm::Crc32CdRomEdc => CrcFastAlgorithm::Crc32CdRomEdc,
403
                CrcAlgorithm::Crc32Cksum => CrcFastAlgorithm::Crc32Cksum,
404
                CrcAlgorithm::Crc32Custom => CrcFastAlgorithm::Crc32Custom,
405
                CrcAlgorithm::Crc32Iscsi => CrcFastAlgorithm::Crc32Iscsi,
406
                CrcAlgorithm::Crc32IsoHdlc => CrcFastAlgorithm::Crc32IsoHdlc,
407
                CrcAlgorithm::Crc32Jamcrc => CrcFastAlgorithm::Crc32Jamcrc,
408
                CrcAlgorithm::Crc32Mef => CrcFastAlgorithm::Crc32Mef,
409
                CrcAlgorithm::Crc32Mpeg2 => CrcFastAlgorithm::Crc32Mpeg2,
410
                CrcAlgorithm::Crc32Xfer => CrcFastAlgorithm::Crc32Xfer,
411
                CrcAlgorithm::CrcCustom => CrcFastAlgorithm::CrcCustom,
412
                CrcAlgorithm::Crc64Custom => CrcFastAlgorithm::Crc64Custom,
413
                CrcAlgorithm::Crc64Ecma182 => CrcFastAlgorithm::Crc64Ecma182,
414
                CrcAlgorithm::Crc64GoIso => CrcFastAlgorithm::Crc64GoIso,
415
                CrcAlgorithm::Crc64Ms => CrcFastAlgorithm::Crc64Ms,
416
                CrcAlgorithm::Crc64Nvme => CrcFastAlgorithm::Crc64Nvme,
417
                CrcAlgorithm::Crc64Redis => CrcFastAlgorithm::Crc64Redis,
418
                CrcAlgorithm::Crc64We => CrcFastAlgorithm::Crc64We,
419
                CrcAlgorithm::Crc64Xz => CrcFastAlgorithm::Crc64Xz,
420
            },
421
            width: params.width,
422
            poly: params.poly,
423
            init: params.init,
424
            refin: params.refin,
425
            refout: params.refout,
426
            xorout: params.xorout,
427
            check: params.check,
428
            key_count,
429
            keys: keys_ptr,
430
        }
431
    }
432
}
433
434
/// Creates a new Digest to compute CRC checksums using algorithm
435
#[no_mangle]
436
pub extern "C" fn crc_fast_digest_new(algorithm: CrcFastAlgorithm) -> *mut CrcFastDigestHandle {
437
    clear_last_error();
438
    let digest = Box::new(Digest::new(algorithm.into()));
439
    let handle = Box::new(CrcFastDigestHandle(Box::into_raw(digest)));
440
    Box::into_raw(handle)
441
}
442
443
/// Creates a new Digest with a custom initial state
444
#[no_mangle]
445
pub extern "C" fn crc_fast_digest_new_with_init_state(
446
    algorithm: CrcFastAlgorithm,
447
    init_state: u64,
448
) -> *mut CrcFastDigestHandle {
449
    clear_last_error();
450
    let digest = Box::new(Digest::new_with_init_state(algorithm.into(), init_state));
451
    let handle = Box::new(CrcFastDigestHandle(Box::into_raw(digest)));
452
    Box::into_raw(handle)
453
}
454
455
/// Creates a new Digest to compute CRC checksums using custom parameters
456
/// Returns NULL if parameters are invalid (invalid key count or null pointer)
457
/// Call crc_fast_get_last_error() to get the specific error code
458
#[no_mangle]
459
pub extern "C" fn crc_fast_digest_new_with_params(
460
    params: CrcFastParams,
461
) -> *mut CrcFastDigestHandle {
462
    clear_last_error();
463
    match try_params_from_ffi(&params) {
464
        Some(crc_params) => {
465
            let digest = Box::new(Digest::new_with_params(crc_params));
466
            let handle = Box::new(CrcFastDigestHandle(Box::into_raw(digest)));
467
            Box::into_raw(handle)
468
        }
469
        None => {
470
            // Set appropriate error based on the failure
471
            if params.keys.is_null() {
472
                set_last_error(CrcFastError::NullPointer);
473
            } else {
474
                set_last_error(CrcFastError::InvalidKeyCount);
475
            }
476
            std::ptr::null_mut()
477
        }
478
    }
479
}
480
481
/// Updates the Digest with data
482
#[no_mangle]
483
pub extern "C" fn crc_fast_digest_update(
484
    handle: *mut CrcFastDigestHandle,
485
    data: *const c_char,
486
    len: usize,
487
) {
488
    if handle.is_null() {
489
        set_last_error(CrcFastError::NullPointer);
490
        return;
491
    }
492
    if data.is_null() {
493
        set_last_error(CrcFastError::NullPointer);
494
        return;
495
    }
496
497
    clear_last_error();
498
    unsafe {
499
        let digest = &mut *(*handle).0;
500
501
        #[allow(clippy::unnecessary_cast)]
502
        let bytes = slice::from_raw_parts(data as *const u8, len);
503
        digest.update(bytes);
504
    }
505
}
506
507
/// Calculates the CRC checksum for data that's been written to the Digest
508
/// Returns 0 on error (e.g. null handle)
509
#[no_mangle]
510
pub extern "C" fn crc_fast_digest_finalize(handle: *mut CrcFastDigestHandle) -> u64 {
511
    if handle.is_null() {
512
        set_last_error(CrcFastError::NullPointer);
513
        return 0;
514
    }
515
516
    clear_last_error();
517
    unsafe {
518
        let digest = &*(*handle).0;
519
        digest.finalize()
520
    }
521
}
522
523
/// Free the Digest resources without finalizing
524
#[no_mangle]
525
pub extern "C" fn crc_fast_digest_free(handle: *mut CrcFastDigestHandle) {
526
    if handle.is_null() {
527
        set_last_error(CrcFastError::NullPointer);
528
        return;
529
    }
530
531
    clear_last_error();
532
    unsafe {
533
        let handle = Box::from_raw(handle);
534
        let _ = Box::from_raw(handle.0); // This drops the digest
535
    }
536
}
537
538
/// Reset the Digest state
539
#[no_mangle]
540
pub extern "C" fn crc_fast_digest_reset(handle: *mut CrcFastDigestHandle) {
541
    if handle.is_null() {
542
        set_last_error(CrcFastError::NullPointer);
543
        return;
544
    }
545
546
    clear_last_error();
547
    unsafe {
548
        let digest = &mut *(*handle).0;
549
550
        digest.reset();
551
    }
552
}
553
554
/// Finalize and reset the Digest in one operation
555
/// Returns 0 on error (e.g. null handle)
556
#[no_mangle]
557
pub extern "C" fn crc_fast_digest_finalize_reset(handle: *mut CrcFastDigestHandle) -> u64 {
558
    if handle.is_null() {
559
        set_last_error(CrcFastError::NullPointer);
560
        return 0;
561
    }
562
563
    clear_last_error();
564
    unsafe {
565
        let digest = &mut *(*handle).0;
566
567
        digest.finalize_reset()
568
    }
569
}
570
571
/// Combine two Digest checksums
572
#[no_mangle]
573
pub extern "C" fn crc_fast_digest_combine(
574
    handle1: *mut CrcFastDigestHandle,
575
    handle2: *mut CrcFastDigestHandle,
576
) {
577
    if handle1.is_null() || handle2.is_null() {
578
        set_last_error(CrcFastError::NullPointer);
579
        return;
580
    }
581
582
    clear_last_error();
583
    unsafe {
584
        let digest1 = &mut *(*handle1).0;
585
        let digest2 = &*(*handle2).0;
586
        digest1.combine(digest2);
587
    }
588
}
589
590
/// Gets the amount of data processed by the Digest so far
591
/// Returns 0 on error (e.g. null handle)
592
#[no_mangle]
593
pub extern "C" fn crc_fast_digest_get_amount(handle: *mut CrcFastDigestHandle) -> u64 {
594
    if handle.is_null() {
595
        set_last_error(CrcFastError::NullPointer);
596
        return 0;
597
    }
598
599
    clear_last_error();
600
    unsafe {
601
        let digest = &*(*handle).0;
602
        digest.get_amount()
603
    }
604
}
605
606
/// Gets the current state of the Digest
607
/// Returns 0 on error (e.g. null handle)
608
#[no_mangle]
609
pub extern "C" fn crc_fast_digest_get_state(handle: *mut CrcFastDigestHandle) -> u64 {
610
    if handle.is_null() {
611
        set_last_error(CrcFastError::NullPointer);
612
        return 0;
613
    }
614
    clear_last_error();
615
    unsafe {
616
        let digest = &*(*handle).0;
617
        digest.get_state()
618
    }
619
}
620
621
/// Helper method to calculate a CRC checksum directly for a string using algorithm
622
/// Returns 0 on error (e.g. null data pointer)
623
#[no_mangle]
624
pub extern "C" fn crc_fast_checksum(
625
    algorithm: CrcFastAlgorithm,
626
    data: *const c_char,
627
    len: usize,
628
) -> u64 {
629
    if data.is_null() {
630
        set_last_error(CrcFastError::NullPointer);
631
        return 0;
632
    }
633
    clear_last_error();
634
    unsafe {
635
        #[allow(clippy::unnecessary_cast)]
636
        let bytes = slice::from_raw_parts(data as *const u8, len);
637
        crate::checksum(algorithm.into(), bytes)
638
    }
639
}
640
641
/// Helper method to calculate a CRC checksum directly for data using custom parameters
642
/// Returns 0 if parameters are invalid or data is null
643
/// Call crc_fast_get_last_error() to get the specific error code
644
#[no_mangle]
645
pub extern "C" fn crc_fast_checksum_with_params(
646
    params: CrcFastParams,
647
    data: *const c_char,
648
    len: usize,
649
) -> u64 {
650
    if data.is_null() {
651
        set_last_error(CrcFastError::NullPointer);
652
        return 0;
653
    }
654
    match try_params_from_ffi(&params) {
655
        Some(crc_params) => {
656
            clear_last_error();
657
            unsafe {
658
                #[allow(clippy::unnecessary_cast)]
659
                let bytes = slice::from_raw_parts(data as *const u8, len);
660
                crate::checksum_with_params(crc_params, bytes)
661
            }
662
        }
663
        None => {
664
            if params.keys.is_null() {
665
                set_last_error(CrcFastError::NullPointer);
666
            } else {
667
                set_last_error(CrcFastError::InvalidKeyCount);
668
            }
669
            0
670
        }
671
    }
672
}
673
674
/// Helper method to just calculate a CRC checksum directly for a file using algorithm
675
/// Returns 0 if path is null or file I/O fails
676
/// Call crc_fast_get_last_error() to get the specific error code
677
#[no_mangle]
678
pub extern "C" fn crc_fast_checksum_file(
679
    algorithm: CrcFastAlgorithm,
680
    path_ptr: *const u8,
681
    path_len: usize,
682
) -> u64 {
683
    if path_ptr.is_null() {
684
        set_last_error(CrcFastError::NullPointer);
685
        return 0;
686
    }
687
688
    unsafe {
689
        match crate::checksum_file(
690
            algorithm.into(),
691
            &convert_to_string(path_ptr, path_len),
692
            None,
693
        ) {
694
            Ok(result) => {
695
                clear_last_error();
696
                result
697
            }
698
            Err(_) => {
699
                set_last_error(CrcFastError::IoError);
700
                0
701
            }
702
        }
703
    }
704
}
705
706
/// Helper method to calculate a CRC checksum directly for a file using custom parameters
707
/// Returns 0 if parameters are invalid, path is null, or file I/O fails
708
/// Call crc_fast_get_last_error() to get the specific error code
709
#[no_mangle]
710
pub extern "C" fn crc_fast_checksum_file_with_params(
711
    params: CrcFastParams,
712
    path_ptr: *const u8,
713
    path_len: usize,
714
) -> u64 {
715
    if path_ptr.is_null() {
716
        set_last_error(CrcFastError::NullPointer);
717
        return 0;
718
    }
719
720
    match try_params_from_ffi(&params) {
721
        Some(crc_params) => unsafe {
722
            match crate::checksum_file_with_params(
723
                crc_params,
724
                &convert_to_string(path_ptr, path_len),
725
                None,
726
            ) {
727
                Ok(result) => {
728
                    clear_last_error();
729
                    result
730
                }
731
                Err(_) => {
732
                    set_last_error(CrcFastError::IoError);
733
                    0
734
                }
735
            }
736
        },
737
        None => {
738
            if params.keys.is_null() {
739
                set_last_error(CrcFastError::NullPointer);
740
            } else {
741
                set_last_error(CrcFastError::InvalidKeyCount);
742
            }
743
            0
744
        }
745
    }
746
}
747
748
/// Combine two CRC checksums using algorithm
749
#[no_mangle]
750
pub extern "C" fn crc_fast_checksum_combine(
751
    algorithm: CrcFastAlgorithm,
752
    checksum1: u64,
753
    checksum2: u64,
754
    checksum2_len: u64,
755
) -> u64 {
756
    clear_last_error();
757
    crate::checksum_combine(algorithm.into(), checksum1, checksum2, checksum2_len)
758
}
759
760
/// Combine two CRC checksums using custom parameters
761
/// Returns 0 if parameters are invalid
762
/// Call crc_fast_get_last_error() to get the specific error code
763
#[no_mangle]
764
pub extern "C" fn crc_fast_checksum_combine_with_params(
765
    params: CrcFastParams,
766
    checksum1: u64,
767
    checksum2: u64,
768
    checksum2_len: u64,
769
) -> u64 {
770
    match try_params_from_ffi(&params) {
771
        Some(crc_params) => {
772
            clear_last_error();
773
            crate::checksum_combine_with_params(crc_params, checksum1, checksum2, checksum2_len)
774
        }
775
        None => {
776
            if params.keys.is_null() {
777
                set_last_error(CrcFastError::NullPointer);
778
            } else {
779
                set_last_error(CrcFastError::InvalidKeyCount);
780
            }
781
            0
782
        }
783
    }
784
}
785
786
/// Returns the custom CRC parameters for a given set of Rocksoft CRC parameters
787
/// If width is not 32 or 64, sets error to UnsupportedWidth
788
#[no_mangle]
789
pub extern "C" fn crc_fast_get_custom_params(
790
    name_ptr: *const c_char,
791
    width: u8,
792
    poly: u64,
793
    init: u64,
794
    reflected: bool,
795
    xorout: u64,
796
    check: u64,
797
) -> CrcFastParams {
798
    // Validate width
799
    if width != 32 && width != 64 {
800
        set_last_error(CrcFastError::UnsupportedWidth);
801
    } else {
802
        clear_last_error();
803
    }
804
805
    let name = if name_ptr.is_null() {
806
        "custom"
807
    } else {
808
        unsafe {
809
            match CStr::from_ptr(name_ptr).to_str() {
810
                Ok(s) => s,
811
                Err(_) => {
812
                    set_last_error(CrcFastError::InvalidUtf8);
813
                    "custom"
814
                }
815
            }
816
        }
817
    };
818
819
    // Get the custom params from the library
820
    let params = CrcParams::new(
821
        get_or_leak_string(name), // ✅ Use cached leak
822
        width,
823
        poly,
824
        init,
825
        reflected,
826
        xorout,
827
        check,
828
    );
829
830
    // Create stable key pointer for FFI usage
831
    let (keys_ptr, key_count) = create_stable_key_pointer(&params.keys);
832
833
    // Convert to FFI struct - use CrcCustom for all widths since CrcParams::new now uses it
834
    CrcFastParams {
835
        algorithm: CrcFastAlgorithm::CrcCustom,
836
        width: params.width,
837
        poly: params.poly,
838
        init: params.init,
839
        refin: params.refin,
840
        refout: params.refout,
841
        xorout: params.xorout,
842
        check: params.check,
843
        key_count,
844
        keys: keys_ptr,
845
    }
846
}
847
848
/// Gets the target build properties (CPU architecture and fine-tuning parameters) for this algorithm
849
/// Returns NULL if string conversion fails
850
/// Call crc_fast_get_last_error() to get the specific error code
851
#[no_mangle]
852
pub extern "C" fn crc_fast_get_calculator_target(algorithm: CrcFastAlgorithm) -> *const c_char {
853
    let target = get_calculator_target(algorithm.into());
854
855
    match std::ffi::CString::new(target) {
856
        Ok(s) => {
857
            clear_last_error();
858
            s.into_raw()
859
        }
860
        Err(_) => {
861
            set_last_error(CrcFastError::StringConversionError);
862
            std::ptr::null_mut()
863
        }
864
    }
865
}
866
867
/// Gets the version of this library
868
/// Returns a pointer to "unknown" if version string is invalid
869
#[no_mangle]
870
pub extern "C" fn crc_fast_get_version() -> *const c_char {
871
    const VERSION: &CStr =
872
        match CStr::from_bytes_with_nul(concat!(env!("CARGO_PKG_VERSION"), "\0").as_bytes()) {
873
            Ok(version) => version,
874
            // Fallback to "unknown" if version string is malformed
875
            Err(_) => c"unknown",
876
        };
877
878
    VERSION.as_ptr()
879
}
880
881
/// Calculates the CRC-32/ISCSI checksum (commonly called "crc32c" in many, but not all,
882
/// implementations).
883
///
884
/// https://reveng.sourceforge.io/crc-catalogue/all.htm#crc.cat.crc-32-iscsi
885
///
886
/// Returns 0 on error (e.g. null data pointer)
887
#[no_mangle]
888
pub extern "C" fn crc_fast_crc32_iscsi(data: *const c_char, len: usize) -> u32 {
889
    if data.is_null() {
890
        set_last_error(CrcFastError::NullPointer);
891
        return 0;
892
    }
893
    clear_last_error();
894
    unsafe {
895
        #[allow(clippy::unnecessary_cast)]
896
        let bytes = slice::from_raw_parts(data as *const u8, len);
897
        crate::crc32_iscsi(bytes)
898
    }
899
}
900
901
/// Calculates the CRC-32/ISO-HDLC checksum (commonly called "crc32" in many, but not all,
902
/// implementations).
903
///
904
/// https://reveng.sourceforge.io/crc-catalogue/all.htm#crc.cat.crc-32-iso-hdlc
905
///
906
/// Returns 0 on error (e.g. null data pointer)
907
#[no_mangle]
908
pub extern "C" fn crc_fast_crc32_iso_hdlc(data: *const c_char, len: usize) -> u32 {
909
    if data.is_null() {
910
        set_last_error(CrcFastError::NullPointer);
911
        return 0;
912
    }
913
    clear_last_error();
914
    unsafe {
915
        #[allow(clippy::unnecessary_cast)]
916
        let bytes = slice::from_raw_parts(data as *const u8, len);
917
        crate::crc32_iso_hdlc(bytes)
918
    }
919
}
920
921
/// Calculates the CRC-64/NVME checksum.
922
///
923
/// https://reveng.sourceforge.io/crc-catalogue/all.htm#crc.cat.crc-64-nvme
924
///
925
/// Returns 0 on error (e.g. null data pointer)
926
#[no_mangle]
927
pub extern "C" fn crc_fast_crc64_nvme(data: *const c_char, len: usize) -> u64 {
928
    if data.is_null() {
929
        set_last_error(CrcFastError::NullPointer);
930
        return 0;
931
    }
932
    clear_last_error();
933
    unsafe {
934
        #[allow(clippy::unnecessary_cast)]
935
        let bytes = slice::from_raw_parts(data as *const u8, len);
936
        crate::crc64_nvme(bytes)
937
    }
938
}
939
940
unsafe fn convert_to_string(data: *const u8, len: usize) -> String {
941
    if data.is_null() {
942
        return String::new();
943
    }
944
945
    // Safely construct string slice from raw parts
946
    match std::str::from_utf8(slice::from_raw_parts(data, len)) {
947
        Ok(s) => s.to_string(),
948
        Err(_) => String::new(), // Return empty string for invalid UTF-8
949
    }
950
}
951
952
fn get_or_leak_string(s: &str) -> &'static str {
953
    let cache = STRING_CACHE.get_or_init(|| Mutex::new(HashSet::new()));
954
    let mut cache = cache.lock().unwrap();
955
956
    // Check if we already have this string
957
    if let Some(&cached) = cache.get(s) {
958
        return cached;
959
    }
960
961
    // Leak it and cache the result
962
    let leaked: &'static str = Box::leak(s.to_string().into_boxed_str());
963
    cache.insert(leaked);
964
    leaked
965
}