Skip to main content

gix_packetline/
lib.rs

1//! Read and write the git packet line wire format without copying it.
2//!
3//! ## Examples
4//!
5//! ```
6//! # #[cfg(feature = "blocking-io")]
7//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
8//! use std::io::Write;
9//!
10//! use gix_packetline::{
11//!     blocking_io::{encode, StreamingPeekableIter, Writer},
12//!     PacketLineRef,
13//! };
14//!
15//! let mut writer = Writer::new(Vec::new());
16//! writer.enable_text_mode();
17//! writer.write_all(b"command=ls-refs")?;
18//! writer.write_all(b"agent=gitoxide")?;
19//! encode::flush_to_write(writer.inner_mut())?;
20//!
21//! let bytes = writer.into_inner();
22//! let mut reader = StreamingPeekableIter::new(&bytes[..], &[PacketLineRef::Flush], false);
23//!
24//! assert_eq!(
25//!     reader.read_line().unwrap()??.as_text().unwrap().as_bstr(),
26//!     "command=ls-refs"
27//! );
28//! assert_eq!(
29//!     reader.read_line().unwrap()??.as_text().unwrap().as_bstr(),
30//!     "agent=gitoxide"
31//! );
32//! assert!(reader.read_line().is_none());
33//! assert_eq!(reader.stopped_at(), Some(PacketLineRef::Flush));
34//! # Ok(()) }
35//! # #[cfg(not(feature = "blocking-io"))]
36//! # fn main() {}
37//! ```
38//!
39//! ## Feature Flags
40#![cfg_attr(
41    all(doc, all(doc, feature = "document-features")),
42    doc = ::document_features::document_features!()
43)]
44#![cfg_attr(all(doc, feature = "document-features"), feature(doc_cfg))]
45#![deny(missing_docs, rust_2018_idioms, unsafe_code)]
46
47use bstr::BStr;
48
49///
50#[cfg(feature = "async-io")]
51pub mod async_io {
52    ///
53    pub mod encode;
54    mod read;
55    pub use read::StreamingPeekableIter;
56    mod sidebands;
57    pub use sidebands::WithSidebands;
58    mod write;
59    pub use write::Writer;
60}
61
62///
63#[cfg(feature = "blocking-io")]
64pub mod blocking_io {
65    ///
66    pub mod encode;
67    mod read;
68    pub use read::StreamingPeekableIter;
69    mod sidebands;
70    pub use sidebands::WithSidebands;
71    mod write;
72    pub use write::Writer;
73}
74
75/// Various utilities for `io::Read` trait implementation.
76///
77/// Only useful in conjunction with the `async-io` and `blocking-io` cargo features.
78pub mod read;
79
80const U16_HEX_BYTES: usize = 4;
81const MAX_DATA_LEN: usize = 65516;
82const MAX_LINE_LEN: usize = MAX_DATA_LEN + U16_HEX_BYTES;
83const FLUSH_LINE: &[u8] = b"0000";
84const DELIMITER_LINE: &[u8] = b"0001";
85const RESPONSE_END_LINE: &[u8] = b"0002";
86const ERR_PREFIX: &[u8] = b"ERR ";
87
88/// One of three sideband types allowing to multiplex information over a single connection.
89#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
90#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
91pub enum Channel {
92    /// The usable data itself in any format.
93    Data = 1,
94    /// Progress information in a user-readable format.
95    Progress = 2,
96    /// Error information in a user readable format. Receiving it usually terminates the connection.
97    Error = 3,
98}
99
100/// A borrowed packet line as it refers to a slice of data by reference.
101#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
102#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
103pub enum PacketLineRef<'a> {
104    /// A chunk of raw data.
105    Data(&'a [u8]),
106    /// A flush packet.
107    Flush,
108    /// A delimiter packet.
109    Delimiter,
110    /// The end of the response.
111    ResponseEnd,
112}
113
114impl<'a> PacketLineRef<'a> {
115    /// Return this instance as slice if it's [`Data`](PacketLineRef::Data).
116    pub fn as_slice(&self) -> Option<&'a [u8]> {
117        match self {
118            PacketLineRef::Data(d) => Some(d),
119            PacketLineRef::Flush | PacketLineRef::Delimiter | PacketLineRef::ResponseEnd => None,
120        }
121    }
122    /// Return this instance's [`as_slice()`](PacketLineRef::as_slice()) as [`BStr`].
123    pub fn as_bstr(&self) -> Option<&'a BStr> {
124        self.as_slice().map(Into::into)
125    }
126    /// Interpret this instance's [`as_slice()`](PacketLineRef::as_slice()) as [`ErrorRef`].
127    ///
128    /// This works for any data received in an error [channel](crate::Channel).
129    ///
130    /// Note that this creates an unchecked error using the slice verbatim, which is useful to serialize it.
131    /// See [`check_error()`](PacketLineRef::check_error()) for a version that assures the error information is in the expected format.
132    pub fn as_error(&self) -> Option<ErrorRef<'a>> {
133        self.as_slice().map(ErrorRef)
134    }
135    /// Check this instance's [`as_slice()`](PacketLineRef::as_slice()) is a valid [`ErrorRef`] and return it.
136    ///
137    /// This works for any data received in an error [channel](crate::Channel).
138    pub fn check_error(&self) -> Option<ErrorRef<'a>> {
139        self.as_slice().and_then(|data| {
140            if data.len() >= ERR_PREFIX.len() && &data[..ERR_PREFIX.len()] == ERR_PREFIX {
141                Some(ErrorRef(&data[ERR_PREFIX.len()..]))
142            } else {
143                None
144            }
145        })
146    }
147    /// Return this instance as text, with the trailing newline truncated if present.
148    pub fn as_text(&self) -> Option<TextRef<'a>> {
149        self.as_slice().map(Into::into)
150    }
151
152    /// Interpret the data in this [`slice`](PacketLineRef::as_slice()) as [`BandRef`] according to the given `kind` of channel.
153    ///
154    /// Note that this is only relevant in a sideband channel.
155    /// See [`decode_band()`](PacketLineRef::decode_band()) in case `kind` is unknown.
156    pub fn as_band(&self, kind: Channel) -> Option<BandRef<'a>> {
157        self.as_slice().map(|d| match kind {
158            Channel::Data => BandRef::Data(d),
159            Channel::Progress => BandRef::Progress(d),
160            Channel::Error => BandRef::Error(d),
161        })
162    }
163
164    /// Decode the band of this [`slice`](PacketLineRef::as_slice())
165    pub fn decode_band(&self) -> Result<BandRef<'a>, decode::band::Error> {
166        let d = self.as_slice().ok_or(decode::band::Error::NonDataLine)?;
167        Ok(match d[0] {
168            1 => BandRef::Data(&d[1..]),
169            2 => BandRef::Progress(&d[1..]),
170            3 => BandRef::Error(&d[1..]),
171            band => return Err(decode::band::Error::InvalidSideBand { band_id: band }),
172        })
173    }
174}
175
176/// A packet line representing an Error in a sideband channel.
177#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
178#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
179pub struct ErrorRef<'a>(pub &'a [u8]);
180
181/// A packet line representing text, which may include a trailing newline.
182#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
183#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
184pub struct TextRef<'a>(pub &'a [u8]);
185
186impl<'a> TextRef<'a> {
187    /// Return this instance's data.
188    pub fn as_slice(&self) -> &'a [u8] {
189        self.0
190    }
191    /// Return this instance's data as [`BStr`].
192    pub fn as_bstr(&self) -> &'a BStr {
193        self.0.into()
194    }
195}
196
197impl<'a> From<&'a [u8]> for TextRef<'a> {
198    fn from(d: &'a [u8]) -> Self {
199        let d = if d[d.len() - 1] == b'\n' { &d[..d.len() - 1] } else { d };
200        TextRef(d)
201    }
202}
203
204/// A band in a sideband channel.
205#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
206#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
207pub enum BandRef<'a> {
208    /// A band carrying data.
209    Data(&'a [u8]),
210    /// A band carrying user readable progress information.
211    Progress(&'a [u8]),
212    /// A band carrying user readable errors.
213    Error(&'a [u8]),
214}
215
216/// Utilities to help decoding packet lines
217pub mod decode;
218#[doc(inline)]
219pub use decode::all_at_once as decode;
220
221/// Utilities to encode different kinds of packet lines
222pub mod encode;