hdf5-pure-rust 0.3.4

Pure Rust implementation of the HDF5 file format
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
#![allow(dead_code, non_snake_case)]

use std::borrow::Cow;
use std::cmp::Ordering;
use std::fmt;
use std::path::Path;
use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
use std::sync::{Condvar, Mutex, Once};
use std::thread::{self, JoinHandle};
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};

use crate::error::{Error, Result};

static LIBRARY_OPEN: AtomicBool = AtomicBool::new(false);
static LIBRARY_TERMINATING: AtomicBool = AtomicBool::new(false);

#[derive(Debug, Clone)]
pub struct H5Timer {
    pub started: Option<Instant>,
    pub elapsed: Duration,
}

impl Default for H5Timer {
    /// Construct a fresh, idle timer with zero accumulated elapsed time.
    fn default() -> Self {
        Self {
            started: None,
            elapsed: Duration::ZERO,
        }
    }
}

#[derive(Debug, Default)]
pub struct H5TsMutex {
    locked: Mutex<bool>,
}

#[derive(Debug, Default)]
pub struct H5TsCond {
    cond: Condvar,
}

#[derive(Debug, Default)]
pub struct H5TsSemaphore {
    permits: Mutex<usize>,
    cond: Condvar,
}

#[derive(Debug, Default)]
pub struct H5TsRwLock {
    state: Mutex<(usize, bool)>,
    cond: Condvar,
}

/// Returns an `Unsupported` error stub for platform/MPI features not implemented in pure-Rust mode.
fn unsupported_support(name: &str) -> Error {
    Error::Unsupported(format!(
        "{name} requires platform/MPI behavior not implemented in pure-Rust mode"
    ))
}

/// Sentinel iteration-error return value.
pub fn H5_ITER_ERROR() -> i32 {
    -1
}

/// Hook invoked just before a user callback fires.
pub fn H5_BEFORE_USER_CB() {}

/// Hook invoked before a no-error user callback fires.
pub fn H5_BEFORE_USER_CB_NOERR() {}

/// Duplicate an MPI communicator; unsupported in pure-Rust mode.
pub fn H5_mpi_comm_dup() -> Result<()> {
    Err(unsupported_support("H5_mpi_comm_dup"))
}

/// Free an MPI communicator; unsupported in pure-Rust mode.
pub fn H5_mpi_comm_free() -> Result<()> {
    Err(unsupported_support("H5_mpi_comm_free"))
}

/// Compare two MPI communicators; unsupported in pure-Rust mode.
pub fn H5_mpi_comm_cmp() -> Result<Ordering> {
    Err(unsupported_support("H5_mpi_comm_cmp"))
}

/// Allocate a buffer for MPI gatherv; unsupported in pure-Rust mode.
pub fn H5_mpio_gatherv_alloc() -> Result<()> {
    Err(unsupported_support("H5_mpio_gatherv_alloc"))
}

/// Simple variant of MPI gatherv allocation; unsupported in pure-Rust mode.
pub fn H5_mpio_gatherv_alloc_simple() -> Result<()> {
    Err(unsupported_support("H5_mpio_gatherv_alloc_simple"))
}

/// Whether the MPI implementation requires explicit file syncs (always false here).
pub fn H5_mpio_get_file_sync_required() -> bool {
    false
}

/// Attribute-open common path; tracked in the attribute API rather than here.
pub fn H5A__open_common() -> Result<()> {
    Err(Error::Unsupported(
        "attribute open-common duplicate is tracked in the attribute API".into(),
    ))
}

/// Convert a `SystemTime` to seconds-since-epoch in UTC.
pub fn H5_gmtime_r(time: SystemTime) -> u64 {
    time.duration_since(UNIX_EPOCH)
        .unwrap_or_default()
        .as_secs()
}

/// Convert a `SystemTime` to seconds-since-epoch (local time is treated as UTC here).
pub fn H5_localtime_r(time: SystemTime) -> u64 {
    H5_gmtime_r(time)
}

