Skip to main content

libfw_server/
ws.rs

1//! WebSocket handler: the unified block-transfer transport.
2//!
3//! Every transfer — **upload or download** — uses the identical block
4//! protocol from [`libfw_core::ws`]:
5//!
6//! - The sender pipelines fixed-size blocks without waiting for a per-block
7//!   acknowledgment (no-ack), so blocks may travel out of order.
8//! - The receiver verifies **every** block in real time (CRC32 + bounds),
9//!   marks bad blocks with `FRAME_NAK` and asks the sender to re-queue them.
10//! - A wave boundary (`FRAME_WAVE_DONE`) triggers reconciliation: the
11//!   receiver replies with `FRAME_REQ` (still-missing blocks) or
12//!   `FRAME_COMPLETE` (everything verified). The sender re-adds requested
13//!   blocks to its transfer queue and re-sends until the receiver is happy.
14//!
15//! All control commands (hello, directory listing, file metadata) travel over
16//! the same WebSocket; there are no separate HTTP calls on the transfer path.
17//! One connection may carry any number of transfers **sequentially** (the
18//! browser client currently opens one connection per file, which lets
19//! multiple files transfer concurrently across separate sockets).
20
21use std::collections::VecDeque;
22use std::io::Read;
23use std::sync::Arc;
24
25use axum::extract::ws::{Message, WebSocket, WebSocketUpgrade};
26use axum::extract::State;
27use axum::response::Response;
28use bytes::Bytes;
29use futures::SinkExt;
30use libfw_core::auth::{Action, AuthError};
31use libfw_core::claims::TokenClaims;
32use libfw_core::compress::{
33    CompressionFormat, MAX_FRAME_OUTPUT, compressor, decompressor_with_limit,
34};
35use libfw_core::storage::WriteMode;
36use libfw_core::ws::*;
37use libfw_core::{RangeSpec, protocol_compatible};
38
39use crate::{ServerState, validate_rel_path};
40
41/// Default block size for a WebSocket download stream (256 KiB), kept small
42/// so the browser's out-of-order reorder buffer stays modest.
43const DEFAULT_DOWNLOAD_BLOCK: u64 = 256 * 1024;
44/// Default in-flight blocks per wave when the client doesn't specify one
45/// (bounds the receiver's buffering; each wave costs one RTT, so raise it on
46/// high-latency links).
47const SENDER_WINDOW: usize = 16;
48/// Lower bound on a client-requested block size (kept low so small-block
49/// transfers and tests keep working). Combined with the `MAX_BLOCKS` cap,
50/// this still prevents a crafted `block_size: 1` on a huge file from forcing
51/// an absurd block count.
52const MIN_BLOCK_SIZE: u64 = 1024;
53/// Upper bound on a client-requested block size (per-block memory is one
54/// block per wave on the sender and receiver).
55const MAX_BLOCK_SIZE: u64 = 16 * 1024 * 1024;
56/// Upper bound on a client-requested in-flight window (bounds per-wave
57/// memory: `window × block_size`).
58const MAX_WINDOW: usize = 64;
59/// Maximum number of blocks a single transfer may have. The download sender
60/// eagerly materializes the full transfer queue (`VecDeque<u32>`, 4 bytes per
61/// block), so this bounds that plan: 2²⁴ blocks ≈ 67 MiB worst case, which
62/// covers ~4 TiB at the default 256 KiB block size.
63const MAX_BLOCKS: u64 = 1 << 24;
64
65/// axum route handler for `GET /ws`.
66///
67/// The upgrade is not gated by the `x-libfw-protocol` HTTP header (a browser
68/// WebSocket handshake cannot carry it); instead the protocol version is
69/// checked inside the `FRAME_HELLO` handshake.
70pub async fn ws_handler(
71    ws: WebSocketUpgrade,
72    State(state): State<Arc<ServerState>>,
73) -> Response {
74    ws.on_upgrade(move |socket| run_socket(socket, state))
75}
76
77// ---------------------------------------------------------------------------
78// Connection lifecycle
79// ---------------------------------------------------------------------------
80
81async fn run_socket(mut socket: WebSocket, state: Arc<ServerState>) {
82    // 1. Handshake: HELLO (protocol + token) → HELLO_OK.
83    let claims = match handshake(&mut socket, &state).await {
84        Ok(claims) => claims,
85        Err(err) => {
86            let _ = send_frame(&mut socket, error_frame("handshake", &err)).await;
87            let _ = socket.close().await;
88            return;
89        }
90    };
91
92    // 2. Control + any number of sequential transfers. All frames are binary
93    //    (first byte = frame type); Text is accepted for robustness.
94    loop {
95        let msg = match socket.recv().await {
96            Some(Ok(msg)) => msg,
97            _ => break,
98        };
99        let frame: Vec<u8> = match msg {
100            Message::Binary(data) => data.to_vec(),
101            Message::Text(text) => text.as_bytes().to_vec(),
102            Message::Close(_) => break,
103            _ => continue,
104        };
105        match frame_type(&frame) {
106            Some(FRAME_LIST_REQ) => {
107                let reply = list_reply(&state, &claims, &frame).await;
108                if send_frame(&mut socket, reply).await.is_err() {
109                    break;
110                }
111            }
112            Some(FRAME_META_REQ) => {
113                let reply = meta_reply(&state, &claims, &frame).await;
114                if send_frame(&mut socket, reply).await.is_err() {
115                    break;
116                }
117            }
118            Some(FRAME_START) => {
119                let start: StartRequest = match parse_control(&frame, FRAME_START) {
120                    Some(s) => s,
121                    None => {
122                        let _ = send_frame(
123                            &mut socket,
124                            error_frame("protocol", "malformed START"),
125                        )
126                        .await;
127                        break;
128                    }
129                };
130                match start.kind {
131                    TransferKind::Download => {
132                        run_download(&mut socket, &state, &claims, start).await;
133                    }
134                    TransferKind::Upload => {
135                        run_upload(&mut socket, &state, &claims, start).await;
136                    }
137                }
138                // Keep the connection open for further control/transfers.
139            }
140            Some(FRAME_COMPLETE) => {
141                // A client abort before any transfer started.
142                break;
143            }
144            // BLOCK/NAK/REQ outside an active transfer are out of protocol.
145            _ => {}
146        }
147    }
148}
149
150/// Perform the `FRAME_HELLO` handshake and return the verified claims.
151async fn handshake(socket: &mut WebSocket, state: &ServerState) -> Result<TokenClaims, String> {
152    let msg = match socket.recv().await {
153        Some(Ok(Message::Binary(data))) => data.to_vec(),
154        Some(Ok(Message::Text(text))) => text.as_bytes().to_vec(),
155        _ => return Err("expected FRAME_HELLO".into()),
156    };
157    let hello: Hello = parse_control(&msg, FRAME_HELLO).ok_or("expected FRAME_HELLO")?;
158    if !protocol_compatible(&hello.protocol) {
159        return Err(format!("unsupported protocol `{}`", hello.protocol));
160    }
161    let claims = state
162        .verifier
163        .verify(&hello.token)
164        .map_err(|e| format!("authentication failed: {e}"))?;
165    let ok = control_frame(FRAME_HELLO_OK, &serde_json::json!({ "ok": true }));
166    send_frame(socket, ok).await.map_err(|_| "send failed".to_string())?;
167    Ok(claims)
168}
169
170/// Send one raw frame over the socket.
171async fn send_frame(socket: &mut WebSocket, frame: Vec<u8>) -> Result<(), ()> {
172    socket
173        .send(Message::Binary(Bytes::from(frame)))
174        .await
175        .map_err(|_| ())
176}
177
178/// Send a `FRAME_COMPLETE` message.
179async fn send_complete(socket: &mut WebSocket, ok: bool, size: u64, err: Option<&str>) {
180    let msg = CompleteMessage {
181        ok,
182        size,
183        error: err.map(str::to_string),
184    };
185    let _ = send_frame(socket, control_frame(FRAME_COMPLETE, &msg)).await;
186}
187
188/// Build a `FRAME_ERROR` frame.
189fn error_frame(code: &str, message: &str) -> Vec<u8> {
190    control_frame(
191        FRAME_ERROR,
192        &ErrorMessage {
193            code: code.to_string(),
194            message: message.to_string(),
195        },
196    )
197}
198
199fn authorize(
200    state: &ServerState,
201    claims: &TokenClaims,
202    path: &str,
203    action: Action,
204) -> Result<(), String> {
205    state.authorize(claims, path, action).map_err(|err| match err {
206        AuthError::Forbidden { path, action } => {
207            format!("permission denied: {action} on `{path}`")
208        }
209        other => format!("unauthorized: {other}"),
210    })
211}
212
213// ---------------------------------------------------------------------------
214// Control: directory listing & metadata
215// ---------------------------------------------------------------------------
216
217async fn list_reply(state: &ServerState, claims: &TokenClaims, frame: &[u8]) -> Vec<u8> {
218    #[derive(serde::Deserialize)]
219    struct ListReq {
220        #[serde(default)]
221        path: String,
222    }
223    let req: ListReq = match serde_json::from_slice(frame_payload(frame)) {
224        Ok(r) => r,
225        Err(_) => return error_frame("protocol", "malformed LIST_REQ"),
226    };
227    let path = match validate_rel_path(&req.path) {
228        Ok(p) => p,
229        Err(e) => return error_frame("path", e),
230    };
231    if let Err(e) = authorize(state, claims, &path, Action::Read) {
232        return error_frame("auth", &e);
233    }
234    match state.storage.list_dir(&path).await {
235        Ok(entries) => control_frame(
236            FRAME_LIST_REPLY,
237            &serde_json::json!({ "path": req.path, "entries": entries }),
238        ),
239        Err(e) => error_frame("storage", &e.to_string()),
240    }
241}
242
243async fn meta_reply(state: &ServerState, claims: &TokenClaims, frame: &[u8]) -> Vec<u8> {
244    #[derive(serde::Deserialize)]
245    struct MetaReq {
246        #[serde(default)]
247        path: String,
248    }
249    let req: MetaReq = match serde_json::from_slice(frame_payload(frame)) {
250        Ok(r) => r,
251        Err(_) => return error_frame("protocol", "malformed META_REQ"),
252    };
253    let path = match validate_rel_path(&req.path) {
254        Ok(p) => p,
255        Err(e) => return error_frame("path", e),
256    };
257    if let Err(e) = authorize(state, claims, &path, Action::Read) {
258        return error_frame("auth", &e);
259    }
260    match state.storage.file_meta(&path).await {
261        Ok(Some(meta)) => control_frame(
262            FRAME_META_REPLY,
263            &serde_json::json!({
264                "path": meta.path,
265                "size": meta.size,
266                "mtime": meta.mtime,
267                "etag": meta.etag,
268            }),
269        ),
270        Ok(None) => error_frame("not_found", &format!("file not found: {path}")),
271        Err(e) => error_frame("storage", &e.to_string()),
272    }
273}
274
275// ---------------------------------------------------------------------------
276// Download (server is the SENDER)
277// ---------------------------------------------------------------------------
278
279/// Serve one download stream: slice `[offset, size)` into blocks and push
280/// them to the client (receiver), handling real-time NAK/REQ retransmission.
281async fn run_download(
282    socket: &mut WebSocket,
283    state: &ServerState,
284    claims: &TokenClaims,
285    start: StartRequest,
286) {
287    let path = match validate_rel_path(&start.path) {
288        Ok(p) => p,
289        Err(e) => {
290            let _ = send_frame(socket, error_frame("path", e)).await;
291            return;
292        }
293    };
294    if let Err(e) = authorize(state, claims, &path, Action::Read) {
295        let _ = send_frame(socket, error_frame("auth", &e)).await;
296        return;
297    }
298    let meta = match state.storage.file_meta(&path).await {
299        Ok(Some(m)) => m,
300        Ok(None) => {
301            let _ = send_frame(socket, error_frame("not_found", &path)).await;
302            return;
303        }
304        Err(e) => {
305            let _ = send_frame(socket, error_frame("storage", &e.to_string())).await;
306            return;
307        }
308    };
309
310    // Clamp client-supplied sizing so a crafted START can't force an absurd
311    // block count (block_size too small) or an unbounded in-flight wave
312    // (window too large). The READY reply echoes the effective values, so a
313    // legit client adapts to them automatically.
314    let block_size = if (MIN_BLOCK_SIZE..=MAX_BLOCK_SIZE).contains(&start.block_size) {
315        start.block_size
316    } else {
317        DEFAULT_DOWNLOAD_BLOCK
318    };
319    let window = if start.window > 0 {
320        (start.window as usize).clamp(1, MAX_WINDOW)
321    } else {
322        SENDER_WINDOW
323    };
324    // Whether the server actually compresses (only when the client asked AND
325    // the server's configured download compression is zrip).
326    let compress = start.compress && state.compression == CompressionFormat::Zrip;
327    let start_off = start.offset.min(meta.size);
328    let total = meta.size - start_off;
329    let total_blocks = block_count(total, block_size);
330    // Bound the eagerly-allocated transfer queue (see `MAX_BLOCKS`); reject
331    // instead of clamping so the client's block geometry stays what it asked
332    // for whenever it is sane.
333    if total_blocks as u64 > MAX_BLOCKS {
334        let _ = send_frame(socket, error_frame("too_large", "transfer too large")).await;
335        return;
336    }
337
338    let ready = ReadyReply {
339        kind: TransferKind::Download,
340        path: path.clone(),
341        size: meta.size,
342        mtime: meta.mtime,
343        etag: meta.etag.clone(),
344        compress,
345        block_size,
346        total_blocks,
347        offset: start_off,
348        received: Vec::new(),
349    };
350    if send_frame(socket, control_frame(FRAME_READY, &ready)).await.is_err() {
351        return;
352    }
353
354    // Sender transfer queue: indices to send. NAK/REQ re-add to this queue.
355    let mut queue: VecDeque<u32> = (0..total_blocks).collect();
356
357    loop {
358        // 1. Read one wave of blocks into memory, then send them.
359        //
360        // Each block is read with a FRESH reader opened at its absolute
361        // offset and drained in a tight loop. This avoids a Windows/tokio
362        // quirk where a long-lived `read_stream` reader returns early EOF
363        // once its reads are interleaved with socket awaits — each block
364        // read is independent, and memory stays bounded (one wave).
365        let mut wave: Vec<(u32, Vec<u8>, u32, u32)> = Vec::new();
366        let mut sent = 0usize;
367        while sent < window {
368            let Some(idx) = queue.pop_front() else {
369                break;
370            };
371            let (start, end) = block_bounds(idx, block_size, total);
372            let abs = block_offset(idx, block_size, start_off);
373            let mut data = vec![0u8; (end - start) as usize];
374            let mut reader = match state
375                .storage
376                .read_stream(&path, RangeSpec { start: abs, end: abs + (end - start) })
377                .await
378            {
379                Ok(r) => r,
380                Err(e) => {
381                    let _ = send_frame(socket, error_frame("storage", &e.to_string())).await;
382                    return;
383                }
384            };
385            if read_exact(&mut reader, &mut data).is_err() {
386                let _ = send_frame(socket, error_frame("io", "read failed")).await;
387                return;
388            }
389            let raw_len = data.len() as u32;
390            let payload: Vec<u8> = if compress {
391                match compress_frame(&data) {
392                    Some(p) => p,
393                    None => {
394                        let _ =
395                            send_frame(socket, error_frame("compress", "compress failed")).await;
396                        return;
397                    }
398                }
399            } else {
400                data
401            };
402            let crc = crc32(&payload);
403            wave.push((idx, payload, crc, raw_len));
404            sent += 1;
405        }
406
407        // 2. Send the wave's blocks (no per-block ack).
408        for (idx, payload, crc, raw_len) in wave {
409            let frame = block_frame(idx, crc, raw_len, &payload);
410            if send_frame(socket, frame).await.is_err() {
411                return;
412            }
413        }
414
415        // 3. Wave boundary: the receiver reconciles.
416        if send_frame(socket, wave_done_frame()).await.is_err() {
417            return;
418        }
419
420        // 4. Read events until the receiver asks for more (REQ) or is done
421        //    (COMPLETE). NAKs re-queue immediately ("实时核验 → 重传队列").
422        loop {
423            let msg = match socket.recv().await {
424                Some(Ok(msg)) => msg,
425                _ => return, // client gone
426            };
427            let frame: Vec<u8> = match msg {
428                Message::Binary(data) => data.to_vec(),
429                Message::Text(text) => text.as_bytes().to_vec(),
430                Message::Close(_) => return,
431                _ => continue,
432            };
433            match frame_type(&frame) {
434                Some(FRAME_NAK) => {
435                    if let Some(index) = parse_nak(&frame) {
436                        // Ignore out-of-range indices: `block_bounds` on such
437                        // an index underflows and would allocate a huge
438                        // buffer (crash/OOM) in the wave below.
439                        if index < total_blocks {
440                            queue.push_back(index);
441                        }
442                    }
443                }
444                Some(FRAME_REQ) => {
445                    if let Some(indices) = parse_req(&frame) {
446                        queue.extend(indices.into_iter().filter(|&i| i < total_blocks));
447                    }
448                    break; // next wave
449                }
450                Some(FRAME_COMPLETE) => return, // receiver finished → done
451                _ => {}
452            }
453        }
454    }
455}
456
457// ---------------------------------------------------------------------------
458// Upload (server is the RECEIVER)
459// ---------------------------------------------------------------------------
460
461/// Receive one upload stream: verify every block (CRC32 + bounds), write it
462/// at its absolute offset into a shared session temp, NAK bad blocks and
463/// commit once all blocks are verified.
464async fn run_upload(
465    socket: &mut WebSocket,
466    state: &ServerState,
467    claims: &TokenClaims,
468    start: StartRequest,
469) {
470    let path = match validate_rel_path(&start.path) {
471        Ok(p) => p,
472        Err(e) => {
473            let _ = send_frame(socket, error_frame("path", e)).await;
474            return;
475        }
476    };
477    if let Err(e) = authorize(state, claims, &path, Action::Write) {
478        let _ = send_frame(socket, error_frame("auth", &e)).await;
479        return;
480    }
481    if start.size > state.max_upload_size {
482        let _ = send_frame(
483            socket,
484            error_frame(
485                "too_large",
486                &format!("upload exceeds limit of {} bytes", state.max_upload_size),
487            ),
488        )
489        .await;
490        return;
491    }
492
493    let block_size = if (MIN_BLOCK_SIZE..=MAX_BLOCK_SIZE).contains(&start.block_size) {
494        start.block_size
495    } else {
496        libfw_core::CHUNK_SIZE
497    };
498    let total_blocks = block_count(start.size, block_size);
499    // Bound the receiver's verified-set allocation (see `MAX_BLOCKS`).
500    if total_blocks as u64 > MAX_BLOCKS {
501        let _ = send_frame(socket, error_frame("too_large", "transfer too large")).await;
502        return;
503    }
504    let mode = if start.mode.eq_ignore_ascii_case("create") {
505        WriteMode::Create
506    } else {
507        WriteMode::Overwrite
508    };
509    // Deterministic session id from the ETag → an interrupted upload of the
510    // same file version resumes the same shared temp on the server.
511    let session = start.etag.trim_matches('"');
512
513    let mut sink = match state.storage.write_stream_session(&path, session, mode).await {
514        Ok(s) => s,
515        Err(e) => {
516            let _ = send_frame(socket, error_frame("storage", &e.to_string())).await;
517            return;
518        }
519    };
520
521    // Seed the verified set + resume ranges from what the server already
522    // holds, so the client only retransmits the missing blocks.
523    let received = sink.received_ranges().await.unwrap_or_default();
524    let received_pairs: Vec<[u64; 2]> = received.iter().map(|r| [r.start, r.end]).collect();
525    let received_slices: Vec<(u64, u64)> = received.iter().map(|r| (r.start, r.end)).collect();
526    let mut verified = BlockSet::new(total_blocks);
527    verified.seed_from_ranges(block_size, &received_slices);
528
529    let ready = ReadyReply {
530        kind: TransferKind::Upload,
531        path: path.clone(),
532        size: start.size,
533        mtime: start.mtime,
534        etag: start.etag.clone(),
535        compress: start.compress,
536        block_size,
537        total_blocks,
538        offset: 0,
539        received: received_pairs,
540    };
541    if send_frame(socket, control_frame(FRAME_READY, &ready)).await.is_err() {
542        return; // keep the session temp for a later resume
543    }
544
545    let compress = start.compress;
546    loop {
547        let msg = match socket.recv().await {
548            Some(Ok(msg)) => msg,
549            _ => {
550                // Client gone: drop the sink WITHOUT abort so the session
551                // temp survives for a resumable retry (tus expiration).
552                return;
553            }
554        };
555        let frame: Vec<u8> = match msg {
556            Message::Binary(data) => data.to_vec(),
557            Message::Text(text) => text.as_bytes().to_vec(),
558            Message::Close(_) => {
559                // Keep the session temp for a possible resume.
560                return;
561            }
562            _ => continue,
563        };
564        match frame_type(&frame) {
565            Some(FRAME_BLOCK) => {
566                let Some(block) = parse_block(&frame) else {
567                    continue;
568                };
569                // Already-verified (duplicate / re-sent) or out of range →
570                // idempotent no-op.
571                if block.index >= total_blocks || verified.contains(block.index) {
572                    continue;
573                }
574                // Real-time verification: CRC + length + bounds.
575                let crc_ok = crc32(&block.data) == block.crc;
576                let raw: Vec<u8> = if compress {
577                    match decompress_frame(&block.data) {
578                        Ok(d) => d,
579                        Err(_) => {
580                            let _ = send_frame(socket, nak_frame(block.index)).await;
581                            continue;
582                        }
583                    }
584                } else {
585                    block.data
586                };
587                let len_ok = !compress || raw.len() as u32 == block.raw_len;
588                let abs = block_offset(block.index, block_size, 0);
589                let end = abs.saturating_add(raw.len() as u64);
590                let in_bounds = end <= start.size && end <= state.max_upload_size;
591                if !(crc_ok && len_ok && in_bounds) {
592                    // Mark bad → ask the sender to re-queue it.
593                    let _ = send_frame(socket, nak_frame(block.index)).await;
594                    continue;
595                }
596                if let Err(e) = sink.write_at(abs, &raw).await {
597                    let _ = send_frame(
598                        socket,
599                        control_frame(
600                            FRAME_COMPLETE,
601                            &CompleteMessage::err(format!("write failed: {e}")),
602                        ),
603                    )
604                    .await;
605                    let _ = sink.abort().await;
606                    return;
607                }
608                verified.insert(block.index);
609            }
610            Some(FRAME_WAVE_DONE) => {
611                // Reconciliation: everything verified → commit, else ask the
612                // sender to re-send the missing blocks.
613                let missing = verified.missing();
614                if missing.is_empty() {
615                    let len = match sink.len().await {
616                        Ok(l) => l,
617                        Err(e) => {
618                            let _ = send_complete(
619                                socket,
620                                false,
621                                0,
622                                Some(&format!("len failed: {e}")),
623                            )
624                            .await;
625                            let _ = sink.abort().await;
626                            return;
627                        }
628                    };
629                    if len != start.size {
630                        let _ = send_complete(socket, false, 0, Some("commit size mismatch")).await;
631                        let _ = sink.abort().await;
632                        return;
633                    }
634                    // `commit` consumes the sink; on failure there is nothing
635                    // left to abort (temp is best-effort).
636                    match sink.commit().await {
637                        Ok(_) => {
638                            let _ = send_complete(socket, true, start.size, None).await;
639                            return;
640                        }
641                        Err(e) => {
642                            let _ = send_complete(
643                                socket,
644                                false,
645                                0,
646                                Some(&format!("commit failed: {e}")),
647                            )
648                            .await;
649                            return;
650                        }
651                    }
652                } else {
653                    let _ = send_frame(socket, req_frame(&missing)).await;
654                }
655            }
656            Some(FRAME_COMPLETE) => {
657                // A client abort mid-upload keeps the session temp.
658                return;
659            }
660            _ => {}
661        }
662    }
663}
664
665// ---------------------------------------------------------------------------
666// Small helpers
667// ---------------------------------------------------------------------------
668
669/// Read exactly `buf.len()` bytes (or fail on early EOF) from a reader.
670fn read_exact(reader: &mut Box<dyn Read + Send>, buf: &mut [u8]) -> Result<(), ()> {
671    let mut filled = 0usize;
672    while filled < buf.len() {
673        match reader.read(&mut buf[filled..]) {
674            Ok(0) => return Err(()),
675            Ok(n) => filled += n,
676            Err(_) => return Err(()),
677        }
678    }
679    Ok(())
680}
681
682/// Compress `data` into one independent zrip frame.
683fn compress_frame(data: &[u8]) -> Option<Vec<u8>> {
684    let mut enc = compressor(CompressionFormat::Zrip).ok()?;
685    let mut out = Vec::with_capacity(data.len());
686    enc.compress(data, &mut out).ok()?;
687    enc.finish(&mut out).ok()?;
688    Some(out)
689}
690
691/// Decompress one independent zrip frame.
692fn decompress_frame(data: &[u8]) -> Result<Vec<u8>, libfw_core::StorageError> {
693    let mut dec = decompressor_with_limit(CompressionFormat::Zrip, MAX_FRAME_OUTPUT);
694    let mut out: Vec<u8> = Vec::new();
695    dec.decompress(data, &mut out)
696        .map_err(|e| libfw_core::StorageError::Other(std::io::Error::other(format!("{e}"))))?;
697    dec.finish(&mut out)
698        .map_err(|e| libfw_core::StorageError::Other(std::io::Error::other(format!("{e}"))))?;
699    Ok(out)
700}