sftp-protocol 0.1.0

A pure Rust implementation of the SFTP protocol
Documentation
use camino::Utf8Path;
use nom_derive::Parse;
use serde::Serialize;

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

pub mod kind;
use kind::PacketType;

pub mod init;
use init::Init;
pub mod version;
use version::Version;
pub mod open;
use open::Open;
pub mod close;
use close::Close;
pub mod read;
use read::Read;
pub mod write;
use write::Write;
pub mod lstat;
use lstat::Lstat;
pub mod fstat;
use fstat::Fstat;
pub mod setstat;
use setstat::SetStat;
pub mod fsetstat;
use fsetstat::FSetStat;
pub mod opendir;
use opendir::OpenDir;
pub mod readdir;
use readdir::ReadDir;
pub mod remove;
use remove::Remove;
pub mod mkdir;
use mkdir::MkDir;
pub mod rmdir;
use rmdir::RmDir;
pub mod realpath;
use realpath::RealPath;
pub mod stat;
use stat::Stat;
pub mod rename;
use rename::Rename;
pub mod readlink;
use readlink::ReadLink;
pub mod symlink;
use symlink::Symlink;
pub mod status;
use status::Status;
use status::StatusType;
pub mod handle;
use handle::Handle;
pub mod data;
use data::Data;
pub mod name;
use name::Name;
pub mod attrs;
use attrs::Attrs;
pub mod extended;
use extended::Request as ExtendedRequest;
use extended::Response as ExtendedResponse;

#[derive(Debug, Nom, Serialize)]
#[nom(BigEndian)]
pub struct PacketRaw<'a> {
	pub length: u32,
	pub kind: PacketType,
	#[nom(Take="i.len()")]
	pub raw_payload: &'a [u8]
}

#[derive(Clone, Debug, Eq, PartialEq, Nom, Serialize)]
#[nom(BigEndian)]
pub struct PacketHeader {
	pub length: u32,
	pub kind: PacketType
}

#[derive(Debug, Eq, PartialEq, Nom, Serialize)]
#[nom(BigEndian)]
pub struct Packet {
	pub header: PacketHeader,
	#[nom(Parse="{ |i| Payload::parse(i, header.kind) }")]
	pub payload: Payload
}

#[cfg(test)]
impl proptest::arbitrary::Arbitrary for Packet {
	type Parameters = ();
	type Strategy = proptest::strategy::BoxedStrategy<Self>;
	fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
		Payload::arbitrary_with(()).prop_map(|payload| payload.into_packet()).boxed()
	}
}

pub trait PayloadTrait : Serialize + Into<Payload> {
	const Type: PacketType;
	fn binsize(&self) -> u32;

	fn header(&self) -> PacketHeader {
		PacketHeader{
			length: 1 + self.binsize(),
			kind: Self::Type
		}
	}

	fn into_packet(self) -> Packet {
		Packet{
			header: self.header(),
			payload: self.into()
		}
	}
}