/// Render a byte slice as a space-separated hex string for debug output.
pub fn H5_buffer_dump_into<W>(bytes: &[u8], out: &mut W) -> fmt::Result
where
    W: fmt::Write + ?Sized,
{
    for (idx, byte) in bytes.iter().enumerate() {
        if idx > 0 {
            out.write_char(' ')?;
        }
        write!(out, "{byte:02x}")?;
    }
    Ok(())
}

/// Render a byte slice as a space-separated hex string for debug output.
#[deprecated(note = "use H5_buffer_dump_into to reuse formatting storage")]
pub fn H5_buffer_dump(bytes: &[u8]) -> String {
    let mut out = String::new();
    H5_buffer_dump_into(bytes, &mut out).expect("writing to String cannot fail");
    out
}

/// Acquire the global API lock; no-op in pure-Rust mode.
pub fn H5TS_api_lock() {}

/// Compute bandwidth in bytes/second from a transfer size and elapsed time.
pub fn H5_bandwidth(bytes: u64, elapsed: Duration) -> f64 {
    let secs = elapsed.as_secs_f64();
    if secs == 0.0 {
        0.0
    } else {
        bytes as f64 / secs
    }
}

/// Return the current wall-clock time in whole seconds since the Unix epoch.
pub fn H5_now() -> u64 {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap_or_default()
        .as_secs()
}

/// Return the current wall-clock time in microseconds since the Unix epoch.
pub fn H5_now_usec() -> u128 {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap_or_default()
        .as_micros()
}

/// Return the current wall-clock time as a `SystemTime`.
pub fn H5_get_time() -> SystemTime {
    SystemTime::now()
}

/// Return current `(secs, usecs)` time used by the libhdf5 timer subsystem.
pub fn H5__timer_get_timevals() -> (u64, u128) {
    (H5_now(), H5_now_usec())
}

/// Construct a fresh, idle timer.
pub fn H5_timer_init() -> H5Timer {
    H5Timer::default()
}

/// Start (or resume) a timer.
pub fn H5_timer_start(timer: &mut H5Timer) {
    timer.started = Some(Instant::now());
}

/// Stop a running timer and return its accumulated elapsed duration.
pub fn H5_timer_stop(timer: &mut H5Timer) -> Duration {
    if let Some(started) = timer.started.take() {
        timer.elapsed = timer.elapsed.saturating_add(started.elapsed());
    }
    timer.elapsed
}

/// Return the elapsed duration recorded by the timer so far.
pub fn H5_timer_get_times(timer: &H5Timer) -> Duration {
    timer.elapsed
}

/// Return the cumulative duration recorded by the timer.
pub fn H5_timer_get_total_times(timer: &H5Timer) -> Duration {
    timer.elapsed
}

/// Format a duration as a human-readable seconds string.
pub fn H5_timer_get_time_string_fmt<W>(duration: Duration, out: &mut W) -> fmt::Result
where
    W: fmt::Write + ?Sized,
{
    write!(out, "{:.6}s", duration.as_secs_f64())
}

/// Format a duration as a human-readable seconds string.
pub fn H5_timer_get_time_string_into(duration: Duration, out: &mut String) {
    out.clear();
    H5_timer_get_time_string_fmt(duration, out).expect("writing to String cannot fail");
}

/// Format a duration as a human-readable seconds string.
#[deprecated(note = "use H5_timer_get_time_string_into to reuse String storage")]
pub fn H5_timer_get_time_string(duration: Duration) -> String {
    let mut out = String::new();
    H5_timer_get_time_string_into(duration, &mut out);
    out
}

/// Initialize the H5 package: mark the library as open.
pub fn H5__init_package() {
    LIBRARY_OPEN.store(true, AtomicOrdering::SeqCst);
}

/// Initialize the HDF5 library.
pub fn H5_init_library() {
    H5__init_package();
}

/// Terminate the HDF5 library and release all resources.
pub fn H5_term_library() {
    LIBRARY_TERMINATING.store(true, AtomicOrdering::SeqCst);
    LIBRARY_OPEN.store(false, AtomicOrdering::SeqCst);
}

/// Disable the library's `atexit` handler; no-op in pure-Rust mode.
pub fn H5dont_atexit() {}

/// Run a garbage-collection pass on free lists; no-op in pure-Rust mode.
pub fn H5garbage_collect() {}

