xash3d_protocol/
admin.rs

1// SPDX-License-Identifier: LGPL-3.0-only
2// SPDX-FileCopyrightText: 2023 Denis Drakhnia <numas13@gmail.com>
3
4//! Admin packets.
5
6use crate::cursor::{Cursor, CursorMut};
7use crate::wrappers::Hide;
8use crate::{CursorError, Error};
9
10/// Default hash length.
11pub const HASH_LEN: usize = 64;
12/// Default hash key.
13pub const HASH_KEY: &str = "Half-Life";
14/// Default hash personality.
15pub const HASH_PERSONAL: &str = "Freeman";
16
17/// Admin challenge request.
18#[derive(Clone, Debug, PartialEq)]
19pub struct AdminChallenge;
20
21impl AdminChallenge {
22    /// Packet header.
23    pub const HEADER: &'static [u8] = b"adminchallenge";
24
25    /// Decode packet from `src`.
26    pub fn decode(src: &[u8]) -> Result<Self, Error> {
27        if src == Self::HEADER {
28            Ok(Self)
29        } else {
30            Err(CursorError::Expect)?
31        }
32    }
33
34    /// Encode packet to `buf`.
35    pub fn encode(&self, buf: &mut [u8]) -> Result<usize, Error> {
36        Ok(CursorMut::new(buf).put_bytes(Self::HEADER)?.pos())
37    }
38}
39
40/// Admin command.
41#[derive(Clone, Debug, PartialEq)]
42pub struct AdminCommand<'a> {
43    /// A number received in admin challenge response.
44    pub master_challenge: u32,
45    /// A password hash mixed with a challenge number received in admin challenge response.
46    pub hash: Hide<&'a [u8]>,
47    /// A command to execute on a master server.
48    pub command: &'a str,
49}
50
51impl<'a> AdminCommand<'a> {
52    /// Packet header.
53    pub const HEADER: &'static [u8] = b"admin";
54
55    /// Creates a new `AdminCommand`.
56    pub fn new(master_challenge: u32, hash: &'a [u8], command: &'a str) -> Self {
57        Self {
58            master_challenge,
59            hash: Hide(hash),
60            command,
61        }
62    }
63
64    /// Decode packet from `src` with specified hash length.
65    pub fn decode_with_hash_len(hash_len: usize, src: &'a [u8]) -> Result<Self, Error> {
66        let mut cur = Cursor::new(src);
67        cur.expect(Self::HEADER)?;
68        let master_challenge = cur.get_u32_le()?;
69        let hash = Hide(cur.get_bytes(hash_len)?);
70        let command = cur.get_str(cur.remaining())?;
71        cur.expect_empty()?;
72        Ok(Self {
73            master_challenge,
74            hash,
75            command,
76        })
77    }
78
79    /// Decode packet from `src`.
80    #[inline]
81    pub fn decode(src: &'a [u8]) -> Result<Self, Error> {
82        Self::decode_with_hash_len(HASH_LEN, src)
83    }
84
85    /// Encode packet to `buf`.
86    pub fn encode(&self, buf: &mut [u8]) -> Result<usize, Error> {
87        Ok(CursorMut::new(buf)
88            .put_bytes(Self::HEADER)?
89            .put_u32_le(self.master_challenge)?
90            .put_bytes(&self.hash)?
91            .put_str(self.command)?
92            .pos())
93    }
94}
95
96/// Admin packet.
97#[derive(Clone, Debug, PartialEq)]
98pub enum Packet<'a> {
99    /// Admin challenge request.
100    AdminChallenge,
101    /// Admin command.
102    AdminCommand(AdminCommand<'a>),
103}
104
105impl<'a> Packet<'a> {
106    /// Decode packet from `src` with specified hash length.
107    pub fn decode(hash_len: usize, src: &'a [u8]) -> Result<Option<Self>, Error> {
108        if src.starts_with(AdminChallenge::HEADER) {
109            AdminChallenge::decode(src).map(|_| Self::AdminChallenge)
110        } else if src.starts_with(AdminCommand::HEADER) {
111            AdminCommand::decode_with_hash_len(hash_len, src).map(Self::AdminCommand)
112        } else {
113            return Ok(None);
114        }
115        .map(Some)
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[test]
124    fn admin_challenge() {
125        let p = AdminChallenge;
126        let mut buf = [0; 512];
127        let n = p.encode(&mut buf).unwrap();
128        assert_eq!(
129            Packet::decode(HASH_LEN, &buf[..n]),
130            Ok(Some(Packet::AdminChallenge))
131        );
132    }
133
134    #[test]
135    fn admin_command() {
136        let p = AdminCommand::new(0x12345678, &[1; HASH_LEN], "foo bar baz");
137        let mut buf = [0; 512];
138        let n = p.encode(&mut buf).unwrap();
139        assert_eq!(
140            Packet::decode(HASH_LEN, &buf[..n]),
141            Ok(Some(Packet::AdminCommand(p)))
142        );
143    }
144}