minetest_protocol/wire/
command.rs

1use super::audit::audit_command;
2use super::deser::Deserialize;
3use super::deser::DeserializeError;
4use super::deser::DeserializeResult;
5use super::deser::Deserializer;
6use super::ser::Serialize;
7use super::ser::SerializeResult;
8use super::ser::Serializer;
9use super::types::*;
10use anyhow::bail;
11use minetest_protocol_derive::MinetestDeserialize;
12use minetest_protocol_derive::MinetestSerialize;
13use std::ops::Deref;
14
15#[macro_export]
16macro_rules! as_item {
17    ($i:item) => {
18        $i
19    };
20}
21
22#[macro_export]
23macro_rules! default_serializer {
24    ($spec_ty: ident { }) => {
25        impl Serialize for $spec_ty {
26            type Input = Self;
27            fn serialize<S: Serializer>(value: &Self::Input, _: &mut S) -> SerializeResult {
28                Ok(())
29            }
30        }
31    };
32    ($spec_ty: ident { $($fname: ident: $ftyp: ty ),+ }) => {
33        impl Serialize for $spec_ty {
34            type Input = Self;
35            fn serialize<S: Serializer>(value: &Self::Input, ser: &mut S) -> SerializeResult {
36                $(
37                    <$ftyp as Serialize>::serialize(&value.$fname, ser)?;
38                )+
39                Ok(())
40            }
41        }
42    };
43}
44
45#[macro_export]
46macro_rules! default_deserializer {
47    ($spec_ty: ident { }) => {
48        impl Deserialize for $spec_ty {
49            type Output = Self;
50            fn deserialize(_deser: &mut Deserializer) -> DeserializeResult<Self> {
51                Ok($spec_ty)
52            }
53        }
54    };
55    ($spec_ty: ident { $($fname: ident: $ftyp: ty ),+ }) => {
56        impl Deserialize for $spec_ty {
57            type Output = Self;
58            fn deserialize(deser: &mut Deserializer) -> DeserializeResult<Self> {
59                Ok($spec_ty {
60                    $(
61                        $fname: <$ftyp>::deserialize(deser)?,
62                    )+
63                })
64            }
65        }
66    };
67}
68
69#[macro_export]
70macro_rules! implicit_from {
71    ($command_ty: ident, $name: ident, $spec_ty: ident) => {
72        impl From<$spec_ty> for $command_ty {
73            fn from(value: $spec_ty) -> Self {
74                $command_ty::$name(Box::new(value))
75            }
76        }
77    };
78}
79
80#[macro_export]
81macro_rules! proto_struct {
82    ($spec_ty: ident { }) => {
83        #[derive(Debug, Clone, PartialEq, Default, MinetestSerialize, MinetestDeserialize)]
84        pub struct $spec_ty;
85    };
86    ($spec_ty: ident {
87        $($fname: ident: $ftype: ty $([$attr:meta])? ),+
88    }) => {
89        $crate::as_item! {
90            #[derive(Debug, Clone, PartialEq, MinetestSerialize, MinetestDeserialize)]
91            pub struct $spec_ty {
92               $( $(#[$attr])? pub $fname: $ftype),+
93            }
94        }
95    };
96}
97
98macro_rules! define_protocol {
99    ($version: literal,
100     $protocol_id: literal,
101     $dir: ident,
102     $command_ty: ident => {
103         $($name: ident, $id: literal, $channel: literal, $reliable: literal => $spec_ty: ident
104             { $($fname: ident : $ftype: ty $([$attr:meta])? ),* } ),*
105    }) => {
106        $crate::as_item! {
107            #[derive(Debug, PartialEq, Clone)]
108            pub enum $command_ty {
109                $($name(Box<$spec_ty>)),*,
110            }
111        }
112
113        $crate::as_item! {
114            impl CommandProperties for $command_ty {
115                fn direction(&self) -> CommandDirection {
116                    CommandDirection::$dir
117                }
118
119                fn default_channel(&self) -> u8 {
120                    match self {
121                        $($command_ty::$name(_) => $channel),*,
122                    }
123                }
124
125                fn default_reliability(&self) -> bool {
126                    match self {
127                        $($command_ty::$name(_) => $reliable),*,
128                    }
129                }
130
131                fn command_name(&self) -> &'static str {
132                    match self {
133                        $($command_ty::$name(_) => stringify!($name)),*,
134                    }
135                }
136            }
137        }
138
139        $crate::as_item! {
140            impl Serialize for $command_ty {
141                type Input = Self;
142                fn serialize<S: Serializer>(value: &Self::Input, ser: &mut S) -> SerializeResult {
143                    match value {
144                        $($command_ty::$name(spec) => { u16::serialize(&$id, ser)?; <$spec_ty as Serialize>::serialize(Deref::deref(spec), ser) }),*,
145                    }
146                }
147            }
148        }
149
150        $crate::as_item! {
151            impl Deserialize for $command_ty {
152                type Output = Self;
153                fn deserialize(deser: &mut Deserializer) -> DeserializeResult<Self> {
154                    let orig_buffer = deser.peek_all();
155                    let command_id = u16::deserialize(deser)?;
156                    let dir = deser.direction();
157                    let result = match (dir, command_id) {
158                        $( (CommandDirection::$dir, $id) => $command_ty::$name(Box::new(<$spec_ty as Deserialize>::deserialize(deser)?)) ),*,
159                        _ => bail!(DeserializeError::BadPacketId(dir, command_id)),
160                    };
161                    audit_command(deser.context(), orig_buffer, &result);
162                    Ok(result)
163                }
164            }
165        }
166
167        $($crate::proto_struct!($spec_ty { $($fname: $ftype $([$attr])?),* });)*
168        $($crate::implicit_from!($command_ty, $name, $spec_ty);)*
169
170    };
171}
172
173define_protocol!(41, 0x4f457403, ToClient, ToClientCommand => {
174    // CommandName, CommandType, Direction, Channel, Reliable
175    Hello, 0x02, 0, true => HelloSpec {
176        serialization_ver: u8,
177        compression_mode: u16,
178        proto_ver: u16,
179        auth_mechs: AuthMechsBitset,
180        username_legacy: String
181    },
182
183    AuthAccept, 0x03, 0, true => AuthAcceptSpec {
184        player_pos: v3f,
185        map_seed: u64,
186        recommended_send_interval: f32,
187        sudo_auth_methods: u32
188    },
189
190    AcceptSudoMode, 0x04, 0, true => AcceptSudoModeSpec {
191        // No fields
192    },
193
194    DenySudoMode, 0x05, 0, true => DenySudoModeSpec {
195        // No fields
196    },
197
198    AccessDenied, 0x0A, 0, true => AccessDeniedSpec {
199        code: AccessDeniedCode
200    },
201
202    Blockdata, 0x20, 2, true => BlockdataSpec {
203        pos: v3s16,
204        block: MapBlock,
205        network_specific_version: u8
206    },
207    Addnode, 0x21, 0, true => AddnodeSpec {
208        pos: v3s16,
209        node: MapNode,
210        keep_metadata: bool
211    },
212
213    Removenode, 0x22, 0, true => RemovenodeSpec {
214        pos: v3s16
215    },
216
217    Inventory, 0x27, 0, true => InventorySpec {
218        inventory: Inventory
219    },
220
221    TimeOfDay, 0x29, 0, true => TimeOfDaySpec {
222        time_of_day: u16,
223        time_speed: Option<f32>
224    },
225
226    CsmRestrictionFlags, 0x2A, 0, true => CsmRestrictionFlagsSpec {
227        csm_restriction_flags: u64,
228        csm_restriction_noderange: u32
229    },
230
231    PlayerSpeed, 0x2B, 0, true => PlayerSpeedSpec {
232        added_vel: v3f
233    },
234
235    MediaPush, 0x2C, 0, true => MediaPushSpec {
236        raw_hash: String,
237        filename: String,
238        cached: bool,
239        token: u32
240    },
241
242    TCChatMessage, 0x2F, 0, true => TCChatMessageSpec {
243        version: u8,
244        message_type: u8,
245        sender: String [wrap(WString)],
246        message: String [wrap(WString)],
247        timestamp: u64
248    },
249
250    ActiveObjectRemoveAdd, 0x31, 0, true => ActiveObjectRemoveAddSpec {
251        removed_object_ids: Vec<u16> [wrap(Array16<u16>)],
252        added_objects: Vec<AddedObject> [wrap(Array16<AddedObject>)]
253    },
254
255    ActiveObjectMessages, 0x32, 0, true => ActiveObjectMessagesSpec {
256        objects: Vec<ActiveObjectMessage> [wrap(Array0<ActiveObjectMessage>)]
257    },
258
259    Hp, 0x33, 0, true => HpSpec {
260        hp: u16,
261        damage_effect: Option<bool>
262    },
263
264    MovePlayer, 0x34, 0, true => MovePlayerSpec {
265        pos: v3f,
266        pitch: f32,
267        yaw: f32
268    },
269
270    AccessDeniedLegacy, 0x35, 0, true => AccessDeniedLegacySpec {
271        reason: String [wrap(WString)]
272    },
273
274    Fov, 0x36, 0, true => FovSpec {
275        fov: f32,
276        is_multiplier: bool,
277        transition_time: Option<f32>
278    },
279
280    Deathscreen, 0x37, 0, true => DeathscreenSpec {
281        set_camera_point_target: bool,
282        camera_point_target: v3f
283    },
284
285    Media, 0x38, 2, true => MediaSpec {
286        num_bunches: u16,
287        bunch_index: u16,
288        files: Vec<MediaFileData> [wrap(Array32<MediaFileData>)]
289    },
290
291    Nodedef, 0x3a, 0, true => NodedefSpec {
292        node_def: NodeDefManager [wrap(ZLibCompressed<NodeDefManager>)]
293    },
294
295    AnnounceMedia, 0x3c, 0, true => AnnounceMediaSpec {
296        files: Vec<MediaAnnouncement> [wrap(Array16<MediaAnnouncement>)],
297        remote_servers: String
298    },
299
300    Itemdef, 0x3d, 0, true => ItemdefSpec {
301        item_def: ItemdefList [wrap(ZLibCompressed<ItemdefList>)]
302    },
303
304    PlaySound, 0x3f, 0, true => PlaySoundSpec {
305        server_id: s32,
306        spec_name: String,
307        spec_gain: f32,
308        typ: u8, // 0=local, 1=positional, 2=object
309        pos: v3f,
310        object_id: u16,
311        spec_loop: bool,
312        spec_fade: Option<f32>,
313        spec_pitch: Option<f32>,
314        ephemeral: Option<bool>
315    },
316
317    StopSound, 0x40, 0, true => StopSoundSpec {
318        server_id: s32
319    },
320
321    Privileges, 0x41, 0, true => PrivilegesSpec {
322        privileges: Vec<String> [wrap(Array16<String>)]
323    },
324
325    InventoryFormspec, 0x42, 0, true => InventoryFormspecSpec {
326        formspec: String [wrap(LongString)]
327    },
328
329    DetachedInventory, 0x43, 0, true => DetachedInventorySpec {
330        name: String,
331        keep_inv: bool,
332        // These are present if keep_inv is true.
333        ignore: Option<u16>,
334        contents: Option<Inventory>
335    },
336
337    ShowFormspec, 0x44, 0, true => ShowFormspecSpec {
338        form_spec: String [wrap(LongString)],
339        form_name: String
340    },
341
342    Movement, 0x45, 0, true => MovementSpec {
343        acceleration_default: f32,
344        acceleration_air: f32,
345        acceleration_fast: f32,
346        speed_walk: f32,
347        speed_crouch: f32,
348        speed_fast: f32,
349        speed_climb: f32,
350        speed_jump: f32,
351        liquid_fluidity: f32,
352        liquid_fluidity_smooth: f32,
353        liquid_sink: f32,
354        gravity: f32
355    },
356
357    SpawnParticle, 0x46, 0, true => SpawnParticleSpec {
358        data: ParticleParameters
359    },
360
361    AddParticlespawner, 0x47, 0, true => AddParticlespawnerSpec {
362        legacy: AddParticleSpawnerLegacy
363    },
364
365    Hudadd, 0x49, 1, true => HudaddSpec {
366        server_id: u32,
367        typ: u8,
368        pos: v2f,
369        name: String,
370        scale: v2f,
371        text: String,
372        number: u32,
373        item: u32,
374        dir: u32,
375        align: v2f,
376        offset: v2f,
377        world_pos: Option<v3f>,
378        size: Option<v2s32>,
379        z_index: Option<s16>,
380        text2: Option<String>,
381        style: Option<u32>
382    },
383
384    Hudrm, 0x4a, 1, true => HudrmSpec {
385        server_id: u32
386    },
387
388    Hudchange, 0x4b, 1, true => HudchangeSpec {
389        server_id: u32,
390        stat: HudStat
391    },
392
393    HudSetFlags, 0x4c, 1, true => HudSetFlagsSpec {
394        flags: HudFlags, // flags added
395        mask: HudFlags   // flags possibly removed
396    },
397
398    HudSetParam, 0x4d, 1, true => HudSetParamSpec {
399        value: HudSetParam
400    },
401
402    Breath, 0x4e, 0, true => BreathSpec {
403        breath: u16
404    },
405
406    SetSky, 0x4f, 0, true => SetSkySpec {
407        params: SkyboxParams
408    },
409
410    OverrideDayNightRatio, 0x50, 0, true => OverrideDayNightRatioSpec {
411        do_override: bool,
412        day_night_ratio: u16
413    },
414
415    LocalPlayerAnimations, 0x51, 0, true => LocalPlayerAnimationsSpec {
416        idle: v2s32,
417        walk: v2s32,
418        dig: v2s32,
419        walk_dig: v2s32,
420        frame_speed: f32
421    },
422
423    EyeOffset, 0x52, 0, true => EyeOffsetSpec {
424        eye_offset_first: v3f,
425        eye_offset_third: v3f
426    },
427
428    DeleteParticlespawner, 0x53, 0, true => DeleteParticlespawnerSpec {
429        server_id: u32
430    },
431
432    CloudParams, 0x54, 0, true => CloudParamsSpec {
433        density: f32,
434        color_bright: SColor,
435        color_ambient: SColor,
436        height: f32,
437        thickness: f32,
438        speed: v2f
439    },
440
441    FadeSound, 0x55, 0, true => FadeSoundSpec {
442        sound_id: s32,
443        step: f32,
444        gain: f32
445    },
446
447    UpdatePlayerList, 0x56, 0, true => UpdatePlayerListSpec {
448        typ: u8,
449        players: Vec<String> [wrap(Array16<String>)]
450    },
451
452    TCModchannelMsg, 0x57, 0, true => TCModchannelMsgSpec {
453        channel_name: String,
454        sender: String,
455        channel_msg: String
456    },
457
458    ModchannelSignal, 0x58, 0, true => ModchannelSignalSpec {
459        signal_tmp: u8,
460        channel: String,
461        // signal == MODCHANNEL_SIGNAL_SET_STATE
462        state: Option<u8>
463    },
464
465    NodemetaChanged, 0x59, 0, true => NodemetaChangedSpec {
466        list: AbsNodeMetadataList [wrap(ZLibCompressed<AbsNodeMetadataList>)]
467    },
468
469    SetSun, 0x5a, 0, true => SetSunSpec {
470        sun: SunParams
471    },
472
473    SetMoon, 0x5b, 0, true => SetMoonSpec {
474        moon: MoonParams
475    },
476
477    SetStars, 0x5c, 0, true => SetStarsSpec {
478        stars: StarParams
479    },
480
481    SrpBytesSB, 0x60, 0, true => SrpBytesSBSpec {
482         s: Vec<u8> [wrap(BinaryData16)],
483         b: Vec<u8> [wrap(BinaryData16)]
484    },
485
486    FormspecPrepend, 0x61, 0, true => FormspecPrependSpec {
487        formspec_prepend: String
488    },
489
490    MinimapModes, 0x62, 0, true => MinimapModesSpec {
491        modes: MinimapModeList
492    },
493
494    SetLighting, 0x63, 0, true => SetLightingSpec {
495        lighting: Lighting
496    }
497});
498
499define_protocol!(41, 0x4f457403, ToServer, ToServerCommand => {
500    /////////////////////////////////////////////////////////////////////////
501    // ToServer
502    Null, 0x00, 0, false => NullSpec {
503        // This appears to be sent before init to initialize
504        // the reliable seqnum and peer id.
505    },
506
507    Init, 0x02, 1, false => InitSpec {
508        serialization_ver_max: u8,
509        supp_compr_modes: u16,
510        min_net_proto_version: u16,
511        max_net_proto_version: u16,
512        player_name: String
513    },
514
515    Init2, 0x11, 1, true => Init2Spec {
516        lang: Option<String>
517    },
518
519    ModchannelJoin, 0x17, 0, true => ModchannelJoinSpec {
520        channel_name: String
521    },
522
523    ModchannelLeave, 0x18, 0, true => ModchannelLeaveSpec {
524        channel_name: String
525    },
526
527    TSModchannelMsg, 0x19, 0, true => TSModchannelMsgSpec {
528        channel_name: String,
529        channel_msg: String
530    },
531
532    Playerpos, 0x23, 0, false => PlayerposSpec {
533        player_pos: PlayerPos
534    },
535
536    Gotblocks, 0x24, 2, true => GotblocksSpec {
537        blocks: Vec<v3s16> [wrap(Array8<v3s16>)]
538    },
539
540    Deletedblocks, 0x25, 2, true => DeletedblocksSpec {
541        blocks: Vec<v3s16> [wrap(Array8<v3s16>)]
542    },
543
544    InventoryAction, 0x31, 0, true => InventoryActionSpec {
545        action: InventoryAction
546    },
547
548    TSChatMessage, 0x32, 0, true => TSChatMessageSpec {
549        message: String [wrap(WString)]
550    },
551
552    Damage, 0x35, 0, true => DamageSpec {
553        damage: u16
554    },
555
556    Playeritem, 0x37, 0, true => PlayeritemSpec {
557        item: u16
558    },
559
560    Respawn, 0x38, 0, true => RespawnSpec {
561        // empty
562    },
563
564    Interact, 0x39, 0, true => InteractSpec {
565        action: InteractAction,
566        item_index: u16,
567        pointed_thing: PointedThing [wrap(Wrapped32<PointedThing>)],
568        player_pos: PlayerPos
569    },
570
571    RemovedSounds, 0x3a, 2, true => RemovedSoundsSpec {
572        ids: Vec<s32> [wrap(Array16<s32>)]
573    },
574
575    NodemetaFields, 0x3b, 0, true => NodemetaFieldsSpec {
576        p: v3s16,
577        form_name: String,
578        // (name, value)
579        fields: Vec<(String, String)> [wrap(Array16<Pair<String, LongString>>)]
580    },
581
582    InventoryFields, 0x3c, 0, true => InventoryFieldsSpec {
583        client_formspec_name: String,
584        fields: Vec<(String, String)> [wrap(Array16<Pair<String, LongString>>)]
585    },
586
587    RequestMedia, 0x40, 1, true => RequestMediaSpec {
588        files: Vec<String> [wrap(Array16<String>)]
589    },
590
591    HaveMedia, 0x41, 2, true => HaveMediaSpec {
592        tokens: Vec<u32> [wrap(Array8<u32>)]
593    },
594
595    ClientReady, 0x43, 1, true => ClientReadySpec {
596        major_ver: u8,
597        minor_ver: u8,
598        patch_ver: u8,
599        reserved: u8,
600        full_ver: String,
601        formspec_ver: Option<u16>
602    },
603
604    FirstSrp, 0x50, 1, true => FirstSrpSpec {
605        salt: Vec<u8> [wrap(BinaryData16)],
606        verification_key: Vec<u8> [wrap(BinaryData16)],
607        is_empty: bool
608    },
609
610    SrpBytesA, 0x51, 1, true => SrpBytesASpec {
611        bytes_a: Vec<u8> [wrap(BinaryData16)],
612        based_on: u8
613    },
614
615    SrpBytesM, 0x52, 1, true => SrpBytesMSpec {
616        bytes_m: Vec<u8> [wrap(BinaryData16)]
617    },
618
619    UpdateClientInfo, 0x53, 1, true => UpdateClientInfoSpec {
620        render_target_size: v2u32,
621        real_gui_scaling: f32,
622        real_hud_scaling: f32,
623        max_fs_size: v2f
624    }
625});
626
627#[derive(Debug, PartialEq, Clone)]
628pub enum Command {
629    ToServer(ToServerCommand),
630    ToClient(ToClientCommand),
631}
632
633pub trait CommandProperties {
634    fn direction(&self) -> CommandDirection;
635    fn default_channel(&self) -> u8;
636    fn default_reliability(&self) -> bool;
637    fn command_name(&self) -> &'static str;
638}
639
640/// This only exists to make "audit_command" generic, but it
641/// wasn't as clean as I hoped.
642/// TODO(paradust): Factor this out.
643pub trait CommandRef: CommandProperties + std::fmt::Debug {
644    fn toserver_ref(&self) -> Option<&ToServerCommand>;
645    fn toclient_ref(&self) -> Option<&ToClientCommand>;
646}
647
648pub fn serialize_commandref<Cmd: CommandRef, S: Serializer>(
649    cmd: &Cmd,
650    ser: &mut S,
651) -> SerializeResult {
652    if let Some(r) = cmd.toserver_ref() {
653        ToServerCommand::serialize(r, ser)?;
654    }
655    if let Some(r) = cmd.toclient_ref() {
656        ToClientCommand::serialize(r, ser)?;
657    }
658    Ok(())
659}
660
661impl CommandProperties for Command {
662    fn direction(&self) -> CommandDirection {
663        match self {
664            Command::ToServer(_) => CommandDirection::ToServer,
665            Command::ToClient(_) => CommandDirection::ToClient,
666        }
667    }
668
669    fn default_channel(&self) -> u8 {
670        match self {
671            Command::ToServer(c) => c.default_channel(),
672            Command::ToClient(c) => c.default_channel(),
673        }
674    }
675
676    fn default_reliability(&self) -> bool {
677        match self {
678            Command::ToServer(c) => c.default_reliability(),
679            Command::ToClient(c) => c.default_reliability(),
680        }
681    }
682
683    fn command_name(&self) -> &'static str {
684        match self {
685            Command::ToServer(c) => c.command_name(),
686            Command::ToClient(c) => c.command_name(),
687        }
688    }
689}
690
691impl CommandRef for Command {
692    fn toserver_ref(&self) -> Option<&ToServerCommand> {
693        match self {
694            Command::ToServer(s) => Some(s),
695            Command::ToClient(_) => None,
696        }
697    }
698
699    fn toclient_ref(&self) -> Option<&ToClientCommand> {
700        match self {
701            Command::ToServer(_) => None,
702            Command::ToClient(c) => Some(c),
703        }
704    }
705}
706
707impl CommandRef for ToClientCommand {
708    fn toserver_ref(&self) -> Option<&ToServerCommand> {
709        None
710    }
711
712    fn toclient_ref(&self) -> Option<&ToClientCommand> {
713        Some(self)
714    }
715}
716
717impl CommandRef for ToServerCommand {
718    fn toserver_ref(&self) -> Option<&ToServerCommand> {
719        Some(self)
720    }
721
722    fn toclient_ref(&self) -> Option<&ToClientCommand> {
723        None
724    }
725}
726
727impl Serialize for Command {
728    type Input = Self;
729    fn serialize<S: Serializer>(value: &Self::Input, ser: &mut S) -> SerializeResult {
730        match value {
731            Command::ToServer(c) => ToServerCommand::serialize(c, ser),
732            Command::ToClient(c) => ToClientCommand::serialize(c, ser),
733        }
734    }
735}
736
737impl Deserialize for Command {
738    type Output = Self;
739    fn deserialize(deser: &mut Deserializer) -> DeserializeResult<Self> {
740        Ok(match deser.direction() {
741            CommandDirection::ToClient => Command::ToClient(ToClientCommand::deserialize(deser)?),
742            CommandDirection::ToServer => Command::ToServer(ToServerCommand::deserialize(deser)?),
743        })
744    }
745}