/// Echo the requested debug-print mask back to the caller.
pub fn H5__debug_mask(mask: u64) -> u64 {
    mask
}

/// Callback invoked when MPI deletes its file-key attribute; unsupported here.
pub fn H5__mpi_delete_cb() -> Result<()> {
    Err(unsupported_support("H5__mpi_delete_cb"))
}

/// Return the HDF5 library version as `(major, minor, release)`.
pub fn H5get_libversion() -> (u32, u32, u32) {
    (0, 1, 0)
}

/// Check whether the linked library matches the requested version.
pub fn H5_check_version(major: u32, minor: u32, release: u32) -> bool {
    H5get_libversion() == (major, minor, release)
}

/// Public alias for `H5_check_version`.
pub fn H5check_version(major: u32, minor: u32, release: u32) -> bool {
    H5_check_version(major, minor, release)
}

/// Open the HDF5 library (initialize it if needed).
pub fn H5open() {
    H5_init_library();
}

/// Register an at-close handler; no-op in pure-Rust mode.
pub fn H5atclose() {}

/// Close the HDF5 library and release all resources.
pub fn H5close() {
    H5_term_library();
}

/// Allocate `size` bytes of zero-initialized memory.
pub fn H5allocate_memory(size: usize) -> Vec<u8> {
    vec![0; size]
}

/// Resize a previously allocated buffer to `size` bytes.
pub fn H5resize_memory(mut bytes: Vec<u8>, size: usize) -> Vec<u8> {
    bytes.resize(size, 0);
    bytes
}

/// Free a previously allocated buffer.
pub fn H5free_memory(_bytes: Vec<u8>) {}

/// Return whether the library was built thread-safe.
pub fn H5is_library_threadsafe() -> bool {
    true
}

/// Return whether the library is currently shutting down.
pub fn H5is_library_terminating() -> bool {
    LIBRARY_TERMINATING.load(AtomicOrdering::SeqCst)
}

/// Hook invoked before entering user callback context.
pub fn H5_user_cb_prepare() {}

/// Hook invoked after returning from user callback context.
pub fn H5_user_cb_restore() {}

/// Return whether the library was built with parallel (MPI) support.
pub fn H5_HAVE_PARALLEL() -> bool {
    false
}

/// Update a running checksum with additional bytes (placeholder CRC variant).
pub fn H5__checksum_crc_update(seed: u32, bytes: &[u8]) -> u32 {
    bytes
        .iter()
        .fold(seed, |acc, byte| acc.rotate_left(5) ^ u32::from(*byte))
}

/// Compute a CRC-style checksum over the given bytes.
pub fn H5_checksum_crc(bytes: &[u8]) -> u32 {
    H5__checksum_crc_update(0, bytes)
}

/// Create a use-count tracker initialized to 1.
pub fn H5UC_create() -> usize {
    1
}

/// Decrement a use-count tracker and return the new value.
pub fn H5UC_decr(count: &mut usize) -> usize {
    *count = count.saturating_sub(1);
    *count
}

/// Create a new (unlocked) thread-safety mutex.
pub fn H5TS_mutex_init() -> H5TsMutex {
    H5TsMutex::default()
}

/// Attempt to lock a mutex without blocking; returns whether the lock was acquired.
pub fn H5TS_mutex_trylock(mutex: &H5TsMutex) -> bool {
    if let Ok(mut locked) = mutex.locked.try_lock() {
        if !*locked {
            *locked = true;
            return true;
        }
    }
    false
}

/// Destroy a thread-safety mutex.
pub fn H5TS_mutex_destroy(_mutex: H5TsMutex) {}

/// Create a counting semaphore initialized with `permits` permits.
pub fn H5TS_semaphore_init(permits: usize) -> H5TsSemaphore {
    H5TsSemaphore {
        permits: Mutex::new(permits),
        cond: Condvar::new(),
    }
}

/// Destroy a counting semaphore.
pub fn H5TS_semaphore_destroy(_sem: H5TsSemaphore) {}

/// Run `f` exactly once across all callers of `once`.
pub fn H5TS_once(once: &Once, f: fn()) {
    once.call_once(f);
}

