//! Read and write the git packet line wire format without copying it.//!//! ## Examples//!//! ```//! # #[cfg(feature = "blocking-io")]//! # fn main() -> Result<(), Box<dyn std::error::Error>> {//! use std::io::Write;//!//! use gix_packetline::{//! blocking_io::{encode, StreamingPeekableIter, Writer},//! PacketLineRef,//! };//!//! let mut writer = Writer::new(Vec::new());//! writer.enable_text_mode();//! writer.write_all(b"command=ls-refs")?;//! writer.write_all(b"agent=gitoxide")?;//! encode::flush_to_write(writer.inner_mut())?;//!//! let bytes = writer.into_inner();//! let mut reader = StreamingPeekableIter::new(&bytes[..], &[PacketLineRef::Flush], false);//!//! assert_eq!(//! reader.read_line().unwrap()??.as_text().unwrap().as_bstr(),//! "command=ls-refs"//! );//! assert_eq!(//! reader.read_line().unwrap()??.as_text().unwrap().as_bstr(),//! "agent=gitoxide"//! );//! assert!(reader.read_line().is_none());//! assert_eq!(reader.stopped_at(), Some(PacketLineRef::Flush));//! # Ok(()) }//! # #[cfg(not(feature = "blocking-io"))]//! # fn main() {}//! ```//!//! ## Feature Flags#![cfg_attr( all(doc, all(doc, feature = "document-features")), doc = ::document_features::document_features!())]#![cfg_attr(all(doc, feature = "document-features"), feature(doc_cfg))]#![deny(missing_docs, unsafe_code)] use bstr::BStr; ///#[cfg(feature = "async-io")]pub mod async_io { /// pub mod encode; mod read; pub use read::StreamingPeekableIter; mod sidebands; pub use sidebands::WithSidebands; mod write; pub use write::Writer;} ///#[cfg(feature = "blocking-io")]pub mod blocking_io { /// pub mod encode; mod read; pub use read::StreamingPeekableIter; mod sidebands; pub use sidebands::WithSidebands; mod write; pub use write::Writer;} /// Various utilities for `io::Read` trait implementation.////// Only useful in conjunction with the `async-io` and `blocking-io` cargo features.pub mod read; const U16_HEX_BYTES: usize = 4;const MAX_DATA_LEN: usize = 65516;const MAX_LINE_LEN: usize = MAX_DATA_LEN + U16_HEX_BYTES;const FLUSH_LINE: &[u8] = b"0000";const DELIMITER_LINE: &[u8] = b"0001";const RESPONSE_END_LINE: &[u8] = b"0002";const ERR_PREFIX: &[u8] = b"ERR "; /// One of three sideband types allowing to multiplex information over a single connection.#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]pub enum Channel { /// The usable data itself in any format. Data = 1, /// Progress information in a user-readable format. Progress = 2, /// Error information in a user readable format. Receiving it usually terminates the connection. Error = 3,} /// A borrowed packet line as it refers to a slice of data by reference.#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]pub enum PacketLineRef<'a> { /// A chunk of raw data. Data(&'a [u8]), /// A flush packet. Flush, /// A delimiter packet. Delimiter, /// The end of the response. ResponseEnd,} impl<'a> PacketLineRef<'a> { /// Return this instance as slice if it's [`Data`](PacketLineRef::Data). pub fn as_slice(&self) -> Option<&'a [u8]> { match self { PacketLineRef::Data(d) => Some(d), PacketLineRef::Flush | PacketLineRef::Delimiter | PacketLineRef::ResponseEnd => None, } } /// Return this instance's [`as_slice()`](PacketLineRef::as_slice()) as [`BStr`]. pub fn as_bstr(&self) -> Option<&'a BStr> { self.as_slice().map(Into::into) } /// Interpret this instance's [`as_slice()`](PacketLineRef::as_slice()) as [`ErrorRef`]. /// /// This works for any data received in an error [channel](crate::Channel). /// /// Note that this creates an unchecked error using the slice verbatim, which is useful to serialize it. /// See [`check_error()`](PacketLineRef::check_error()) for a version that assures the error information is in the expected format. pub fn as_error(&self) -> Option<ErrorRef<'a>> { self.as_slice().map(ErrorRef) } /// Check this instance's [`as_slice()`](PacketLineRef::as_slice()) is a valid [`ErrorRef`] and return it. /// /// This works for any data received in an error [channel](crate::Channel). pub fn check_error(&self) -> Option<ErrorRef<'a>> { self.as_slice().and_then(|data| { if data.len() >= ERR_PREFIX.len() && &data[..ERR_PREFIX.len()] == ERR_PREFIX { Some(ErrorRef(&data[ERR_PREFIX.len()..])) } else { None } }) } /// Return this instance as text, with the trailing newline truncated if present. pub fn as_text(&self) -> Option<TextRef<'a>> { self.as_slice().map(Into::into) } /// Interpret the data in this [`slice`](PacketLineRef::as_slice()) as [`BandRef`] according to the given `kind` of channel. /// /// Note that this is only relevant in a sideband channel. /// See [`decode_band()`](PacketLineRef::decode_band()) in case `kind` is unknown. pub fn as_band(&self, kind: Channel) -> Option<BandRef<'a>> { self.as_slice().map(|d| match kind { Channel::Data => BandRef::Data(d), Channel::Progress => BandRef::Progress(d), Channel::Error => BandRef::Error(d), }) } /// Decode the band of this [`slice`](PacketLineRef::as_slice()) pub fn decode_band(&self) -> Result<BandRef<'a>, decode::band::Error> { let d = self.as_slice().ok_or(decode::band::Error::NonDataLine)?; let (&band_id, d) = d.split_first().ok_or(decode::band::Error::NonDataLine)?; Ok(match band_id { 1 => BandRef::Data(d), 2 => BandRef::Progress(d), 3 => BandRef::Error(d), band => return Err(decode::band::Error::InvalidSideBand { band_id: band }), }) }} /// A packet line representing an Error in a sideband channel.#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]pub struct ErrorRef<'a>(pub &'a [u8]); /// A packet line representing text, which may include a trailing newline.#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]pub struct TextRef<'a>(pub &'a [u8]); impl<'a> TextRef<'a> { /// Return this instance's data. pub fn as_slice(&self) -> &'a [u8] { self.0 } /// Return this instance's data as [`BStr`]. pub fn as_bstr(&self) -> &'a BStr { self.0.into() }} impl<'a> From<&'a [u8]> for TextRef<'a> { fn from(d: &'a [u8]) -> Self { let d = d.strip_suffix(b"\n").unwrap_or(d); TextRef(d) }} /// A band in a sideband channel.#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]pub enum BandRef<'a> { /// A band carrying data. Data(&'a [u8]), /// A band carrying user readable progress information. Progress(&'a [u8]), /// A band carrying user readable errors. Error(&'a [u8]),} /// Utilities to help decoding packet linespub mod decode;#[doc(inline)]pub use decode::all_at_once as decode; /// Utilities to encode different kinds of packet linespub mod encode;
//! Read and write the git packet line wire format without copying it.
//!
//! ## Examples
//!
//! ```
//! # #[cfg(feature = "blocking-io")]
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! use std::io::Write;
//!
//! use gix_packetline::{
//! blocking_io::{encode, StreamingPeekableIter, Writer},
//! PacketLineRef,
//! };
//!
//! let mut writer = Writer::new(Vec::new());
//! writer.enable_text_mode();
//! writer.write_all(b"command=ls-refs")?;
//! writer.write_all(b"agent=gitoxide")?;
//! encode::flush_to_write(writer.inner_mut())?;
//!
//! let bytes = writer.into_inner();
//! let mut reader = StreamingPeekableIter::new(&bytes[..], &[PacketLineRef::Flush], false);
//!
//! assert_eq!(
//! reader.read_line().unwrap()??.as_text().unwrap().as_bstr(),
//! "command=ls-refs"
//! );
//! assert_eq!(
//! reader.read_line().unwrap()??.as_text().unwrap().as_bstr(),
//! "agent=gitoxide"
//! );
//! assert!(reader.read_line().is_none());
//! assert_eq!(reader.stopped_at(), Some(PacketLineRef::Flush));
//! # Ok(()) }
//! # #[cfg(not(feature = "blocking-io"))]
//! # fn main() {}
//! ```
//!
//! ## Feature Flags
#![cfg_attr(
all(doc, all(doc, feature = "document-features")),
doc = ::document_features::document_features!()
)]
#![cfg_attr(all(doc, feature = "document-features"), feature(doc_cfg))]
#![deny(missing_docs, unsafe_code)]
use bstr::BStr;
///
#[cfg(feature = "async-io")]
pub mod async_io {
///
pub mod encode;
mod read;
pub use read::StreamingPeekableIter;
mod sidebands;
pub use sidebands::WithSidebands;
mod write;
pub use write::Writer;
}
///
#[cfg(feature = "blocking-io")]
pub mod blocking_io {
///
pub mod encode;
mod read;
pub use read::StreamingPeekableIter;
mod sidebands;
pub use sidebands::WithSidebands;
mod write;
pub use write::Writer;
}
/// Various utilities for `io::Read` trait implementation.
///
/// Only useful in conjunction with the `async-io` and `blocking-io` cargo features.
pub mod read;
const U16_HEX_BYTES: usize = 4;
const MAX_DATA_LEN: usize = 65516;
const MAX_LINE_LEN: usize = MAX_DATA_LEN + U16_HEX_BYTES;
const FLUSH_LINE: &[u8] = b"0000";
const DELIMITER_LINE: &[u8] = b"0001";
const RESPONSE_END_LINE: &[u8] = b"0002";
const ERR_PREFIX: &[u8] = b"ERR ";
/// One of three sideband types allowing to multiplex information over a single connection.
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Channel {
/// The usable data itself in any format.
Data = 1,
/// Progress information in a user-readable format.
Progress = 2,
/// Error information in a user readable format. Receiving it usually terminates the connection.
Error = 3,
}
/// A borrowed packet line as it refers to a slice of data by reference.
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum PacketLineRef<'a> {
/// A chunk of raw data.
Data(&'a [u8]),
/// A flush packet.
Flush,
/// A delimiter packet.
Delimiter,
/// The end of the response.
ResponseEnd,
}
impl<'a> PacketLineRef<'a> {
/// Return this instance as slice if it's [`Data`](PacketLineRef::Data).
pub fn as_slice(&self) -> Option<&'a [u8]> {
match self {
PacketLineRef::Data(d) => Some(d),
PacketLineRef::Flush | PacketLineRef::Delimiter | PacketLineRef::ResponseEnd => None,
}
}
/// Return this instance's [`as_slice()`](PacketLineRef::as_slice()) as [`BStr`].
pub fn as_bstr(&self) -> Option<&'a BStr> {
self.as_slice().map(Into::into)
}
/// Interpret this instance's [`as_slice()`](PacketLineRef::as_slice()) as [`ErrorRef`].
///
/// This works for any data received in an error [channel](crate::Channel).
///
/// Note that this creates an unchecked error using the slice verbatim, which is useful to serialize it.
/// See [`check_error()`](PacketLineRef::check_error()) for a version that assures the error information is in the expected format.
pub fn as_error(&self) -> Option<ErrorRef<'a>> {
self.as_slice().map(ErrorRef)
}
/// Check this instance's [`as_slice()`](PacketLineRef::as_slice()) is a valid [`ErrorRef`] and return it.
///
/// This works for any data received in an error [channel](crate::Channel).
pub fn check_error(&self) -> Option<ErrorRef<'a>> {
self.as_slice().and_then(|data| {
if data.len() >= ERR_PREFIX.len() && &data[..ERR_PREFIX.len()] == ERR_PREFIX {
Some(ErrorRef(&data[ERR_PREFIX.len()..]))
} else {
None
}
})
}
/// Return this instance as text, with the trailing newline truncated if present.
pub fn as_text(&self) -> Option<TextRef<'a>> {
self.as_slice().map(Into::into)
}
/// Interpret the data in this [`slice`](PacketLineRef::as_slice()) as [`BandRef`] according to the given `kind` of channel.
///
/// Note that this is only relevant in a sideband channel.
/// See [`decode_band()`](PacketLineRef::decode_band()) in case `kind` is unknown.
pub fn as_band(&self, kind: Channel) -> Option<BandRef<'a>> {
self.as_slice().map(|d| match kind {
Channel::Data => BandRef::Data(d),
Channel::Progress => BandRef::Progress(d),
Channel::Error => BandRef::Error(d),
})
}
/// Decode the band of this [`slice`](PacketLineRef::as_slice())
pub fn decode_band(&self) -> Result<BandRef<'a>, decode::band::Error> {
let d = self.as_slice().ok_or(decode::band::Error::NonDataLine)?;
let (&band_id, d) = d.split_first().ok_or(decode::band::Error::NonDataLine)?;
Ok(match band_id {
1 => BandRef::Data(d),
2 => BandRef::Progress(d),
3 => BandRef::Error(d),
band => return Err(decode::band::Error::InvalidSideBand { band_id: band }),
})
}
}
/// A packet line representing an Error in a sideband channel.
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ErrorRef<'a>(pub &'a [u8]);
/// A packet line representing text, which may include a trailing newline.
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TextRef<'a>(pub &'a [u8]);
impl<'a> TextRef<'a> {
/// Return this instance's data.
pub fn as_slice(&self) -> &'a [u8] {
self.0
}
/// Return this instance's data as [`BStr`].
pub fn as_bstr(&self) -> &'a BStr {
self.0.into()
}
}
impl<'a> From<&'a [u8]> for TextRef<'a> {
fn from(d: &'a [u8]) -> Self {
let d = d.strip_suffix(b"\n").unwrap_or(d);
TextRef(d)
}
}
/// A band in a sideband channel.
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum BandRef<'a> {
/// A band carrying data.
Data(&'a [u8]),
/// A band carrying user readable progress information.
Progress(&'a [u8]),
/// A band carrying user readable errors.
Error(&'a [u8]),
}
/// Utilities to help decoding packet lines
pub mod decode;
#[doc(inline)]
pub use decode::all_at_once as decode;
/// Utilities to encode different kinds of packet lines
pub mod encode;