dust_devil_core/
users.rs

1//! Constants and types for dealing with Sandstorm users, as well as implementations of
2//! [`ByteRead`] and [`ByteWrite`] for these types.
3
4use std::{
5    fmt,
6    io::{Error, ErrorKind},
7};
8
9use tokio::io::{AsyncRead, AsyncWrite};
10
11use crate::{
12    serialize::{ByteRead, ByteWrite},
13    u8_repr_enum::U8ReprEnum,
14};
15
16/// A character used to write comments on the users file.
17pub const COMMENT_PREFIX_CHAR: char = '!';
18
19/// A symbol character that identifies the admin user role.
20pub const ADMIN_PREFIX_CHAR: char = '@';
21
22/// A symbol character that identifies the regular user role.
23pub const REGULAR_PREFIX_CHAR: char = '#';
24
25/// A character used for escape sequences when specifying users.
26pub const ESCAPE_CHAR: char = '\\';
27
28/// The default user username.
29pub const DEFAULT_USER_USERNAME: &str = "admin";
30
31/// The default user password.
32pub const DEFAULT_USER_PASSWORD: &str = "admin";
33
34/// The roles a user can take.
35#[repr(u8)]
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum UserRole {
38    Admin = 0x01,
39    Regular = 0x02,
40}
41
42impl UserRole {
43    pub fn into_role_char(self) -> char {
44        match self {
45            Self::Admin => ADMIN_PREFIX_CHAR,
46            Self::Regular => REGULAR_PREFIX_CHAR,
47        }
48    }
49}
50
51impl U8ReprEnum for UserRole {
52    fn from_u8(value: u8) -> Option<Self> {
53        match value {
54            0x01 => Some(Self::Admin),
55            0x02 => Some(Self::Regular),
56            _ => None,
57        }
58    }
59
60    fn into_u8(self) -> u8 {
61        self as u8
62    }
63}
64
65impl ByteWrite for UserRole {
66    async fn write<W: AsyncWrite + Unpin + ?Sized>(&self, writer: &mut W) -> Result<(), Error> {
67        self.into_u8().write(writer).await
68    }
69}
70
71impl ByteRead for UserRole {
72    async fn read<R: AsyncRead + Unpin + ?Sized>(reader: &mut R) -> Result<Self, Error> {
73        match UserRole::from_u8(u8::read(reader).await?) {
74            Some(role) => Ok(role),
75            None => Err(Error::new(ErrorKind::InvalidData, "Invalid UserRole type byte")),
76        }
77    }
78}
79
80impl UserRole {
81    /// Gets this `UserRole` represented by a `&'static str`.
82    pub fn to_str(&self) -> &'static str {
83        match self {
84            Self::Admin => "admin",
85            Self::Regular => "regular",
86        }
87    }
88}
89
90impl fmt::Display for UserRole {
91    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92        write!(f, "{}", self.to_str())
93    }
94}
95
96/// Errors that can occur when the server loads a users file.
97#[derive(Debug)]
98pub enum UsersLoadingError {
99    IO(Error),
100    InvalidUtf8 { line_number: u32, byte_at: u64 },
101    LineTooLong { line_number: u32, byte_at: u64 },
102    ExpectedRoleCharGotEOF(u32, u32),
103    InvalidRoleChar(u32, u32, char),
104    ExpectedColonGotEOF(u32, u32),
105    EmptyUsername(u32, u32),
106    UsernameTooLong(u32, u32),
107    EmptyPassword(u32, u32),
108    PasswordTooLong(u32, u32),
109    NoUsers,
110}
111
112impl PartialEq for UsersLoadingError {
113    fn eq(&self, other: &Self) -> bool {
114        match self {
115            Self::IO(io_err) => {
116                if let Self::IO(other_err) = other {
117                    io_err.kind() == other_err.kind()
118                } else {
119                    false
120                }
121            }
122            Self::InvalidUtf8 { line_number, byte_at } => {
123                matches!(other, Self::InvalidUtf8 { line_number: a2, byte_at: b2 } if (line_number, byte_at) == (a2, b2))
124            }
125            Self::LineTooLong { line_number, byte_at } => {
126                matches!(other, Self::LineTooLong { line_number: a2, byte_at: b2 } if (line_number, byte_at) == (a2, b2))
127            }
128            Self::ExpectedRoleCharGotEOF(a, b) => matches!(other, Self::ExpectedRoleCharGotEOF(a2, b2) if (a, b) == (a2, b2)),
129            Self::InvalidRoleChar(a, b, c) => matches!(other, Self::InvalidRoleChar(a2, b2, c2) if (a, b, c) == (a2, b2, c2)),
130            Self::ExpectedColonGotEOF(a, b) => matches!(other, Self::ExpectedColonGotEOF(a2, b2) if (a, b) == (a2, b2)),
131            Self::EmptyUsername(a, b) => matches!(other, Self::EmptyUsername(a2, b2) if (a, b) == (a2, b2)),
132            Self::UsernameTooLong(a, b) => matches!(other, Self::UsernameTooLong(a2, b2) if (a, b) == (a2, b2)),
133            Self::EmptyPassword(a, b) => matches!(other, Self::EmptyPassword(a2, b2) if (a, b) == (a2, b2)),
134            Self::PasswordTooLong(a, b) => matches!(other, Self::PasswordTooLong(a2, b2) if (a, b) == (a2, b2)),
135            Self::NoUsers => matches!(other, Self::NoUsers),
136        }
137    }
138}
139
140impl From<Error> for UsersLoadingError {
141    fn from(value: Error) -> Self {
142        UsersLoadingError::IO(value)
143    }
144}
145
146impl fmt::Display for UsersLoadingError {
147    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148        match self {
149            UsersLoadingError::IO(io_error) => write!(f, "IO error: {io_error}"),
150            UsersLoadingError::InvalidUtf8 { line_number, byte_at } => write!(f, "Invalid UTF-8 at {line_number} byte {byte_at}"),
151            UsersLoadingError::LineTooLong { line_number, byte_at: _ } => write!(f, "Line {line_number} is too long"),
152            UsersLoadingError::ExpectedRoleCharGotEOF(line_number, char_at) => {
153                write!(f, "Expected role char, got EOF at {line_number}:{char_at}")
154            }
155            UsersLoadingError::InvalidRoleChar(line_number, char_at, char) => write!(
156                f,
157                "Expected role char ('{ADMIN_PREFIX_CHAR}' or '{REGULAR_PREFIX_CHAR}'), got '{char}' at {line_number}:{char_at}"
158            ),
159            UsersLoadingError::ExpectedColonGotEOF(line_number, char_at) => {
160                write!(f, "Unexpected EOF (expected colon ':' after name) at {line_number}:{char_at}")
161            }
162            UsersLoadingError::EmptyUsername(line_number, char_at) => write!(f, "Empty username field at {line_number}:{char_at}"),
163            UsersLoadingError::UsernameTooLong(line_number, char_at) => write!(f, "Username too long at {line_number}:{char_at}"),
164            UsersLoadingError::EmptyPassword(line_number, char_at) => write!(f, "Empty password field at {line_number}:{char_at}"),
165            UsersLoadingError::PasswordTooLong(line_number, char_at) => write!(f, "Password too long at {line_number}:{char_at}"),
166            UsersLoadingError::NoUsers => write!(f, "No users"),
167        }
168    }
169}
170
171impl ByteWrite for UsersLoadingError {
172    async fn write<W: AsyncWrite + Unpin + ?Sized>(&self, writer: &mut W) -> Result<(), Error> {
173        match self {
174            UsersLoadingError::IO(io_error) => (1u8, io_error).write(writer).await,
175            UsersLoadingError::InvalidUtf8 { line_number, byte_at } => (2u8, line_number, byte_at).write(writer).await,
176            UsersLoadingError::LineTooLong { line_number, byte_at } => (3u8, line_number, byte_at).write(writer).await,
177            UsersLoadingError::ExpectedRoleCharGotEOF(line_number, char_at) => (4u8, line_number, char_at).write(writer).await,
178            UsersLoadingError::InvalidRoleChar(line_number, char_at, char) => (5u8, line_number, char_at, *char).write(writer).await,
179            UsersLoadingError::ExpectedColonGotEOF(line_number, char_at) => (6u8, line_number, char_at).write(writer).await,
180            UsersLoadingError::EmptyUsername(line_number, char_at) => (7u8, line_number, char_at).write(writer).await,
181            UsersLoadingError::UsernameTooLong(line_number, char_at) => (8u8, line_number, char_at).write(writer).await,
182            UsersLoadingError::EmptyPassword(line_number, char_at) => (9u8, line_number, char_at).write(writer).await,
183            UsersLoadingError::PasswordTooLong(line_number, char_at) => (10u8, line_number, char_at).write(writer).await,
184            UsersLoadingError::NoUsers => 11u8.write(writer).await,
185        }
186    }
187}
188
189impl ByteRead for UsersLoadingError {
190    async fn read<R: AsyncRead + Unpin + ?Sized>(reader: &mut R) -> Result<Self, Error> {
191        let t = u8::read(reader).await?;
192
193        match t {
194            1 => Ok(UsersLoadingError::IO(Error::read(reader).await?)),
195            2 => Ok(UsersLoadingError::InvalidUtf8 {
196                line_number: u32::read(reader).await?,
197                byte_at: u64::read(reader).await?,
198            }),
199            3 => Ok(UsersLoadingError::LineTooLong {
200                line_number: u32::read(reader).await?,
201                byte_at: u64::read(reader).await?,
202            }),
203            4 => Ok(UsersLoadingError::ExpectedRoleCharGotEOF(
204                u32::read(reader).await?,
205                u32::read(reader).await?,
206            )),
207            5 => Ok(UsersLoadingError::InvalidRoleChar(
208                u32::read(reader).await?,
209                u32::read(reader).await?,
210                char::read(reader).await?,
211            )),
212            6 => Ok(UsersLoadingError::ExpectedColonGotEOF(
213                u32::read(reader).await?,
214                u32::read(reader).await?,
215            )),
216            7 => Ok(UsersLoadingError::EmptyUsername(u32::read(reader).await?, u32::read(reader).await?)),
217            8 => Ok(UsersLoadingError::UsernameTooLong(
218                u32::read(reader).await?,
219                u32::read(reader).await?,
220            )),
221            9 => Ok(UsersLoadingError::EmptyPassword(u32::read(reader).await?, u32::read(reader).await?)),
222            10 => Ok(UsersLoadingError::PasswordTooLong(
223                u32::read(reader).await?,
224                u32::read(reader).await?,
225            )),
226            11 => Ok(UsersLoadingError::NoUsers),
227            _ => Err(Error::new(ErrorKind::InvalidData, "Invalid UsersLoadingError type byte")),
228        }
229    }
230}