/// Create a new condition variable.
pub fn H5TS_cond_init() -> H5TsCond {
    H5TsCond::default()
}

/// Destroy a condition variable.
pub fn H5TS_cond_destroy(_cond: H5TsCond) {}

/// Signal a counting semaphore, releasing one permit.
pub fn H5TS_semaphore_signal(sem: &H5TsSemaphore) {
    if let Ok(mut permits) = sem.permits.lock() {
        *permits = permits.saturating_add(1);
        sem.cond.notify_one();
    }
}

/// Block until a permit is available on the semaphore, then consume it.
pub fn H5TS_semaphore_wait(sem: &H5TsSemaphore) {
    if let Ok(mut permits) = sem.permits.lock() {
        while *permits == 0 {
            permits = sem.cond.wait(permits).expect("semaphore wait poisoned");
        }
        *permits = permits.saturating_sub(1);
    }
}

/// Create a new reader/writer lock.
pub fn H5TS_rwlock_init() -> H5TsRwLock {
    H5TsRwLock::default()
}

/// Destroy a reader/writer lock.
pub fn H5TS_rwlock_destroy(_lock: H5TsRwLock) {}

/// Acquire a shared (read) lock, blocking while a writer holds the lock.
pub fn H5TS_rwlock_rdlock(lock: &H5TsRwLock) {
    let mut state = lock.state.lock().expect("rwlock poisoned");
    while state.1 {
        state = lock.cond.wait(state).expect("rwlock wait poisoned");
    }
    state.0 = state.0.saturating_add(1);
}

/// Release a shared (read) lock, waking waiting writers when the last reader exits.
pub fn H5TS_rwlock_rdunlock(lock: &H5TsRwLock) {
    if let Ok(mut state) = lock.state.lock() {
        state.0 = state.0.saturating_sub(1);
        if state.0 == 0 {
            lock.cond.notify_all();
        }
    }
}

/// Acquire an exclusive (write) lock, blocking while any readers or another writer hold it.
pub fn H5TS_rwlock_wrlock(lock: &H5TsRwLock) {
    let mut state = lock.state.lock().expect("rwlock poisoned");
    while state.1 || state.0 != 0 {
        state = lock.cond.wait(state).expect("rwlock wait poisoned");
    }
    state.1 = true;
}

/// Try to acquire an exclusive write lock without blocking.
pub fn H5TS_rwlock_trywrlock(lock: &H5TsRwLock) -> bool {
    if let Ok(mut state) = lock.state.try_lock() {
        if !state.1 && state.0 == 0 {
            state.1 = true;
            return true;
        }
    }
    false
}

/// Release an exclusive (write) lock and notify all waiters.
pub fn H5TS_rwlock_wrunlock(lock: &H5TsRwLock) {
    if let Ok(mut state) = lock.state.lock() {
        state.1 = false;
        lock.cond.notify_all();
    }
}

/// Create a thread-local key.
pub fn H5TS_key_create() -> usize {
    0
}

/// Delete a thread-local key.
pub fn H5TS_key_delete(_key: usize) {}

/// Store a thread-local value under the given key.
pub fn H5TS_key_set_value<T>(_key: usize, value: T) -> T {
    value
}

/// Retrieve the thread-local value associated with the given key.
pub fn H5TS_key_get_value(_key: usize) -> Option<()> {
    None
}

/// Create a thread pool; intentionally unsupported in pure-Rust mode.
#[allow(non_snake_case)]
pub fn H5TS_pool_create() -> Result<()> {
    Err(Error::Unsupported(
        "thread-pool runtime is intentionally unsupported".into(),
    ))
}

/// Format a boolean argument for the API trace log.
pub fn H5_trace_args_bool_ref(value: bool) -> &'static str {
    if value {
        "true"
    } else {
        "false"
    }
}

/// Format a boolean argument for the API trace log.
pub fn H5_trace_args_bool_into(value: bool, out: &mut String) {
    out.clear();
    out.push_str(H5_trace_args_bool_ref(value));
}

/// Format a boolean argument for the API trace log.
#[deprecated(note = "use H5_trace_args_bool_into to reuse String storage")]
pub fn H5_trace_args_bool(value: bool) -> String {
    let mut out = String::new();
    H5_trace_args_bool_into(value, &mut out);
    out
}

