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
//! Property-based tests for buffer lifecycle management using proptest.
use proptest::prelude::*;
use safer_ring::{BufferPool, PinnedBuffer};
use std::sync::Arc;
use tokio::runtime::Runtime;
/// Test that buffer pools maintain correct allocation/deallocation invariants
#[test]
fn test_buffer_pool_invariants() {
let rt = Runtime::new().unwrap();
proptest!(|(
pool_size in 1usize..=100,
buffer_size in 64usize..=4096,
_operations in prop::collection::vec(0usize..10, 1..50)
)| {
rt.block_on(async {
let pool = Arc::new(BufferPool::new(pool_size, buffer_size));
let mut buffers = Vec::new();
// Test allocation up to pool capacity
for _ in 0..pool_size {
if let Some(buffer) = pool.get() {
prop_assert_eq!(buffer.len(), buffer_size);
buffers.push(buffer);
}
}
// Pool should be exhausted
prop_assert!(pool.get().is_none());
// Drop half the buffers
let half = buffers.len() / 2;
buffers.truncate(half);
// Should be able to allocate again
for _ in 0..(pool_size - half) {
if let Some(buffer) = pool.get() {
prop_assert_eq!(buffer.len(), buffer_size);
buffers.push(buffer);
}
}
// Pool should be exhausted again
prop_assert!(pool.get().is_none());
Ok(())
})?
});
}
/// Test that pinned buffers maintain their memory layout
#[test]
fn test_pinned_buffer_stability() {
proptest!(|(
buffer_size in 64usize..=4096,
data in prop::collection::vec(0u8..=255, 64..4096)
)| {
let mut buffer = PinnedBuffer::with_capacity(buffer_size);
let mut slice = buffer.as_mut_slice();
// Store the original pointer
let original_ptr = slice.as_ptr();
// Fill with test data
let copy_len = std::cmp::min(data.len(), slice.len());
unsafe {
let slice_mut = std::pin::Pin::get_unchecked_mut(slice.as_mut());
slice_mut[..copy_len].copy_from_slice(&data[..copy_len]);
}
// Pointer should remain stable
prop_assert_eq!(slice.as_ptr(), original_ptr);
// Data should be preserved
prop_assert_eq!(&slice[..copy_len], &data[..copy_len]);
});
}
/// Test buffer lifecycle with concurrent operations
#[test]
fn test_concurrent_buffer_operations() {
let rt = Runtime::new().unwrap();
proptest!(|(
num_operations in 1usize..=20,
buffer_sizes in prop::collection::vec(64usize..=1024, 1..20)
)| {
rt.block_on(async {
let pool = Arc::new(BufferPool::new(num_operations * 2, 1024));
let mut handles = Vec::new();
for &_size in &buffer_sizes[..std::cmp::min(buffer_sizes.len(), num_operations)] {
let pool_clone = Arc::clone(&pool);
let handle = tokio::spawn(async move {
// Get buffer from pool
let buffer = pool_clone.get();
if let Some(mut buf) = buffer {
// Simulate some work with the buffer
let mut slice = buf.as_mut_slice();
unsafe {
let slice_mut = std::pin::Pin::get_unchecked_mut(slice.as_mut());
for (i, byte) in slice_mut.iter_mut().enumerate() {
*byte = (i % 256) as u8;
}
}
// Verify data integrity
for (i, &byte) in slice.iter().enumerate() {
assert_eq!(byte, (i % 256) as u8);
}
true
} else {
false
}
});
handles.push(handle);
}
// Wait for all operations to complete
let results: Vec<_> = futures::future::join_all(handles).await;
// At least some operations should have succeeded
let successful = results.iter().filter(|r| *r.as_ref().unwrap_or(&false)).count();
prop_assert!(successful > 0);
Ok(())
})?;
});
}
/// Test that buffer generation tracking prevents use-after-free
#[test]
fn test_buffer_generation_tracking() {
proptest!(|(
operations in prop::collection::vec(1usize..=10, 1..20)
)| {
let mut buffer = PinnedBuffer::with_capacity(1024);
let initial_generation = buffer.generation();
// Simulate multiple operation cycles
for &op_count in &operations {
for _ in 0..op_count {
// Simulate operation start
buffer.mark_in_use();
// Generation should increment
prop_assert!(buffer.generation() > initial_generation);
// Simulate operation completion
buffer.mark_available();
}
}
// Buffer should be available after all operations
prop_assert!(buffer.is_available());
});
}