git_zero_server/stream/preparation.rs
1//! A transport-silent handoff between selection and asynchronous pack storage.
2//!
3//! The host owns this value and its caller buffers across awaits. Neither the
4//! request body nor the temporary validation index is borrowed after preparation.
5//! Keep the authorized repository and selected/control buffers immutable until
6//! attachment or cancellation. Independent requests require independent storage.
7use super::{Body, ResponseEncoder};
8use git_zero_core::{
9 Error, ObjectId, Repository, Result,
10 cache::selected::{Key, Selection},
11 pack,
12};
13
14/// A prepared response whose raw pack has not yet been exposed to transport.
15/// Only raw PACK bytes may be persisted; request-specific ACKs, boundaries and
16/// sideband framing stay in the retained response encoder.
17pub struct PreparedResponse<'a> {
18 encoder: ResponseEncoder<'a>,
19 pack: Option<pack::Encoder<'a>>,
20 key: Option<Key>,
21}
22
23impl<'a> PreparedResponse<'a> {
24 /// Consume a freshly prepared, unread response. Authorization, selection,
25 /// graph limits and control-buffer admission must already have succeeded.
26 /// A response without a pack (including negotiation/probes) has no cache key.
27 pub fn new(
28 mut encoder: ResponseEncoder<'a>,
29 repository: &impl Repository,
30 ids: &mut [ObjectId],
31 ) -> Result<Self> {
32 if encoder.started || encoder.cancelled {
33 return Err(Error::InvalidRequest);
34 }
35 let (pack, key) = match core::mem::replace(&mut encoder.body, Body::Buffered) {
36 Body::Pack(pack) => {
37 let selection = pack.selection().map_or(Selection::All, Selection::Indices);
38 let key = Key::derive(repository, selection, ids)?;
39 (Some(pack), Some(key))
40 }
41 body => {
42 encoder.body = body;
43 (None, None)
44 }
45 };
46 Ok(Self { encoder, pack, key })
47 }
48
49 pub fn key(&self) -> Option<Key> {
50 self.key
51 }
52
53 pub fn response(&self) -> crate::Response {
54 self.encoder.response()
55 }
56
57 /// Generate raw bytes on a miss without exposing any HTTP/protocol output.
58 /// Pulls remain bounded by `output`; zero denotes complete PACK EOF. The
59 /// immutable repository must be the one used to prepare this response.
60 /// Discard staging on error. A fresh preparation is needed to restart it.
61 pub fn read_pack(&mut self, repository: &impl Repository, output: &mut [u8]) -> Result<usize> {
62 self.pack
63 .as_mut()
64 .ok_or(Error::InvalidRequest)?
65 .read(repository, output)
66 }
67
68 /// Verify framing, checksums, canonical IDs and the exact selected set before
69 /// returning an ordinary response cursor. Works for hits and generated misses.
70 /// Cache bytes are borrowed and must remain immutable through transport EOF.
71 /// Publication must be awaited separately before exposing the returned cursor.
72 pub fn attach(
73 mut self,
74 bytes: &'a [u8],
75 ids: &mut [ObjectId],
76 byte_limit: usize,
77 ) -> Result<ResponseEncoder<'a>> {
78 self.key
79 .ok_or(Error::InvalidRequest)?
80 .verify_pack(bytes, ids, byte_limit)?;
81 self.encoder.body = Body::Cached { bytes, position: 0 };
82 Ok(self.encoder)
83 }
84
85 /// Finish a non-pack response without consulting storage. Pack responses
86 /// require authenticated attachment, even after complete miss generation.
87 pub fn without_pack(self) -> Result<ResponseEncoder<'a>> {
88 if self.key.is_some() {
89 return Err(Error::InvalidRequest);
90 }
91 Ok(self.encoder)
92 }
93}