/// Format a character-set argument for the API trace log.
pub fn H5_trace_args_cset_fmt<W>(value: u8, out: &mut W) -> fmt::Result
where
    W: fmt::Write + ?Sized,
{
    write!(out, "{value}")
}

/// Format a character-set argument for the API trace log.
pub fn H5_trace_args_cset_into(value: u8, out: &mut String) {
    out.clear();
    H5_trace_args_cset_fmt(value, out).expect("writing to String cannot fail");
}

/// Format a character-set argument for the API trace log.
#[deprecated(note = "use H5_trace_args_cset_into to reuse String storage")]
pub fn H5_trace_args_cset(value: u8) -> String {
    let mut out = String::new();
    H5_trace_args_cset_into(value, &mut out);
    out
}

/// Format a file-close-degree argument for the API trace log.
pub fn H5_trace_args_close_degree_fmt<W>(value: u8, out: &mut W) -> fmt::Result
where
    W: fmt::Write + ?Sized,
{
    H5_trace_args_cset_fmt(value, out)
}

/// Format a file-close-degree argument for the API trace log.
pub fn H5_trace_args_close_degree_into(value: u8, out: &mut String) {
    out.clear();
    H5_trace_args_close_degree_fmt(value, out).expect("writing to String cannot fail");
}

/// Format a file-close-degree argument for the API trace log.
#[deprecated(note = "use H5_trace_args_close_degree_into to reuse String storage")]
pub fn H5_trace_args_close_degree(value: u8) -> String {
    let mut out = String::new();
    H5_trace_args_close_degree_into(value, &mut out);
    out
}

/// Join a list of pre-formatted argument strings into a single trace argument list.
pub fn H5_trace_args_fmt<S, W>(args: &[S], out: &mut W) -> fmt::Result
where
    S: AsRef<str>,
    W: fmt::Write + ?Sized,
{
    for (idx, arg) in args.iter().enumerate() {
        if idx > 0 {
            out.write_str(", ")?;
        }
        out.write_str(arg.as_ref())?;
    }
    Ok(())
}

/// Join a list of pre-formatted argument strings into a single trace argument list.
pub fn H5_trace_args_into<S>(args: &[S], out: &mut String)
where
    S: AsRef<str>,
{
    out.clear();
    H5_trace_args_fmt(args, out).expect("writing to String cannot fail");
}

/// Visit pre-formatted trace arguments in order.
pub fn H5_trace_args_iter<S>(args: &[S]) -> impl Iterator<Item = &str>
where
    S: AsRef<str>,
{
    args.iter().map(AsRef::as_ref)
}

/// Join a list of pre-formatted argument strings into a single trace argument list.
#[deprecated(note = "use H5_trace_args_iter or H5_trace_args_into")]
pub fn H5_trace_args(args: &[String]) -> String {
    let mut out = String::new();
    H5_trace_args_into(args, &mut out);
    out
}

/// Emit a single trace message line.
pub fn H5_trace_ref(message: &str) -> &str {
    message
}

/// Emit a single trace message line.
#[deprecated(note = "use H5_trace_ref to borrow the trace message")]
pub fn H5_trace(message: &str) -> String {
    H5_trace_ref(message).to_string()
}

/// Spawn a new OS thread running `f`.
pub fn H5TS_thread_create<F>(f: F) -> JoinHandle<()>
where
    F: FnOnce() + Send + 'static,
{
    thread::spawn(f)
}

/// Join a previously spawned thread, mapping panics to an error.
pub fn H5TS_thread_join(handle: JoinHandle<()>) -> Result<()> {
    handle
        .join()
        .map_err(|_| Error::InvalidFormat("thread panicked".into()))
}

/// Detach a thread, allowing it to run to completion without being joined.
pub fn H5TS_thread_detach(_handle: JoinHandle<()>) {}

/// Hint to the scheduler that the current thread is willing to yield.
pub fn H5TS_thread_yield() {
    thread::yield_now();
}

