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
use crate::error::{CommonError, Result};
use serde::{de::DeserializeOwned, Serialize};
use std::io::{Cursor, Read};
use std::path::Path;
use std::sync::{Arc, Mutex};
struct Inner {
data: Mutex<Vec<u8>>,
/// Frame-read offset into `data`; independent of writes.
pos: Mutex<usize>,
}
/// The `ZiskStdin` struct provides an abstraction for handling standard input data in a flexible manner.
#[derive(Clone)]
pub struct ZiskStdin {
inner: Arc<Inner>,
}
impl Default for ZiskStdin {
fn default() -> Self {
Self::new()
}
}
impl ZiskStdin {
/// Creates a new, empty `ZiskStdin` instance.
pub fn new() -> Self {
Self { inner: Arc::new(Inner { data: Mutex::new(Vec::new()), pos: Mutex::new(0) }) }
}
/// Creates a `ZiskStdin` instance from a vector of bytes.
pub fn from_vec(data: Vec<u8>) -> Self {
Self { inner: Arc::new(Inner { data: Mutex::new(data), pos: Mutex::new(0) }) }
}
/// Creates a `ZiskStdin` instance by reading data from a file at the specified path.
///
/// # Errors
///
/// Returns [`CommonError::Io`] if the file cannot be read.
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
let data = std::fs::read(path.as_ref()).map_err(|e| {
CommonError::Io(format!("Failed to read input file {:?}: {}", path.as_ref(), e))
})?;
Ok(Self::from_vec(data))
}
/// Create a `ZiskStdin` from a URI string.
/// - `None` → empty stdin
/// - `"file://path"` → read from file
/// - `"inline://[[1,2],[3]]"` → inline input, a JSON array of u64 arrays
/// - No scheme → treated as a file path
///
/// # Errors
///
/// - [`CommonError::UnknownScheme`] if the URI carries an unrecognized scheme.
/// - Any error from [`from_file`](Self::from_file) or [`from_inline`](Self::from_inline)
/// when reading the referenced input.
pub fn from_uri<S: Into<String>>(stdin_uri: Option<S>) -> Result<ZiskStdin> {
let Some(uri) = stdin_uri else { return Ok(ZiskStdin::new()) };
let uri = uri.into();
if let Some(pos) = uri.find("://") {
let (scheme, path) = uri.split_at(pos);
let path = &path[3..];
match scheme {
"file" => ZiskStdin::from_file(path),
"inline" => ZiskStdin::from_inline(path),
_ => Err(CommonError::UnknownScheme(scheme.to_string())),
}
} else {
ZiskStdin::from_file(uri.as_str())
}
}
/// Create a `ZiskStdin` from an inline JSON array of u64 arrays.
///
/// Each inner array is written as one frame via [`write_slice`](Self::write_slice),
/// so the buffer is byte-identical to a saved `input.bin`: every frame carries an
/// 8-byte little-endian length prefix and is padded to an 8-byte boundary.
///
/// Example: `"[[1,2],[3],[4,5,6]]"` produces three frames.
///
/// # Errors
///
/// Returns [`CommonError::Invalid`] if the input is not a valid JSON array of u64 arrays.
pub fn from_inline(json: &str) -> Result<ZiskStdin> {
let frames: Vec<Vec<u64>> = serde_json::from_str(json).map_err(|e| {
CommonError::Invalid(format!(
"inline input must be a JSON array of u64 arrays, e.g. [[1,2],[3]]; got: {json}: {e}"
))
})?;
let stdin = ZiskStdin::new();
for frame in frames {
let mut bytes = Vec::with_capacity(frame.len() * 8);
for word in frame {
bytes.extend_from_slice(&word.to_le_bytes());
}
stdin.write_slice(&bytes);
}
Ok(stdin)
}
/// Read the raw byte data from the `ZiskStdin` buffer.
///
/// # Panics
///
/// Panics if the internal mutex is poisoned.
pub fn read_data(&self) -> Vec<u8> {
self.inner.data.lock().unwrap().clone()
}
/// Borrow the raw buffer for the duration of `f`. Prefer this over
/// [`read_data`](Self::read_data) for read-only use; inputs reach tens of MB.
///
/// `f` must not call back into this `ZiskStdin`: the buffer lock is held for
/// its duration and is not reentrant.
///
/// # Panics
///
/// Panics if the internal mutex is poisoned.
pub fn with_data<R>(&self, f: impl FnOnce(&[u8]) -> R) -> R {
f(self.inner.data.lock().unwrap().as_slice())
}
/// Length of the raw buffer in bytes.
///
/// # Panics
///
/// Panics if the internal mutex is poisoned.
pub fn len(&self) -> usize {
self.inner.data.lock().unwrap().len()
}
/// Whether the raw buffer is empty.
///
/// # Panics
///
/// Panics if the internal mutex is poisoned.
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Read the next frame of data from the `ZiskStdin` buffer as a vector of bytes.
///
/// # Panics
///
/// Panics if the internal mutex is poisoned or if reading the next frame fails.
pub fn read_bytes(&self) -> Vec<u8> {
self.read_raw().expect("Failed to read from stdin buffer")
}
/// Reads the next frame of data from the [`ZiskStdin`] buffer and deserializes
/// it into a value of type `T`.
///
/// # Errors
///
/// - [`CommonError::Io`] if reading from the buffer fails.
/// - [`CommonError::Deserialization`] if deserialization fails.
pub fn read<T: DeserializeOwned>(&self) -> Result<T> {
let data = self
.read_raw()
.map_err(|e| CommonError::Io(format!("Failed to read from stdin: {}", e)))?;
bincode::serde::decode_from_slice(&data, bincode::config::standard())
.map(|(v, _)| v)
.map_err(|e| CommonError::Deserialization(e.to_string()))
}
/// Write a serializable value of type `T` to the `ZiskStdin` buffer as a new frame.
///
/// # Panics
///
/// Panics if `data` cannot be serialized, or if the internal mutex is poisoned.
pub fn write<T: Serialize>(&self, data: &T) {
let bytes = bincode::serde::encode_to_vec(data, bincode::config::standard())
.expect("Failed to serialize");
self.write_slice(&bytes);
}
/// Write a raw slice of bytes to the `ZiskStdin` buffer as a new frame, prefixed with its length and padded to an 8-byte boundary.
///
/// # Panics
///
/// Panics if the internal mutex is poisoned.
pub fn write_slice(&self, data: &[u8]) {
let data_len = data.len();
let total_len = 8 + data_len;
let padding = (8 - (total_len % 8)) % 8;
let len_bytes = data_len.to_le_bytes();
let mut buf = self.inner.data.lock().unwrap();
buf.extend_from_slice(&len_bytes);
buf.extend_from_slice(data);
let padded_len = buf.len() + padding;
buf.resize(padded_len, 0);
}
/// Save the `ZiskStdin` buffer to a file at the specified path.
///
/// # Errors
///
/// Returns [`CommonError::Io`] if the parent directory cannot be created or the file cannot be written.
///
/// # Panics
///
/// Panics if the internal mutex is poisoned.
pub fn save(&self, path: &Path) -> Result<()> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).map_err(|e| {
CommonError::Io(format!(
"failed to create parent directory {}: {e}",
parent.display()
))
})?;
}
std::fs::write(path, self.inner.data.lock().unwrap().as_slice()).map_err(|e| {
CommonError::Io(format!("failed to write stdin to {}: {e}", path.display()))
})?;
Ok(())
}
/// Reset the read cursor to the beginning.
///
/// # Panics
///
/// Panics if the internal mutex is poisoned.
pub fn rewind(&self) {
*self.inner.pos.lock().unwrap() = 0;
}
/// Alias for `rewind`.
pub fn reset(&self) {
self.rewind();
}
/// Clear the `ZiskStdin` buffer and reset the cursor.
///
/// # Panics
///
/// Panics if the internal mutex is poisoned.
pub fn clear(&self) {
self.inner.data.lock().unwrap().clear();
*self.inner.pos.lock().unwrap() = 0;
}
/// Reads the next length-prefixed frame, advancing past its padding.
fn read_raw(&self) -> std::io::Result<Vec<u8>> {
let buf = self.inner.data.lock().unwrap();
let mut pos = self.inner.pos.lock().unwrap();
let mut cursor = Cursor::new(&buf[..]);
cursor.set_position(*pos as u64);
let mut len_bytes = [0u8; 8];
cursor.read_exact(&mut len_bytes)?;
let len = usize::from_le_bytes(len_bytes);
let mut data = vec![0u8; len];
cursor.read_exact(&mut data)?;
// Padding is at most 7 bytes, so it reads into a stack buffer.
let padding = (8 - ((8 + len) % 8)) % 8;
if padding > 0 {
let mut pad = [0u8; 7];
cursor.read_exact(&mut pad[..padding])?;
}
// Commit only on success, so a short read leaves the reader put.
*pos = cursor.position() as usize;
Ok(data)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_vec_round_trips_a_single_frame() {
let stdin = ZiskStdin::new();
stdin.write_slice(&[1, 2, 3]);
// The path a saved `input.bin` takes.
let reloaded = ZiskStdin::from_vec(stdin.read_data());
assert_eq!(reloaded.read_bytes(), vec![1, 2, 3]);
}
#[test]
fn frames_are_read_back_in_order_with_padding_skipped() {
let stdin = ZiskStdin::new();
stdin.write_slice(&[9]); // 1 byte -> 7 bytes padding
stdin.write_slice(&[1, 2, 3, 4, 5, 6, 7, 8]); // 8 bytes -> 0 padding
stdin.write_slice(&[]); // empty frame
assert_eq!(stdin.read_bytes(), vec![9]);
assert_eq!(stdin.read_bytes(), vec![1, 2, 3, 4, 5, 6, 7, 8]);
assert_eq!(stdin.read_bytes(), Vec::<u8>::new());
}
#[test]
fn every_frame_is_eight_byte_aligned() {
let stdin = ZiskStdin::new();
for len in 0..24usize {
stdin.write_slice(&vec![7u8; len]);
assert_eq!(stdin.len() % 8, 0, "buffer unaligned after a {len}-byte frame");
}
}
#[test]
fn rewind_replays_from_the_start() {
let stdin = ZiskStdin::new();
stdin.write_slice(&[4, 5]);
assert_eq!(stdin.read_bytes(), vec![4, 5]);
stdin.rewind();
assert_eq!(stdin.read_bytes(), vec![4, 5]);
}
#[test]
fn writes_after_a_read_are_visible_without_disturbing_the_position() {
let stdin = ZiskStdin::new();
stdin.write_slice(&[1]);
assert_eq!(stdin.read_bytes(), vec![1]);
stdin.write_slice(&[2]);
assert_eq!(stdin.read_bytes(), vec![2]);
}
#[test]
fn reading_past_the_end_errors_and_leaves_the_position_put() {
let stdin = ZiskStdin::new();
stdin.write_slice(&[1, 2]);
assert_eq!(stdin.read_bytes(), vec![1, 2]);
assert!(stdin.read_raw().is_err());
// A failed read must not consume anything.
stdin.write_slice(&[3]);
assert_eq!(stdin.read_bytes(), vec![3]);
}
#[test]
fn clear_empties_the_buffer_and_resets_the_reader() {
let stdin = ZiskStdin::new();
stdin.write_slice(&[1, 2, 3]);
stdin.clear();
assert!(stdin.is_empty());
assert!(stdin.read_raw().is_err());
stdin.write_slice(&[8]);
assert_eq!(stdin.read_bytes(), vec![8]);
}
#[test]
fn with_data_sees_the_same_bytes_as_read_data() {
let stdin = ZiskStdin::new();
stdin.write_slice(&[1, 2, 3]);
assert_eq!(stdin.with_data(|d| d.to_vec()), stdin.read_data());
assert_eq!(stdin.with_data(|d| d.len()), stdin.len());
}
#[test]
fn clones_share_one_buffer() {
let stdin = ZiskStdin::new();
let clone = stdin.clone();
stdin.write_slice(&[42]);
assert_eq!(clone.read_bytes(), vec![42]);
}
}