#[derive(Debug, Eq, PartialEq, Nom, Serialize)]
#[nom(Selector = "PacketType")]
#[serde(untagged)]
#[cfg_attr(test, derive(test_strategy::Arbitrary))]
pub enum Payload {
	#[nom(Selector = "PacketType::Init")]
	Init(Init),
	#[nom(Selector = "PacketType::Version")]
	Version(Version),
	#[nom(Selector = "PacketType::Open")]
	Open(Open),
	#[nom(Selector = "PacketType::Close")]
	Close(Close),
	#[nom(Selector = "PacketType::Read")]
	Read(Read),
	#[nom(Selector = "PacketType::Write")]
	Write(Write),
	#[nom(Selector = "PacketType::Lstat")]
	Lstat(Lstat),
	#[nom(Selector = "PacketType::Fstat")]
	Fstat(Fstat),
	#[nom(Selector = "PacketType::SetStat")]
	SetStat(SetStat),
	#[nom(Selector = "PacketType::FSetStat")]
	FSetStat(FSetStat),
	#[nom(Selector = "PacketType::OpenDir")]
	OpenDir(OpenDir),
	#[nom(Selector = "PacketType::ReadDir")]
	ReadDir(ReadDir),
	#[nom(Selector = "PacketType::Remove")]
	Remove(Remove),
	#[nom(Selector = "PacketType::MkDir")]
	MkDir(MkDir),
	#[nom(Selector = "PacketType::RmDir")]
	RmDir(RmDir),
	#[nom(Selector = "PacketType::RealPath")]
	RealPath(RealPath),
	#[nom(Selector = "PacketType::Stat")]
	Stat(Stat),
	#[nom(Selector = "PacketType::Rename")]
	Rename(Rename),
	#[nom(Selector = "PacketType::ReadLink")]
	ReadLink(ReadLink),
	#[nom(Selector = "PacketType::Symlink")]
	Symlink(Symlink),
	#[nom(Selector = "PacketType::Status")]
	Status(Status),
	#[nom(Selector = "PacketType::Handle")]
	Handle(Handle),
	#[nom(Selector = "PacketType::Data")]
	Data(Data),
	#[nom(Selector = "PacketType::Name")]
	Name(Name),
	#[nom(Selector = "PacketType::Attrs")]
	Attrs(Attrs),
	#[nom(Selector = "PacketType::Extended")]
	Extended(ExtendedRequest),
	#[nom(Selector = "PacketType::ExtendedReply")]
	ExtendedReply(ExtendedResponse)
}

impl Payload {
	pub fn init(version: u32, extension_data: Vec<u8>) -> Self {
		Self::Init(Init{
			version,
			extension_data
		})
	}

	pub fn version(version: u32, extension_data: Vec<u8>) -> Self {
		Self::Version(Version{
			version,
			extension_data
		})
	}

	pub fn real_path(id: u32, path: impl AsRef<Utf8Path>) -> Self {
		Self::RealPath(RealPath{
			id,
			path: path.as_ref().to_path_buf()
		})
	}

	pub fn status(id: u32, status: StatusType, message: impl AsRef<str>) -> Self {
		Self::Status(Status{
			id,
			status,
			message: message.as_ref().to_string(),
			language: "en-US".to_string()
		})
	}

	pub fn handle(id: u32) -> Handle {
		Handle{
			id,
			handle: uuid::Uuid::new_v4()
		}
	}

	pub fn name(id: u32) -> Name {
		Name{
			id,
			files: Vec::with_capacity(1)
		}
	}

	pub fn attrs(id: u32) -> Attrs {
		Attrs{
			id,
			attrs: FileAttributes::new()
		}
	}

	// TODO:  Configurable limit for size
	pub fn data_with_size(id: u32, size: u32) -> Data {
		Data{
			id,
			data: vec![0; size as usize]
		}
	}

	pub fn into_packet(self) -> Packet {
		match self {
			Self::Init(p) => p.into_packet(),
			Self::Version(p) => p.into_packet(),
			Self::Open(p) => p.into_packet(),
			Self::Close(p) => p.into_packet(),
			Self::Read(p) => p.into_packet(),
			Self::Write(p) => p.into_packet(),
			Self::Lstat(p) => p.into_packet(),
			Self::Fstat(p) => p.into_packet(),
			Self::SetStat(p) => p.into_packet(),
			Self::FSetStat(p) => p.into_packet(),
			Self::OpenDir(p) => p.into_packet(),
			Self::ReadDir(p) => p.into_packet(),
			Self::Remove(p) => p.into_packet(),
			Self::MkDir(p) => p.into_packet(),
			Self::RmDir(p) => p.into_packet(),
			Self::RealPath(p) => p.into_packet(),
			Self::Stat(p) => p.into_packet(),
			Self::Rename(p) => p.into_packet(),
			Self::ReadLink(p) => p.into_packet(),
			Self::Symlink(p) => p.into_packet(),
			Self::Status(p) => p.into_packet(),
			Self::Handle(p) => p.into_packet(),
			Self::Data(p) => p.into_packet(),
			Self::Name(p) => p.into_packet(),
			Self::Attrs(p) => p.into_packet(),
			Self::Extended(p) => p.into_packet(),
			Self::ExtendedReply(p) => p.into_packet()
		}
	}
}