/// Copy a VFD property list; tracked in the VFD API rather than here.
pub fn H5FD__copy_plist() -> Result<()> {
    Err(Error::Unsupported(
        "VFD property-list copy duplicate is tracked in the VFD API".into(),
    ))
}

/// Dump pending subfiling I/O vectors; unsupported in pure-Rust mode.
pub fn H5_subfiling_dump_iovecs() -> Result<()> {
    Err(unsupported_support("H5_subfiling_dump_iovecs"))
}

/// Convert a seconds-since-epoch value to a `SystemTime`.
pub fn H5_make_time(secs: u64) -> SystemTime {
    UNIX_EPOCH + Duration::from_secs(secs)
}

/// Format a `SystemTime` as a local-time string (here, seconds since epoch).
pub fn H5_get_localtime_str_fmt<W>(time: SystemTime, out: &mut W) -> fmt::Result
where
    W: fmt::Write + ?Sized,
{
    write!(out, "{}", H5_gmtime_r(time))
}

/// Format a `SystemTime` as a local-time string (here, seconds since epoch).
pub fn H5_get_localtime_str_into(time: SystemTime, out: &mut String) {
    out.clear();
    H5_get_localtime_str_fmt(time, out).expect("writing to String cannot fail");
}

/// Format a `SystemTime` as a local-time string (here, seconds since epoch).
#[deprecated(note = "use H5_get_localtime_str_into to reuse String storage")]
pub fn H5_get_localtime_str(time: SystemTime) -> String {
    H5_gmtime_r(time).to_string()
}

/// Retrieve Windows-style high-resolution times; unsupported on non-Windows platforms.
pub fn H5_get_win32_times() -> Result<()> {
    Err(unsupported_support("H5_get_win32_times"))
}

/// Decode a UTF-16 buffer into a `String`.
pub fn H5_get_utf16_str_into(bytes: &[u16], out: &mut String) -> Result<()> {
    out.clear();
    for item in std::char::decode_utf16(bytes.iter().copied()) {
        let ch = item.map_err(|_| Error::InvalidFormat("invalid UTF-16 string".into()))?;
        out.push(ch);
    }
    Ok(())
}

/// Decode a UTF-16 buffer into a `String`.
#[deprecated(note = "use H5_get_utf16_str_into to reuse String storage")]
pub fn H5_get_utf16_str(bytes: &[u16]) -> Result<String> {
    let mut out = String::new();
    H5_get_utf16_str_into(bytes, &mut out)?;
    Ok(out)
}

/// Build a `prefix/name` external-link path.
pub fn H5_build_extpath_cow<'a>(prefix: &str, name: &'a str) -> Cow<'a, str> {
    if prefix.is_empty() {
        Cow::Borrowed(name)
    } else {
        Cow::Owned(format!("{prefix}/{name}"))
    }
}

/// Build a `prefix/name` external-link path into caller-owned storage.
pub fn H5_build_extpath_into(prefix: &str, name: &str, out: &mut String) {
    out.clear();
    if prefix.is_empty() {
        out.push_str(name);
    } else {
        out.push_str(prefix);
        out.push('/');
        out.push_str(name);
    }
}

/// Build a `prefix/name` external-link path.
#[deprecated(note = "use H5_build_extpath_cow or H5_build_extpath_into")]
pub fn H5_build_extpath(prefix: &str, name: &str) -> String {
    H5_build_extpath_cow(prefix, name).into_owned()
}

/// Sleep for `nanos` nanoseconds.
pub fn H5_nanosleep(nanos: u64) {
    thread::sleep(Duration::from_nanos(nanos));
}

/// Expand `%ENV%`-style Windows environment variables in a string (no-op here).
pub fn H5_expand_windows_env_vars_ref(value: &str) -> &str {
    value
}

/// Expand `%ENV%`-style Windows environment variables in a string (no-op here).
#[deprecated(note = "use H5_expand_windows_env_vars_ref to borrow the unchanged string")]
pub fn H5_expand_windows_env_vars(value: &str) -> String {
    H5_expand_windows_env_vars_ref(value).to_string()
}

