sftp-protocol 0.1.0

A pure Rust implementation of the SFTP protocol
Documentation
use camino::{Utf8Path, Utf8PathBuf};
use nom::Err::Failure;
use nom::error::Error as NomError;
use nom::error::ErrorKind as NomErrorKind;
use nom::error::FromExternalError;
use nom::IResult;
use nom::number::complete::be_u32;
use nom::bytes::complete::take;
use serde::ser::SerializeTuple;
use serde::Serialize;
use serde::Serializer;
use uuid::Uuid;

#[cfg(test)]
use proptest::strategy::Strategy;

pub fn vec_u8_as_slice<S: Serializer>(elements: &Vec<u8>, serializer: S) -> Result<S::Ok, S::Error> {
	let mut tuple = serializer.serialize_tuple(elements.len())?;
	for e in elements {
		tuple.serialize_element(e)?;
	}
	tuple.end()
}

pub fn vec_with_u32_length<S: Serializer, T: Serialize>(elements: &Vec<T>, serializer: S) -> Result<S::Ok, S::Error> {
	let mut seq = serializer.serialize_tuple(elements.len() + 1)?;
	seq.serialize_element(&(elements.len() as u32))?;
	for e in elements {
		seq.serialize_element(e)?;
	}
	seq.end()
}

pub fn path_with_u32_length<S: Serializer>(s: &Utf8Path, serializer: S) -> Result<S::Ok, S::Error> {
	str_with_u32_length(s.as_str(), serializer)
}

pub fn str_with_u32_length<S: Serializer>(s: &str, serializer: S) -> Result<S::Ok, S::Error> {
	let slice = s.as_bytes();
	let mut seq = serializer.serialize_tuple(slice.len() + 1)?;
	seq.serialize_element(&(slice.len() as u32))?;
	for c in slice {
		seq.serialize_element(c)?;
	}
	seq.end()
}

pub fn serialize_uuid<S: Serializer>(uuid: &Uuid, serializer: S) -> Result<S::Ok, S::Error> {
	str_with_u32_length(&uuid.to_string(), serializer)
}

#[inline]
pub fn parse_u8_vec(i: &[u8]) -> IResult<&[u8], Vec<u8>> {
	let (i, len) = be_u32(i)?;
	if(i.len() < len as usize) {
		return Ok((i, Vec::new()));
	}
	let (i, slice) = take(len)(i)?;
	Ok((i, Vec::from(slice)))
}

#[inline]
pub fn parse_vec<'a, T: nom_derive::Parse<&'a [u8]>>(i: &'a [u8]) -> IResult<&'a [u8], Vec<T>> {
	let (mut i, len) = be_u32(i)?;
	let mut s;
	let mut vec = Vec::with_capacity(len as usize);
	for _ in 0..len {
		(i, s) = T::parse(i)?;
		vec.push(s)
	}
	Ok((i, vec))
}

// TODO:  Generic over I instead of hardcoded &[u8]
pub fn parse_str(i: &[u8]) -> IResult<&[u8], &str> {
	let (i, len) = be_u32(i)?;
	let (i, string) = take(len)(i)?;
	Ok((i, std::str::from_utf8(string).map_err(|e| Failure(NomError::from_external_error(i, NomErrorKind::Char, e)))?))
}

#[inline]
pub fn parse_string(i: &[u8]) -> IResult<&[u8], String> {
	let (i, string) = parse_str(i)?;
	Ok((i, string.to_string()))
}

#[inline]
pub fn parse_path(i: &[u8]) -> IResult<&[u8], Utf8PathBuf> {
	let (i, string) = parse_str(i)?;
	Ok((i, string.into()))
}

#[inline]
pub fn parse_uuid(i: &[u8]) -> IResult<&[u8], Uuid> {
	let (i, s) = parse_str(i)?;
	Ok((i, Uuid::parse_str(s).map_err(|e| Failure(NomError::from_external_error(i, NomErrorKind::Char, e)))?))
}

#[cfg(test)]
pub(crate) fn arbitrary_uuid() -> impl Strategy<Value = Uuid> {
	proptest::prelude::any::<u128>().prop_map(Uuid::from_u128)
}