Skip to main content

steel_protocol/packets/game/chat/
command.rs

1use std::io::{Cursor, Read};
2
3use steel_macros::{ReadFrom, ServerPacket};
4#[expect(unused_imports)]
5use steel_registry::packets::play::S_CHAT_COMMAND;
6use steel_utils::codec::VarInt;
7use steel_utils::serial::PrefixedRead;
8
9#[derive(ServerPacket, ReadFrom)]
10#[packet_id(Play = S_CHAT_COMMAND)]
11pub struct SChatCommand {
12    #[read(as = Prefixed(VarInt))]
13    pub command: String,
14}
15
16/// Client -> Server: Executes a signed command.
17///
18/// Commands with signable arguments have each argument individually signed.
19/// This prevents tampering with command arguments.
20///
21/// Equivalent to `ServerboundChatCommandSignedPacket` in Minecraft.
22#[derive(ServerPacket, Clone, Debug)]
23pub struct SChatCommandSigned {
24    /// The command string (without leading slash)
25    pub command: String,
26
27    /// Timestamp when command was issued (milliseconds since epoch)
28    pub timestamp: i64,
29
30    /// Random salt for uniqueness
31    pub salt: i64,
32
33    /// Signatures for each command argument
34    pub argument_signatures: Vec<ArgumentSignature>,
35
36    /// Acknowledgment of previously seen messages
37    pub last_seen: LastSeenMessagesUpdate,
38}
39
40impl steel_utils::serial::ReadFrom for SChatCommandSigned {
41    fn read(reader: &mut Cursor<&[u8]>) -> std::io::Result<Self> {
42        let command = String::read_prefixed_bound::<VarInt>(reader, 256)?;
43        let timestamp = i64::read(reader)?;
44        let salt = i64::read(reader)?;
45
46        let arg_count = VarInt::read(reader)?.0 as usize;
47        if arg_count > 8 {
48            return Err(std::io::Error::new(
49                std::io::ErrorKind::InvalidData,
50                "Too many argument signatures",
51            ));
52        }
53        let mut argument_signatures = Vec::with_capacity(arg_count);
54        for _ in 0..arg_count {
55            argument_signatures.push(ArgumentSignature::read(reader)?);
56        }
57
58        let last_seen = LastSeenMessagesUpdate::read(reader)?;
59
60        Ok(Self {
61            command,
62            timestamp,
63            salt,
64            argument_signatures,
65            last_seen,
66        })
67    }
68}
69
70/// Signature for a single command argument
71#[derive(Clone, Debug)]
72pub struct ArgumentSignature {
73    /// The argument name
74    pub name: String,
75
76    /// The signature bytes (256 bytes for RSA 2048-bit)
77    pub signature: [u8; 256],
78}
79
80impl steel_utils::serial::ReadFrom for ArgumentSignature {
81    fn read(reader: &mut Cursor<&[u8]>) -> std::io::Result<Self> {
82        // Read argument name (max 16 chars)
83        let name = String::read_prefixed_bound::<VarInt>(reader, 16)?;
84
85        // Read signature
86        let mut signature = [0u8; 256];
87        reader.read_exact(&mut signature)?;
88
89        Ok(Self { name, signature })
90    }
91}
92
93/// Last seen messages update from client
94#[derive(Clone, Debug)]
95pub struct LastSeenMessagesUpdate {
96    /// Offset to advance the message window
97    pub offset: VarInt,
98
99    /// `BitSet` indicating which of the last 20 messages were acknowledged
100    /// 3 bytes = 24 bits (using 20)
101    pub acknowledged: [u8; 3],
102}
103
104impl steel_utils::serial::ReadFrom for LastSeenMessagesUpdate {
105    fn read(reader: &mut Cursor<&[u8]>) -> std::io::Result<Self> {
106        let offset = VarInt::read(reader)?;
107        let mut acknowledged = [0u8; 3];
108        reader.read_exact(&mut acknowledged)?;
109
110        Ok(Self {
111            offset,
112            acknowledged,
113        })
114    }
115}