dust_devil_core/sandstorm/
mod.rs

1//! A full implementation of the Sandstorm protocol.
2//!
3//! # Provided types & serialization
4//! This crate provides types for the handshake, events, and both requests and responses of the
5//! Sandstorm protocol, alongside implementations of [`ByteRead`] and [`ByteWrite`] for them.
6//!
7//! Note however that the implementations of read and write are not truly mirrors, as the writes
8//! include writing the command type byte (from [`SandstormCommandType`]), while the reads do not
9//! read this value, and expect you to handle it separately.
10//!
11//! # Ref types
12//! Some of these types need to store strings or lists (for example, [`AddUserRequest`] or
13//! [`ListSocks5SocketsResponse`]), and do so with owned types (for those examples, [`String`] and
14//! [`Vec<T>`] respectively). However, if a client wants to write an [`AddUserRequest`] with the
15//! username coming from a `&str`, it would be inefficient if they have to turn that `&str` into an
16//! owned [`String`]. For these cases, "ref" types are provided ([`AddUserRequestRef`] and
17//! [`ListSocks5SocketsResponseRef`]), which make use of `&str` and `&[T]` and allow writing (but
18//! not reading) these requests or responses without needing additional memory allocations.
19//!
20//! # Usage
21//! Since these types implement [`ByteRead`] and [`ByteWrite`], they are all easily used in the
22//! same fashion:
23//! ```ignore
24//! // On the client:
25//! SetBufferSizeRequest(4096).write(writer).await?;
26//!
27//! let command_type = SandstormCommandType::read(reader).await?;
28//! if command_type != SandstormCommandType::SetBufferSize {
29//!     panic!("Server returned wrong response type!");
30//! }
31//!
32//! let result = SetBufferSizeResponse::read(reader).await?;
33//! match result.0 {
34//!     true => println!("Buffer size updated successfully"),
35//!     false => println!("Could not update buffer size!"),
36//! }
37//!
38//! // On the server:
39//! let command_type = SandstormCommandType::read(reader).await?;
40//! match command_type {
41//!     SandstormCommandType::SetBufferSize => {
42//!         let request = SetBufferSizeRequest::read(reader).await?;
43//!         let status: bool = server.set_buffer_size(request.0);
44//!         SetBufferSizeResponse(status).write(writer).await?;
45//!     }
46//!     ... // Implement other command types
47//! }
48//! ```
49//!
50//! This example shows how one could implement simple, non-pipelined requests and responses. In
51//! reality though, the Sandstorm protocol supports pipelining and asynchronous requests. The
52//! responses for different simultaneous requests may not even come back in the same order, leading
53//! to a faster protocol, but at the cost of the complexity of implementations.
54
55use std::io::{Error, ErrorKind};
56
57use tokio::io::{AsyncRead, AsyncWrite};
58
59use crate::{
60    serialize::{ByteRead, ByteWrite},
61    u8_repr_enum::U8ReprEnum,
62};
63
64mod auth_methods;
65mod buffer_size;
66mod event_stream;
67mod event_stream_config;
68mod handshake;
69mod meow;
70mod sandstorm_sockets;
71mod shutdown;
72mod socks5_sockets;
73mod users;
74
75pub use auth_methods::*;
76pub use buffer_size::*;
77pub use event_stream::*;
78pub use event_stream_config::*;
79pub use handshake::*;
80pub use meow::*;
81pub use sandstorm_sockets::*;
82pub use shutdown::*;
83pub use socks5_sockets::*;
84pub use users::*;
85
86/// The Sandstorm command types, and their identifying `u8` value.
87#[repr(u8)]
88#[derive(Debug, Clone, Copy, PartialEq, Eq)]
89pub enum SandstormCommandType {
90    Shutdown = 0x00,
91    EventStreamConfig = 0x01,
92    EventStream = 0x02,
93    ListSocks5Sockets = 0x03,
94    AddSocks5Socket = 0x04,
95    RemoveSocks5Socket = 0x05,
96    ListSandstormSockets = 0x06,
97    AddSandstormSocket = 0x07,
98    RemoveSandstormSocket = 0x08,
99    ListUsers = 0x09,
100    AddUser = 0x0A,
101    UpdateUser = 0x0B,
102    DeleteUser = 0x0C,
103    ListAuthMethods = 0x0D,
104    ToggleAuthMethod = 0x0E,
105    RequestCurrentMetrics = 0x0F,
106    GetBufferSize = 0x10,
107    SetBufferSize = 0x11,
108    Meow = 0xFF,
109}
110
111impl U8ReprEnum for SandstormCommandType {
112    fn from_u8(value: u8) -> Option<Self> {
113        match value {
114            0x00 => Some(Self::Shutdown),
115            0x01 => Some(Self::EventStreamConfig),
116            0x02 => Some(Self::EventStream),
117            0x03 => Some(Self::ListSocks5Sockets),
118            0x04 => Some(Self::AddSocks5Socket),
119            0x05 => Some(Self::RemoveSocks5Socket),
120            0x06 => Some(Self::ListSandstormSockets),
121            0x07 => Some(Self::AddSandstormSocket),
122            0x08 => Some(Self::RemoveSandstormSocket),
123            0x09 => Some(Self::ListUsers),
124            0x0A => Some(Self::AddUser),
125            0x0B => Some(Self::UpdateUser),
126            0x0C => Some(Self::DeleteUser),
127            0x0D => Some(Self::ListAuthMethods),
128            0x0E => Some(Self::ToggleAuthMethod),
129            0x0F => Some(Self::RequestCurrentMetrics),
130            0x10 => Some(Self::GetBufferSize),
131            0x11 => Some(Self::SetBufferSize),
132            0xFF => Some(Self::Meow),
133            _ => None,
134        }
135    }
136
137    fn into_u8(self) -> u8 {
138        self as u8
139    }
140}
141
142impl ByteWrite for SandstormCommandType {
143    async fn write<W: AsyncWrite + Unpin + ?Sized>(&self, writer: &mut W) -> Result<(), Error> {
144        self.into_u8().write(writer).await
145    }
146}
147
148impl ByteRead for SandstormCommandType {
149    async fn read<R: AsyncRead + Unpin + ?Sized>(reader: &mut R) -> Result<Self, Error> {
150        match SandstormCommandType::from_u8(u8::read(reader).await?) {
151            Some(value) => Ok(value),
152            None => Err(Error::new(ErrorKind::InvalidData, "Invalid SandstormCommandType type byte")),
153        }
154    }
155}
156
157/// The result of a remove socket operation. This is a common type shared by the
158/// socks5-sockets-type requests and the sandstorm-sockets-type requests.
159#[repr(u8)]
160#[derive(Debug, Clone, Copy, PartialEq, Eq)]
161pub enum RemoveSocketResponse {
162    Ok = 0x00,
163    SocketNotFound = 0x01,
164}
165
166impl U8ReprEnum for RemoveSocketResponse {
167    fn from_u8(value: u8) -> Option<Self> {
168        match value {
169            0x00 => Some(Self::Ok),
170            0x01 => Some(Self::SocketNotFound),
171            _ => None,
172        }
173    }
174
175    fn into_u8(self) -> u8 {
176        self as u8
177    }
178}
179
180impl ByteRead for RemoveSocketResponse {
181    async fn read<R: AsyncRead + Unpin + ?Sized>(reader: &mut R) -> Result<Self, Error> {
182        match Self::from_u8(u8::read(reader).await?) {
183            Some(value) => Ok(value),
184            None => Err(Error::new(ErrorKind::InvalidData, "Invalid RemoveSocketResponse type byte")),
185        }
186    }
187}
188
189impl ByteWrite for RemoveSocketResponse {
190    async fn write<W: AsyncWrite + Unpin + ?Sized>(&self, writer: &mut W) -> Result<(), Error> {
191        self.into_u8().write(writer).await
192    }
193}