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;