/// Duplicate the first `len` characters of a string.
pub fn H5_strndup_cow(value: &str, len: usize) -> Cow<'_, str> {
    let mut char_indices = value.char_indices();
    match char_indices.nth(len) {
        Some((end, _)) => Cow::Borrowed(&value[..end]),
        None => Cow::Borrowed(value),
    }
}

/// Duplicate the first `len` characters of a string into caller-owned storage.
pub fn H5_strndup_into(value: &str, len: usize, out: &mut String) {
    out.clear();
    out.push_str(&H5_strndup_cow(value, len));
}

/// Duplicate the first `len` characters of a string.
#[deprecated(note = "use H5_strndup_cow or H5_strndup_into")]
pub fn H5_strndup(value: &str, len: usize) -> String {
    H5_strndup_cow(value, len).into_owned()
}

/// Return the directory portion of a path, or "." if there is none.
pub fn H5_dirname_cow(value: &str) -> Cow<'_, str> {
    Path::new(value)
        .parent()
        .map(Path::to_string_lossy)
        .unwrap_or(Cow::Borrowed("."))
}

/// Return the directory portion of a path into caller-owned storage.
pub fn H5_dirname_into(value: &str, out: &mut String) {
    out.clear();
    out.push_str(&H5_dirname_cow(value));
}

/// Return the directory portion of a path, or "." if there is none.
#[deprecated(note = "use H5_dirname_cow or H5_dirname_into")]
pub fn H5_dirname(value: &str) -> String {
    H5_dirname_cow(value).into_owned()
}

/// Return the trailing component (filename) of a path.
pub fn H5_basename_cow(value: &str) -> Cow<'_, str> {
    Path::new(value)
        .file_name()
        .map(|path| path.to_string_lossy())
        .unwrap_or(Cow::Borrowed(value))
}

/// Return the trailing component (filename) of a path into caller-owned storage.
pub fn H5_basename_into(value: &str, out: &mut String) {
    out.clear();
    out.push_str(&H5_basename_cow(value));
}

/// Return the trailing component (filename) of a path.
#[deprecated(note = "use H5_basename_cow or H5_basename_into")]
pub fn H5_basename(value: &str) -> String {
    H5_basename_cow(value).into_owned()
}

/// Return `value` if set, otherwise the provided default.
pub fn H5_get_option<T: Copy>(value: Option<T>, default: T) -> T {
    value.unwrap_or(default)
}

/// Case-insensitive substring search returning the byte offset of the match.
pub fn H5_strcasestr(haystack: &str, needle: &str) -> Option<usize> {
    let needle = needle.as_bytes();
    if needle.is_empty() {
        return Some(0);
    }

    haystack
        .as_bytes()
        .windows(needle.len())
        .position(|window| {
            window
                .iter()
                .zip(needle)
                .all(|(left, right)| left.eq_ignore_ascii_case(right))
        })
}

/// Return whether the host CPU is little-endian.
pub fn is_host_little_endian() -> bool {
    cfg!(target_endian = "little")
}

/// Create an extensible-array data block; not implemented (tracked elsewhere).
pub fn H5EA__dblock_create() -> Result<()> {
    Err(Error::Unsupported(
        "extensible-array data block creation is not implemented".into(),
    ))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn strcasestr_searches_without_changing_offsets() {
        assert_eq!(H5_strcasestr("prefixDATA", "data"), Some(6));
        assert_eq!(H5_strcasestr("abcDEFghi", "Def"), Some(3));
        assert_eq!(H5_strcasestr("abc", ""), Some(0));
        assert_eq!(H5_strcasestr("abc", "abcd"), None);
        assert_eq!(H5_strcasestr("abc", "z"), None);
    }

    #[test]
    fn support_owned_compat_wrappers_route_through_new_apis() {
        let mut out = String::new();

        H5_buffer_dump_into(&[0, 10, 255], &mut out).expect("formatting should succeed");
        assert_eq!(out, "00 0a ff");

        H5_trace_args_into(&["one", "two"], &mut out);
        assert_eq!(out, "one, two");

        H5_strndup_into("abcdef", 3, &mut out);
        assert_eq!(out, "abc");

        H5_build_extpath_into("dir", "file.h5", &mut out);
        assert_eq!(out, "dir/file.h5");
    }
}