1#[cfg(all(not(feature = "std"), feature = "alloc"))]
15use alloc::boxed::Box;
16#[cfg(all(not(feature = "std"), feature = "alloc"))]
17use alloc::vec::Vec;
18#[cfg(all(not(feature = "std"), feature = "alloc"))]
19use alloc::{
20 format,
21 vec,
22};
23use core::sync::atomic::{
24 AtomicBool,
25 AtomicUsize,
26 Ordering,
27};
28#[cfg(feature = "std")]
29use std::boxed::Box;
30use std::sync::{
31 Arc,
32 OnceLock,
33 RwLock,
34 RwLockReadGuard,
35 RwLockWriteGuard,
36};
37use std::thread;
38use std::time::Duration;
39#[cfg(feature = "std")]
40use std::vec::Vec;
41#[cfg(feature = "std")]
42use std::{
43 format,
44 vec,
45};
46
47use crate::{
48 OptimizationLevel,
49 keccak_p,
50};
51
52#[cfg(all(
55 feature = "thread-affinity",
56 any(target_os = "linux", target_os = "windows", target_os = "macos")
57))]
58fn set_thread_affinity(thread_id: usize, strategy: AffinityStrategy) {
59 use std::sync::OnceLock;
60
61 if matches!(strategy, AffinityStrategy::Disabled) {
63 return;
64 }
65
66 static CPU_COUNT: OnceLock<usize> = OnceLock::new();
68
69 let cpu_count = CPU_COUNT.get_or_init(|| {
70 core_affinity::get_core_ids()
71 .map(|ids| ids.len())
72 .unwrap_or_else(num_cpus::get)
73 });
74
75 if *cpu_count == 0 {
76 return; }
78
79 let target_cpu = match strategy {
81 AffinityStrategy::Disabled => return,
82 AffinityStrategy::Spread => {
83 thread_id % *cpu_count
85 }
86 AffinityStrategy::Compact => {
87 let active_cores = cpu_count.div_ceil(2); thread_id % active_cores
90 }
91 AffinityStrategy::Custom => {
92 thread_id % *cpu_count
94 }
95 };
96
97 if let Some(core_ids) = core_affinity::get_core_ids() &&
99 let Some(core_id) = core_ids.get(target_cpu)
100 {
101 let _ = core_affinity::set_for_current(*core_id);
104 }
105}
106
107#[cfg(all(
109 feature = "thread-affinity",
110 not(any(target_os = "linux", target_os = "windows", target_os = "macos"))
111))]
112fn set_thread_affinity(_thread_id: usize, _strategy: AffinityStrategy) {}
113
114#[cfg(not(feature = "thread-affinity"))]
116fn set_thread_affinity(_thread_id: usize, _strategy: AffinityStrategy) {
117 }
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
122pub enum AffinityStrategy {
123 Disabled,
125 Spread,
127 Compact,
129 Custom,
131}
132
133#[derive(Debug, Clone)]
135pub struct ThreadingConfig {
136 pub num_threads: usize,
138 pub min_work_size: usize,
140 pub max_work_per_thread: usize,
142 pub timeout: Duration,
144 pub enable_affinity: bool,
146 pub affinity_strategy: AffinityStrategy,
148}
149
150impl Default for ThreadingConfig {
151 fn default() -> Self {
152 Self {
153 num_threads: num_cpus::get(),
154 min_work_size: 1024, max_work_per_thread: 64 * 1024, timeout: Duration::from_secs(30),
157 enable_affinity: true,
158 affinity_strategy: AffinityStrategy::Spread,
159 }
160 }
161}
162
163impl ThreadingConfig {
164 pub fn security_optimized() -> Self {
166 Self {
167 num_threads: 1, min_work_size: usize::MAX, max_work_per_thread: usize::MAX,
170 timeout: Duration::from_secs(5),
171 enable_affinity: false,
172 affinity_strategy: AffinityStrategy::Disabled,
173 }
174 }
175
176 pub fn performance_optimized() -> Self {
178 Self {
179 num_threads: num_cpus::get(),
180 min_work_size: 512, max_work_per_thread: 32 * 1024, timeout: Duration::from_secs(60),
183 enable_affinity: true,
184 affinity_strategy: AffinityStrategy::Spread,
185 }
186 }
187
188 pub fn balanced() -> Self {
190 Self {
191 num_threads: num_cpus::get().div_ceil(2), min_work_size: 2048, max_work_per_thread: 128 * 1024, timeout: Duration::from_secs(30),
195 enable_affinity: true,
196 affinity_strategy: AffinityStrategy::Compact,
197 }
198 }
199}
200
201#[derive(Debug, Clone)]
203pub struct WorkerStats {
204 pub worker_id: usize,
206 pub work_items_processed: usize,
208}
209
210#[derive(Debug)]
212struct WorkDistribution {
213 total_items: usize,
215 current_position: AtomicUsize,
217 completed: AtomicBool,
219 completed_count: AtomicUsize,
221}
222
223impl WorkDistribution {
224 fn new(total_items: usize) -> Self {
225 Self {
226 total_items,
227 current_position: AtomicUsize::new(0),
228 completed: AtomicBool::new(false),
229 completed_count: AtomicUsize::new(0),
230 }
231 }
232
233 fn get_next_chunk(&self, chunk_size: usize) -> Option<(usize, usize)> {
235 let start = self
236 .current_position
237 .fetch_add(chunk_size, Ordering::AcqRel);
238 if start >= self.total_items {
239 return None;
240 }
241 let end = (start + chunk_size).min(self.total_items);
242 Some((start, end))
243 }
244
245 fn mark_completed(&self) {
247 self.completed.store(true, Ordering::Release);
248 }
249
250 fn increment_completed(&self, count: usize) {
252 self.completed_count.fetch_add(count, Ordering::AcqRel);
253 }
254
255 fn is_all_work_completed(&self) -> bool {
257 self.completed_count.load(Ordering::Acquire) >= self.total_items
258 }
259
260 #[allow(dead_code)] fn is_completed(&self) -> bool {
263 self.completed.load(Ordering::Acquire)
264 }
265}
266
267#[derive(Debug)]
269struct CryptoWorker {
270 #[allow(dead_code)] id: usize,
273 work_dist: Arc<WorkDistribution>,
275 results: Arc<RwLock<Vec<[u64; 25]>>>,
277 config: ThreadingConfig,
279}
280
281fn acquire_results_write<'a>(
289 results: &'a RwLock<Vec<[u64; 25]>>,
290) -> RwLockWriteGuard<'a, Vec<[u64; 25]>> {
291 results.write().unwrap_or_else(|e| e.into_inner())
292}
293
294fn acquire_results_read<'a>(
296 results: &'a RwLock<Vec<[u64; 25]>>,
297) -> RwLockReadGuard<'a, Vec<[u64; 25]>> {
298 results.read().unwrap_or_else(|e| e.into_inner())
299}
300
301impl CryptoWorker {
302 #[allow(dead_code)] pub fn get_stats(&self) -> WorkerStats {
305 WorkerStats {
306 worker_id: self.id,
307 work_items_processed: self.work_dist.completed_count.load(Ordering::Acquire),
308 }
309 }
310
311 #[allow(dead_code)] pub fn get_worker_id(&self) -> usize {
314 self.id
315 }
316
317 fn new(
318 id: usize,
319 work_dist: Arc<WorkDistribution>,
320 results: Arc<RwLock<Vec<[u64; 25]>>>,
321 config: ThreadingConfig,
322 ) -> Self {
323 Self {
324 id,
325 work_dist,
326 results,
327 config,
328 }
329 }
330
331 fn process_keccak_parallel(&self, states: &[[u64; 25]], level: OptimizationLevel) {
333 let chunk_size = self
334 .config
335 .max_work_per_thread
336 .min(states.len() / self.config.num_threads);
337
338 while let Some((start, end)) = self.work_dist.get_next_chunk(chunk_size) {
339 let mut local_results = Vec::new();
340
341 for i in start..end {
344 if i < states.len() {
345 let mut state = states[i];
346 self.apply_keccak_optimization(&mut state, level);
347 local_results.push(state);
348 }
349 }
350
351 let mut results_guard = acquire_results_write(self.results.as_ref());
354 let results_len = results_guard.len();
355 let mut valid_results = 0;
356
357 for (i, result) in local_results.iter().enumerate() {
358 let global_index = start + i;
359 if global_index < results_len && global_index < states.len() {
360 results_guard[global_index] = *result;
361 valid_results += 1;
362 }
363 }
364
365 if valid_results > 0 {
366 self.work_dist.increment_completed(valid_results);
367 }
368 }
369
370 }
373
374 fn apply_keccak_optimization(&self, state: &mut [u64; 25], level: OptimizationLevel) {
376 match level {
377 OptimizationLevel::Reference => {
378 keccak_p(state, 24);
379 }
380 OptimizationLevel::Basic | OptimizationLevel::Advanced | OptimizationLevel::Maximum => {
381 crate::f1600(state)
382 }
383 }
384 }
385}
386
387#[derive(Debug)]
389pub struct CryptoThreadPool {
390 config: ThreadingConfig,
392 shutdown: Arc<AtomicBool>,
394}
395
396impl CryptoThreadPool {
397 pub fn new(config: ThreadingConfig) -> Self {
399 Self {
400 config,
401 shutdown: Arc::new(AtomicBool::new(false)),
402 }
403 }
404
405 pub fn process_keccak_states(
407 &self,
408 states: &[[u64; 25]],
409 level: OptimizationLevel,
410 ) -> Result<Vec<[u64; 25]>, Box<dyn std::error::Error + Send + Sync>> {
411 if states.len() < self.config.min_work_size || self.config.num_threads <= 1 {
413 return self.process_sequential(states, level);
414 }
415
416 let work_dist = Arc::new(WorkDistribution::new(states.len()));
418 let results = Arc::new(RwLock::new(vec![[0u64; 25]; states.len()]));
419 let shutdown = Arc::clone(&self.shutdown);
420
421 let mut handles = Vec::new();
423 for thread_id in 0..self.config.num_threads {
424 let worker = CryptoWorker::new(
425 thread_id,
426 Arc::clone(&work_dist),
427 Arc::clone(&results),
428 self.config.clone(),
429 );
430
431 let states_clone = states.to_vec();
432 let handle = thread::spawn(move || {
433 if worker.config.enable_affinity {
435 set_thread_affinity(thread_id, worker.config.affinity_strategy);
436 }
437
438 worker.process_keccak_parallel(&states_clone, level);
439 });
440
441 handles.push(handle);
442 }
443
444 for handle in handles {
446 if let Err(e) = handle.join() {
447 shutdown.store(true, Ordering::Release);
448 return Err(format!("Thread join error: {:?}", e).into());
449 }
450 }
451
452 work_dist.mark_completed();
454
455 let max_retries = 100; let mut retries = 0;
458
459 while !work_dist.is_all_work_completed() && retries < max_retries {
460 thread::yield_now();
462 retries += 1;
463
464 if retries % 10 == 0 {
466 let completed = work_dist.completed_count.load(Ordering::Acquire);
467 if completed >= work_dist.total_items {
468 break;
469 }
470 }
471 }
472
473 if !work_dist.is_all_work_completed() {
475 let completed = work_dist.completed_count.load(Ordering::Acquire);
476 return Err(format!(
477 "Incomplete processing after timeout: {} of {} items completed",
478 completed, work_dist.total_items
479 )
480 .into());
481 }
482
483 let results_guard = acquire_results_read(results.as_ref());
485 Ok(results_guard.clone())
486 }
487
488 fn process_sequential(
490 &self,
491 states: &[[u64; 25]],
492 level: OptimizationLevel,
493 ) -> Result<Vec<[u64; 25]>, Box<dyn std::error::Error + Send + Sync>> {
494 let mut results = Vec::with_capacity(states.len());
495
496 for state in states {
497 let mut result_state = *state;
498 match level {
499 OptimizationLevel::Reference => {
500 keccak_p(&mut result_state, 24);
501 }
502 OptimizationLevel::Basic => {
503 #[cfg(all(
504 target_arch = "x86_64",
505 feature = "asm",
506 target_feature = "avx2",
507 not(cross_compile)
508 ))]
509 unsafe {
510 crate::x86::p1600_avx2(&mut result_state);
511 }
512 #[cfg(not(all(
513 target_arch = "x86_64",
514 target_feature = "avx2",
515 not(cross_compile)
516 )))]
517 {
518 keccak_p(&mut result_state, 24);
519 }
520 }
521 OptimizationLevel::Advanced => {
522 #[cfg(all(
523 target_arch = "x86_64",
524 feature = "asm",
525 target_feature = "avx2",
526 not(cross_compile)
527 ))]
528 unsafe {
529 crate::x86::p1600_avx2(&mut result_state);
530 }
531 #[cfg(not(all(
532 target_arch = "x86_64",
533 target_feature = "avx2",
534 not(cross_compile)
535 )))]
536 {
537 keccak_p(&mut result_state, 24);
538 }
539 }
540 OptimizationLevel::Maximum => {
541 #[cfg(all(
542 target_arch = "x86_64",
543 feature = "asm",
544 target_feature = "avx512f"
545 ))]
546 unsafe {
547 crate::x86::p1600_avx512(&mut result_state);
548 }
549 #[cfg(all(
550 target_arch = "x86_64",
551 feature = "asm",
552 target_feature = "avx2",
553 not(target_feature = "avx512f"),
554 not(cross_compile)
555 ))]
556 unsafe {
557 crate::x86::p1600_avx2(&mut result_state);
558 }
559 #[cfg(not(all(
560 target_arch = "x86_64",
561 any(target_feature = "avx2", target_feature = "avx512f")
562 )))]
563 {
564 keccak_p(&mut result_state, 24);
565 }
566 }
567 }
568 results.push(result_state);
569 }
570
571 Ok(results)
572 }
573
574 pub fn shutdown(&self) {
576 self.shutdown.store(true, Ordering::Release);
577 }
578}
579
580static GLOBAL_THREAD_POOL: OnceLock<Arc<CryptoThreadPool>> = OnceLock::new();
582
583pub fn init_global_thread_pool(config: ThreadingConfig) {
585 GLOBAL_THREAD_POOL.get_or_init(|| Arc::new(CryptoThreadPool::new(config)));
586}
587
588pub fn get_global_thread_pool() -> Option<Arc<CryptoThreadPool>> {
590 GLOBAL_THREAD_POOL.get().cloned()
591}
592
593pub fn process_keccak_states_global(
595 states: &[[u64; 25]],
596 level: OptimizationLevel,
597) -> Result<Vec<[u64; 25]>, Box<dyn std::error::Error + Send + Sync>> {
598 if let Some(pool) = get_global_thread_pool() {
599 pool.process_keccak_states(states, level)
600 } else {
601 let config = ThreadingConfig::default();
603 let pool = CryptoThreadPool::new(config);
604 pool.process_keccak_states(states, level)
605 }
606}
607
608#[cfg(test)]
609mod tests {
610 use super::*;
611
612 #[test]
613 #[cfg(feature = "std")]
614 fn test_threading_config_defaults() {
615 let config = ThreadingConfig::default();
616 assert!(config.num_threads > 0);
617 assert!(config.min_work_size > 0);
618 assert!(config.max_work_per_thread > 0);
619 assert_eq!(config.affinity_strategy, AffinityStrategy::Spread);
620 assert!(config.enable_affinity);
621 }
622
623 #[test]
624 #[cfg(feature = "std")]
625 fn test_threading_config_security_optimized() {
626 let config = ThreadingConfig::security_optimized();
627 assert_eq!(config.num_threads, 1);
628 assert_eq!(config.min_work_size, usize::MAX);
629 assert_eq!(config.affinity_strategy, AffinityStrategy::Disabled);
630 assert!(!config.enable_affinity);
631 }
632
633 #[test]
634 #[cfg(feature = "std")]
635 fn test_threading_config_performance_optimized() {
636 let config = ThreadingConfig::performance_optimized();
637 assert!(config.num_threads > 0);
638 assert!(config.min_work_size < usize::MAX);
639 assert_eq!(config.affinity_strategy, AffinityStrategy::Spread);
640 assert!(config.enable_affinity);
641 }
642
643 #[test]
644 #[cfg(feature = "std")]
645 fn test_threading_config_balanced() {
646 let config = ThreadingConfig::balanced();
647 assert!(config.num_threads > 0);
648 assert!(config.min_work_size > 0);
649 assert_eq!(config.affinity_strategy, AffinityStrategy::Compact);
650 assert!(config.enable_affinity);
651 }
652
653 #[test]
654 #[cfg(feature = "std")]
655 fn test_work_distribution() {
656 let work_dist = WorkDistribution::new(100);
657 assert!(!work_dist.is_completed());
658
659 let chunk1 = work_dist.get_next_chunk(25);
661 assert_eq!(chunk1, Some((0, 25)));
662
663 let chunk2 = work_dist.get_next_chunk(25);
664 assert_eq!(chunk2, Some((25, 50)));
665
666 work_dist.mark_completed();
667 assert!(work_dist.is_completed());
668 }
669
670 #[test]
673 #[cfg(feature = "std")]
674 fn poisoned_results_lock_still_persists_writes() {
675 let results = Arc::new(RwLock::new(vec![[0u64; 25]; 2]));
676 let results_for_panic = Arc::clone(&results);
677
678 let panicker = thread::spawn(move || {
679 let _guard = results_for_panic
680 .write()
681 .expect("lock results buffer for poison test");
682 panic!("intentional test panic while holding write lock");
683 });
684 assert!(panicker.join().is_err());
685
686 {
687 let mut guard = acquire_results_write(results.as_ref());
688 guard[1] = [42u64; 25];
689 }
690
691 let guard = acquire_results_read(results.as_ref());
692 assert_eq!(guard[1], [42u64; 25]);
693 }
694
695 #[test]
696 #[cfg(feature = "std")]
697 fn test_worker_id_and_stats() {
698 let work_dist = Arc::new(WorkDistribution::new(10));
699 let results = Arc::new(RwLock::new(vec![[0u64; 25]; 10]));
700 let config = ThreadingConfig::default();
701
702 let worker = CryptoWorker::new(42, Arc::clone(&work_dist), Arc::clone(&results), config);
704
705 assert_eq!(worker.get_worker_id(), 42);
707
708 let stats = worker.get_stats();
710 assert_eq!(stats.worker_id, 42);
711 assert_eq!(stats.work_items_processed, 0);
712
713 }
715
716 #[test]
717 #[cfg(feature = "std")]
718 fn test_sequential_processing() {
719 let config = ThreadingConfig::security_optimized();
720 let pool = CryptoThreadPool::new(config);
721
722 let states = vec![[0u64; 25], [1u64; 25], [2u64; 25]];
723
724 let results = pool
725 .process_keccak_states(&states, OptimizationLevel::Reference)
726 .expect("Failed to process Keccak states in thread pool");
727 assert_eq!(results.len(), states.len());
728
729 for (original, result) in states.iter().zip(results.iter()) {
731 assert_ne!(original, result);
732 }
733 }
734
735 #[test]
736 #[cfg(feature = "std")]
737 fn test_global_thread_pool() {
738 let config = ThreadingConfig::balanced();
739 init_global_thread_pool(config);
740
741 let pool = get_global_thread_pool();
742 assert!(pool.is_some());
743
744 let states = vec![[0u64; 25]; 10];
745 let results = process_keccak_states_global(&states, OptimizationLevel::Reference)
746 .expect("Failed to process Keccak states with global thread pool");
747 assert_eq!(results.len(), states.len());
748 }
749}