@@ -20,11 +20,34 @@ use std::slice;
2020use std:: sync:: Mutex ;
2121use std:: sync:: OnceLock ;
2222
23+ /// Error codes for FFI operations
24+ #[ repr( C ) ]
25+ #[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
26+ pub enum CrcFastError {
27+ /// Operation completed successfully
28+ Success = 0 ,
29+ /// Lock was poisoned (thread panicked while holding lock)
30+ LockPoisoned = 1 ,
31+ /// Null pointer was passed where non-null required
32+ NullPointer = 2 ,
33+ /// Invalid key count for CRC parameters
34+ InvalidKeyCount = 3 ,
35+ /// Unsupported CRC width (must be 32 or 64)
36+ UnsupportedWidth = 4 ,
37+ /// Invalid UTF-8 string
38+ InvalidUtf8 = 5 ,
39+ /// File I/O error
40+ IoError = 6 ,
41+ /// Internal string conversion error
42+ StringConversionError = 7 ,
43+ }
44+
2345// Global storage for stable key pointers to ensure they remain valid across FFI boundary
2446static STABLE_KEY_STORAGE : OnceLock < Mutex < HashMap < u64 , Box < [ u64 ] > > > > = OnceLock :: new ( ) ;
2547
2648/// Creates a stable pointer to the keys for FFI usage.
2749/// The keys are stored in global memory to ensure the pointer remains valid.
50+ /// Returns (pointer, count) on success, or (null, 0) on error.
2851fn create_stable_key_pointer ( keys : & crate :: CrcKeysStorage ) -> ( * const u64 , u32 ) {
2952 let storage = STABLE_KEY_STORAGE . get_or_init ( || Mutex :: new ( HashMap :: new ( ) ) ) ;
3053
@@ -44,7 +67,10 @@ fn create_stable_key_pointer(keys: &crate::CrcKeysStorage) -> (*const u64, u32)
4467 }
4568 } ;
4669
47- let mut storage_map = storage. lock ( ) . unwrap ( ) ;
70+ let mut storage_map = match storage. lock ( ) {
71+ Ok ( guard) => guard,
72+ Err ( _) => return ( std:: ptr:: null ( ) , 0 ) , // Lock poisoned
73+ } ;
4874
4975 // Check if we already have this key set stored
5076 if let Some ( stored_keys) = storage_map. get ( & key_hash) {
@@ -140,34 +166,49 @@ pub struct CrcFastParams {
140166 pub keys : * const u64 ,
141167}
142168
143- // Convert from FFI struct to internal struct
169+ /// Fallible conversion from FFI struct to internal struct
170+ /// Returns None if the parameters are invalid (unsupported key count)
171+ fn try_params_from_ffi ( value : & CrcFastParams ) -> Option < CrcParams > {
172+ // Validate key pointer
173+ if value. keys . is_null ( ) {
174+ return None ;
175+ }
176+
177+ // Convert C array back to appropriate CrcKeysStorage
178+ let keys = unsafe { std:: slice:: from_raw_parts ( value. keys , value. key_count as usize ) } ;
179+
180+ let storage = match value. key_count {
181+ 23 => match keys. try_into ( ) {
182+ Ok ( arr) => crate :: CrcKeysStorage :: from_keys_fold_256 ( arr) ,
183+ Err ( _) => return None ,
184+ } ,
185+ 25 => match keys. try_into ( ) {
186+ Ok ( arr) => crate :: CrcKeysStorage :: from_keys_fold_future_test ( arr) ,
187+ Err ( _) => return None ,
188+ } ,
189+ _ => return None , // Unsupported key count
190+ } ;
191+
192+ Some ( CrcParams {
193+ algorithm : value. algorithm . into ( ) ,
194+ name : "custom" , // C interface doesn't need the name field
195+ width : value. width ,
196+ poly : value. poly ,
197+ init : value. init ,
198+ refin : value. refin ,
199+ refout : value. refout ,
200+ xorout : value. xorout ,
201+ check : value. check ,
202+ keys : storage,
203+ } )
204+ }
205+
206+ // Convert from FFI struct to internal struct (legacy, may panic)
207+ // For backwards compatibility, but prefer try_params_from_ffi
144208impl From < CrcFastParams > for CrcParams {
145209 fn from ( value : CrcFastParams ) -> Self {
146- // Convert C array back to appropriate CrcKeysStorage
147- let keys = unsafe { std:: slice:: from_raw_parts ( value. keys , value. key_count as usize ) } ;
148-
149- let storage = match value. key_count {
150- 23 => crate :: CrcKeysStorage :: from_keys_fold_256 (
151- keys. try_into ( ) . expect ( "Invalid key count for fold_256" ) ,
152- ) ,
153- 25 => crate :: CrcKeysStorage :: from_keys_fold_future_test (
154- keys. try_into ( ) . expect ( "Invalid key count for future_test" ) ,
155- ) ,
156- _ => panic ! ( "Unsupported key count: {}" , value. key_count) ,
157- } ;
158-
159- CrcParams {
160- algorithm : value. algorithm . into ( ) ,
161- name : "custom" , // C interface doesn't need the name field
162- width : value. width ,
163- poly : value. poly ,
164- init : value. init ,
165- refin : value. refin ,
166- refout : value. refout ,
167- xorout : value. xorout ,
168- check : value. check ,
169- keys : storage,
170- }
210+ try_params_from_ffi ( & value)
211+ . expect ( "Invalid CRC parameters: unsupported key count or null pointer" )
171212 }
172213}
173214
@@ -234,13 +275,19 @@ pub extern "C" fn crc_fast_digest_new_with_init_state(
234275}
235276
236277/// Creates a new Digest to compute CRC checksums using custom parameters
278+ /// Returns NULL if parameters are invalid (invalid key count or null pointer)
237279#[ no_mangle]
238280pub extern "C" fn crc_fast_digest_new_with_params (
239281 params : CrcFastParams ,
240282) -> * mut CrcFastDigestHandle {
241- let digest = Box :: new ( Digest :: new_with_params ( params. into ( ) ) ) ;
242- let handle = Box :: new ( CrcFastDigestHandle ( Box :: into_raw ( digest) ) ) ;
243- Box :: into_raw ( handle)
283+ match try_params_from_ffi ( & params) {
284+ Some ( crc_params) => {
285+ let digest = Box :: new ( Digest :: new_with_params ( crc_params) ) ;
286+ let handle = Box :: new ( CrcFastDigestHandle ( Box :: into_raw ( digest) ) ) ;
287+ Box :: into_raw ( handle)
288+ }
289+ None => std:: ptr:: null_mut ( ) , // Invalid parameters
290+ }
244291}
245292
246293/// Updates the Digest with data
@@ -377,6 +424,7 @@ pub extern "C" fn crc_fast_checksum(
377424}
378425
379426/// Helper method to calculate a CRC checksum directly for data using custom parameters
427+ /// Returns 0 if parameters are invalid or data is null
380428#[ no_mangle]
381429pub extern "C" fn crc_fast_checksum_with_params (
382430 params : CrcFastParams ,
@@ -386,14 +434,18 @@ pub extern "C" fn crc_fast_checksum_with_params(
386434 if data. is_null ( ) {
387435 return 0 ;
388436 }
389- unsafe {
390- #[ allow( clippy:: unnecessary_cast) ]
391- let bytes = slice:: from_raw_parts ( data as * const u8 , len) ;
392- crate :: checksum_with_params ( params. into ( ) , bytes)
437+ match try_params_from_ffi ( & params) {
438+ Some ( crc_params) => unsafe {
439+ #[ allow( clippy:: unnecessary_cast) ]
440+ let bytes = slice:: from_raw_parts ( data as * const u8 , len) ;
441+ crate :: checksum_with_params ( crc_params, bytes)
442+ } ,
443+ None => 0 , // Invalid parameters
393444 }
394445}
395446
396447/// Helper method to just calculate a CRC checksum directly for a file using algorithm
448+ /// Returns 0 if path is null or file I/O fails
397449#[ no_mangle]
398450pub extern "C" fn crc_fast_checksum_file (
399451 algorithm : CrcFastAlgorithm ,
@@ -410,11 +462,12 @@ pub extern "C" fn crc_fast_checksum_file(
410462 & convert_to_string ( path_ptr, path_len) ,
411463 None ,
412464 )
413- . unwrap ( )
465+ . unwrap_or ( 0 ) // Return 0 on I/O error
414466 }
415467}
416468
417469/// Helper method to calculate a CRC checksum directly for a file using custom parameters
470+ /// Returns 0 if parameters are invalid, path is null, or file I/O fails
418471#[ no_mangle]
419472pub extern "C" fn crc_fast_checksum_file_with_params (
420473 params : CrcFastParams ,
@@ -425,13 +478,16 @@ pub extern "C" fn crc_fast_checksum_file_with_params(
425478 return 0 ;
426479 }
427480
428- unsafe {
429- crate :: checksum_file_with_params (
430- params. into ( ) ,
431- & convert_to_string ( path_ptr, path_len) ,
432- None ,
433- )
434- . unwrap_or ( 0 ) // Return 0 on error instead of panicking
481+ match try_params_from_ffi ( & params) {
482+ Some ( crc_params) => unsafe {
483+ crate :: checksum_file_with_params (
484+ crc_params,
485+ & convert_to_string ( path_ptr, path_len) ,
486+ None ,
487+ )
488+ . unwrap_or ( 0 ) // Return 0 on error instead of panicking
489+ } ,
490+ None => 0 , // Invalid parameters
435491 }
436492}
437493
@@ -447,14 +503,20 @@ pub extern "C" fn crc_fast_checksum_combine(
447503}
448504
449505/// Combine two CRC checksums using custom parameters
506+ /// Returns 0 if parameters are invalid
450507#[ no_mangle]
451508pub extern "C" fn crc_fast_checksum_combine_with_params (
452509 params : CrcFastParams ,
453510 checksum1 : u64 ,
454511 checksum2 : u64 ,
455512 checksum2_len : u64 ,
456513) -> u64 {
457- crate :: checksum_combine_with_params ( params. into ( ) , checksum1, checksum2, checksum2_len)
514+ match try_params_from_ffi ( & params) {
515+ Some ( crc_params) => {
516+ crate :: checksum_combine_with_params ( crc_params, checksum1, checksum2, checksum2_len)
517+ }
518+ None => 0 , // Invalid parameters
519+ }
458520}
459521
460522/// Returns the custom CRC parameters for a given set of Rocksoft CRC parameters
@@ -494,7 +556,8 @@ pub extern "C" fn crc_fast_get_custom_params(
494556 algorithm : match width {
495557 32 => CrcFastAlgorithm :: Crc32Custom ,
496558 64 => CrcFastAlgorithm :: Crc64Custom ,
497- _ => panic ! ( "Unsupported width: {width}" , ) ,
559+ // Default to 32-bit for unsupported widths (defensive programming)
560+ _ => CrcFastAlgorithm :: Crc32Custom ,
498561 } ,
499562 width : params. width ,
500563 poly : params. poly ,
@@ -509,20 +572,28 @@ pub extern "C" fn crc_fast_get_custom_params(
509572}
510573
511574/// Gets the target build properties (CPU architecture and fine-tuning parameters) for this algorithm
575+ /// Returns NULL if string conversion fails (should never happen)
512576#[ no_mangle]
513577pub extern "C" fn crc_fast_get_calculator_target ( algorithm : CrcFastAlgorithm ) -> * const c_char {
514578 let target = get_calculator_target ( algorithm. into ( ) ) ;
515579
516- std:: ffi:: CString :: new ( target) . unwrap ( ) . into_raw ( )
580+ std:: ffi:: CString :: new ( target)
581+ . map ( |s| s. into_raw ( ) )
582+ . unwrap_or ( std:: ptr:: null ( ) )
517583}
518584
519585/// Gets the version of this library
586+ /// Returns a pointer to "unknown" if version string is invalid
520587#[ no_mangle]
521588pub extern "C" fn crc_fast_get_version ( ) -> * const c_char {
522589 const VERSION : & CStr =
523590 match CStr :: from_bytes_with_nul ( concat ! ( env!( "CARGO_PKG_VERSION" ) , "\0 " ) . as_bytes ( ) ) {
524591 Ok ( version) => version,
525- Err ( _) => panic ! ( "package version contains null bytes??" ) ,
592+ // Fallback to "unknown" if version string is malformed
593+ Err ( _) => match CStr :: from_bytes_with_nul ( b"unknown\0 " ) {
594+ Ok ( v) => v,
595+ Err ( _) => unsafe { CStr :: from_bytes_with_nul_unchecked ( b"unknown\0 " ) } ,
596+ } ,
526597 } ;
527598
528599 VERSION . as_ptr ( )
@@ -536,6 +607,6 @@ unsafe fn convert_to_string(data: *const u8, len: usize) -> String {
536607 // Safely construct string slice from raw parts
537608 match std:: str:: from_utf8 ( slice:: from_raw_parts ( data, len) ) {
538609 Ok ( s) => s. to_string ( ) ,
539- Err ( _) => panic ! ( "Invalid UTF-8 string" ) ,
610+ Err ( _) => String :: new ( ) , // Return empty string for invalid UTF-8
540611 }
541612}
0 commit comments