]> git.lizzy.rs Git - mt_net.git/commitdiff
Initial commit
authorLizzy Fleckenstein <eliasfleckenstein@web.de>
Fri, 10 Feb 2023 14:53:15 +0000 (15:53 +0100)
committerLizzy Fleckenstein <eliasfleckenstein@web.de>
Fri, 10 Feb 2023 14:53:15 +0000 (15:53 +0100)
16 files changed:
.gitignore [new file with mode: 0644]
Cargo.toml [new file with mode: 0644]
src/lib.rs [new file with mode: 0644]
src/to_clt.rs [new file with mode: 0644]
src/to_clt/chat.rs [new file with mode: 0644]
src/to_clt/env.rs [new file with mode: 0644]
src/to_clt/hud.rs [new file with mode: 0644]
src/to_clt/media.rs [new file with mode: 0644]
src/to_clt/status.rs [new file with mode: 0644]
src/to_srv.rs [new file with mode: 0644]
tests/.gitignore [new file with mode: 0644]
tests/random.rs [new file with mode: 0644]
tests/reserialize/.gitignore [new file with mode: 0644]
tests/reserialize/go.mod [new file with mode: 0644]
tests/reserialize/go.sum [new file with mode: 0644]
tests/reserialize/main.go [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..4fffb2f
--- /dev/null
@@ -0,0 +1,2 @@
+/target
+/Cargo.lock
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644 (file)
index 0000000..7b8e51d
--- /dev/null
@@ -0,0 +1,28 @@
+[package]
+name = "mt_net"
+version = "0.1.0"
+edition = "2021"
+
+[features]
+all = ["client", "server", "random", "serde"]
+client = []
+random = ["dep:generate-random", "dep:rand"]
+serde = ["dep:serde", "dep:serde_arrays", "enumset/serde"]
+server = []
+
+[dependencies]
+mt_ser = { path = "../mt_ser" }
+enumset = { git = "https://github.com/Lymia/enumset" }
+generate-random = { git = "https://github.com/minetest-rust/generate-random", features = ["enumset"], optional = true }
+rand = { version = "0.8.5", optional = true }
+serde = { version = "1.0.152", features = ["derive"], optional = true }
+serde_arrays = { version = "0.1.0", optional = true }
+
+[dev-dependencies]
+libtest-mimic = "0.6.0"
+serde_json = "1.0.93"
+
+[[test]]
+name = "random"
+path = "tests/random.rs"
+harness = false
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644 (file)
index 0000000..a1b8469
--- /dev/null
@@ -0,0 +1,32 @@
+#![feature(iterator_try_collect)]
+
+#[cfg(feature = "random")]
+pub use generate_random;
+
+#[cfg(feature = "random")]
+pub use rand;
+
+#[cfg(feature = "serde")]
+pub use serde;
+
+use enumset::{EnumSet, EnumSetType};
+use mt_ser::mt_derive;
+use std::{
+    collections::{HashMap, HashSet},
+    fmt,
+};
+
+#[cfg(any(feature = "client", feature = "server"))]
+use mt_ser::{DefCfg, DeserializeError, MtCfg, MtDeserialize, MtSerialize, SerializeError};
+
+#[cfg(feature = "random")]
+use generate_random::GenerateRandom;
+
+#[cfg(feature = "serde")]
+use serde::{Deserialize, Serialize};
+
+mod to_clt;
+mod to_srv;
+
+pub use to_clt::*;
+pub use to_srv::*;
diff --git a/src/to_clt.rs b/src/to_clt.rs
new file mode 100644 (file)
index 0000000..fff0ff7
--- /dev/null
@@ -0,0 +1,326 @@
+use super::*;
+
+#[mt_derive(to = "clt")]
+pub struct ArgbColor {
+    pub a: u8,
+    pub r: u8,
+    pub g: u8,
+    pub b: u8,
+}
+
+#[mt_derive(to = "clt", repr = "u8")]
+pub enum ModChanSig {
+    JoinOk = 0,
+    JoinFail,
+    LeaveOk,
+    LeaveFail,
+    NotRegistered,
+    SetState,
+}
+
+mod chat;
+mod env;
+mod hud;
+mod media;
+mod status;
+
+pub use chat::*;
+pub use env::*;
+pub use hud::*;
+pub use media::*;
+pub use status::*;
+
+#[mt_derive(to = "clt", repr = "u8", tag = "type", content = "data")]
+pub enum ToCltPkt {
+    Hello {
+        serialize_version: u8,
+        #[mt(const16 = 1)] // compression
+        proto_version: u16,
+        auth_methods: EnumSet<AuthMethod>,
+        username: String,
+    } = 2,
+    AcceptAuth {
+        player_pos: [f32; 3],
+        map_seed: u64,
+        send_interval: f32,
+        sudo_auth_methods: EnumSet<AuthMethod>,
+    } = 3,
+    AcceptSudoMode {
+        sudo_auth_methods: EnumSet<AuthMethod>,
+    } = 4,
+    DenySudoMode = 5,
+    Kick(KickReason) = 10,
+    BlockData {
+        pos: [i16; 3],
+        #[mt(zstd)]
+        block: Box<MapBlock>,
+    } = 32,
+    AddNode {
+        pos: [i16; 3],
+        param0: u16,
+        param1: u8,
+        param2: u8,
+        keep_meta: bool,
+    } = 33,
+    RemoveNode {
+        pos: [i16; 3],
+    } = 34,
+    Inv {
+        inv: String,
+    } = 39,
+    TimeOfDay {
+        time: u16,
+        speed: f32,
+    } = 41,
+    CsmRestrictionFlags {
+        flags: EnumSet<CsmRestrictionFlag>,
+        map_range: u32,
+    } = 42,
+    AddPlayerVelocity {
+        vel: [f32; 3],
+    } = 43,
+    MediaPush {
+        no_len_hash: String,
+        filename: String,
+        callback_token: u32,
+        should_cache: bool,
+    } = 44,
+    ChatMsg {
+        #[mt(const8 = 1)]
+        msg_type: ChatMsgType,
+        #[mt(utf16)]
+        sender: String,
+        #[mt(utf16)]
+        text: String,
+        timestamp: i64, // unix time
+    } = 47,
+    ObjRemoveAdd {
+        remove: Vec<u16>,
+        add: Vec<ObjAdd>,
+    } = 49,
+    ObjMsgs {
+        msgs: Vec<ObjMsg>,
+    } = 50,
+    Hp {
+        hp: u16,
+        #[mt(default)]
+        damage_effect: bool,
+    } = 51,
+    MovePlayer {
+        pos: [f32; 3],
+        pitch: f32,
+        yaw: f32,
+    } = 52,
+    LegacyKick {
+        #[mt(utf16)]
+        reason: String,
+    } = 53,
+    Fov {
+        fov: f32,
+        multiplier: bool,
+        transition_time: f32,
+    } = 54,
+    DeathScreen {
+        point_cam: bool,
+        point_at: [f32; 3],
+    } = 55,
+    Media {
+        n: u16,
+        i: u16,
+        files: Vec<MediaPayload>, // FIXME: can we use a HashMap for this?
+    } = 56,
+    NodeDefs {
+        defs: Vec<NodeDef>,
+    } = 58,
+    AnnounceMedia {
+        files: Vec<MediaAnnounce>, // FIXME: can we use a HashMap for this?
+        url: String,
+    } = 60,
+    #[mt(size32, zlib)]
+    ItemDefs {
+        #[mt(const8 = 0)] // version
+        defs: Vec<ItemDef>,
+        aliases: HashMap<String, String>,
+    } = 61,
+    PlaySound {
+        id: u32,
+        name: String,
+        gain: f32,
+        src_type: SoundSrcType,
+        pos: [f32; 3],
+        src_obj_id: u16,
+        #[serde(rename = "loop")]
+        sound_loop: bool,
+        fade: f32,
+        pitch: f32,
+        ephermeral: bool,
+    } = 63,
+    StopSound {
+        id: u32,
+    } = 64,
+    Privs {
+        privs: HashSet<String>,
+    } = 65,
+    InvFormspec {
+        #[mt(size32)]
+        formspec: String,
+    } = 66,
+    DetachedInv {
+        name: String,
+        keep: bool,
+        len: u16,
+        #[mt(len0)]
+        inv: String,
+    } = 67,
+    ShowFormspec {
+        #[mt(len32)]
+        formspec: String,
+        formname: String,
+    } = 68,
+    Movement {
+        default_accel: f32,
+        air_accel: f32,
+        fast_accel: f32,
+        walk_speed: f32,
+        crouch_speed: f32,
+        fast_speed: f32,
+        climb_speed: f32,
+        jump_speed: f32,
+        gravity: f32,
+    } = 69,
+    SpawnParticle {
+        pos: [f32; 3],
+        vel: [f32; 3],
+        acc: [f32; 3],
+        expiration_time: f32,
+        size: f32,
+        collide: bool,
+        #[mt(len32)]
+        texture: String,
+        vertical: bool,
+        collision_rm: bool,
+        anim_params: TileAnim,
+        glow: u8,
+        obj_collision: bool,
+        node_param0: u16,
+        node_param2: u8,
+        node_tile: u8,
+    } = 70,
+    AddParticleSpawner {
+        amount: u16,
+        duration: f32,
+        pos: [[f32; 3]; 2],
+        vel: [[f32; 3]; 2],
+        acc: [[f32; 3]; 2],
+        expiration_time: [f32; 2],
+        size: [f32; 2],
+        collide: bool,
+        #[mt(len32)]
+        texture: String,
+        id: u32,
+        vertical: bool,
+        collision_rm: bool,
+        attached_obj_id: u16,
+        anim_params: TileAnim,
+        glow: u8,
+        obj_collision: bool,
+        node_param0: u16,
+        node_param2: u8,
+        node_tile: u8,
+    } = 71,
+    AddHud {
+        id: u32,
+        hud: HudElement,
+    } = 73,
+    RemoveHud {
+        id: u32,
+    } = 74,
+    ChangeHud {
+        id: u32,
+        change: HudChange,
+    } = 75,
+    HudFlags {
+        flags: EnumSet<HudFlag>,
+        mask: EnumSet<HudFlag>,
+    } = 76,
+    SetHotbarParam(HotbarParam) = 77,
+    Breath {
+        breath: u16,
+    } = 78,
+    // TODO
+    SkyParams = 79,
+    OverrideDayNightRatio {
+        #[serde(rename = "override")]
+        ratio_override: bool,
+        ratio: u16,
+    } = 80,
+    LocalPlayerAnim {
+        idle: [i32; 2],
+        walk: [i32; 2],
+        dig: [i32; 2],
+        walk_dig: [i32; 2],
+        speed: f32,
+    } = 81,
+    EyeOffset {
+        first: [f32; 3],
+        third: [f32; 3],
+    } = 82,
+    RemoveParticleSpawner {
+        id: u32,
+    } = 83,
+    CloudParams {
+        density: f32,
+        diffuse_color: ArgbColor,
+        ambient_color: ArgbColor,
+        height: f32,
+        thickness: f32,
+        speed: [f32; 2],
+    } = 84,
+    FadeSound {
+        id: u32,
+        step: f32,
+        gain: f32,
+    } = 85,
+    UpdatePlayerList {
+        update_type: PlayerListUpdateType,
+        players: HashSet<String>,
+    } = 86,
+    ModChanMsg {
+        channel: String,
+        sender: String,
+        msg: String,
+    } = 87,
+    ModChanSig {
+        signal: ModChanSig,
+        channel: String,
+    } = 88,
+    NodeMetasChanged(#[mt(size32)] HashMap<[i16; 3], NodeMeta>) = 89,
+    SunParams {
+        visible: bool,
+        texture: String,
+        tone_map: String,
+        rise: String,
+        rising: bool,
+        size: f32,
+    } = 90,
+    MoonParams {
+        visible: bool,
+        texture: String,
+        tone_map: String,
+        size: f32,
+    } = 91,
+    StarParams {
+        visible: bool,
+        texture: String,
+        tone_map: String,
+        size: f32,
+    } = 92,
+    SrpBytesSaltB {
+        salt: Vec<u8>,
+        b: Vec<u8>,
+    } = 96,
+    FormspecPrepend {
+        prepend: String,
+    } = 97,
+    MinimapModes(MinimapModePkt) = 98,
+}
diff --git a/src/to_clt/chat.rs b/src/to_clt/chat.rs
new file mode 100644 (file)
index 0000000..4d99853
--- /dev/null
@@ -0,0 +1,16 @@
+use super::*;
+
+#[mt_derive(to = "clt", repr = "u8")]
+pub enum ChatMsgType {
+    Raw = 0,
+    Normal,
+    Announce,
+    System,
+}
+
+#[mt_derive(to = "clt", repr = "u8")]
+pub enum PlayerListUpdateType {
+    Init = 0,
+    Add,
+    Remove,
+}
diff --git a/src/to_clt/env.rs b/src/to_clt/env.rs
new file mode 100644 (file)
index 0000000..f242298
--- /dev/null
@@ -0,0 +1,44 @@
+use super::*;
+
+#[mt_derive(to = "clt")]
+pub struct ObjAdd; // TODO
+
+#[mt_derive(to = "clt")]
+pub struct ObjMsg; // TODO
+
+#[mt_derive(to = "clt", repr = "u8", enumset)]
+pub enum MapBlockFlag {
+    IsUnderground = 0,
+    DayNightDiff,
+    LightExpired,
+    NotGenerated,
+}
+
+pub const ALWAYS_LIT_FROM: u16 = 0xf000;
+
+#[mt_derive(to = "clt")]
+pub struct MapBlock {
+    pub flags: EnumSet<MapBlockFlag>,
+    pub lit_from: u16,
+
+    #[mt(const8 = 2)]
+    #[serde(skip)]
+    pub param0_size: (),
+
+    #[mt(const8 = 2)]
+    #[serde(skip)]
+    pub param12_size: (),
+
+    #[serde(with = "serde_arrays")]
+    pub param_0: [u16; 4096],
+    #[serde(with = "serde_arrays")]
+    pub param_1: [u8; 4096],
+    #[serde(with = "serde_arrays")]
+    pub param_2: [u8; 4096],
+
+    pub node_metas: HashMap<u16, NodeMeta>,
+
+    #[mt(const8 = 2)]
+    #[serde(skip)]
+    pub version: (),
+}
diff --git a/src/to_clt/hud.rs b/src/to_clt/hud.rs
new file mode 100644 (file)
index 0000000..f45d735
--- /dev/null
@@ -0,0 +1,155 @@
+use super::*;
+
+#[mt_derive(to = "clt", repr = "u32", enumset)]
+pub enum HudStyleFlag {
+    Bold,
+    Italic,
+    Mono,
+}
+
+#[mt_derive(to = "clt", repr = "u8", tag = "attribute", content = "value")]
+pub enum HudChange {
+    Pos([f32; 2]) = 0,
+    Name(String),
+    Scale([f32; 2]),
+    Text(String),
+    Number(u32),
+    Item(u32),
+    Dir(u32),
+    Align([f32; 2]),
+    Offset([f32; 2]),
+    WorldPos([f32; 3]),
+    ZIndex(i32),
+    Text2(String),
+    Style(EnumSet<HudStyleFlag>),
+}
+
+#[mt_derive(to = "clt", repr = "u8")]
+pub enum HudType {
+    Image = 0,
+    Text,
+    Statbar,
+    Inv,
+    Waypoint,
+    ImageWaypoint,
+}
+
+#[mt_derive(to = "clt")]
+pub struct HudElement {
+    pub hud_type: HudType,
+    pub pos: [f32; 2],
+    pub name: String,
+    pub scale: [f32; 2],
+    pub text: String,
+    pub number: u32,
+    pub item: u32,
+    pub dir: u32,
+    pub align: [f32; 2],
+    pub offset: [f32; 2],
+    pub world_pos: [f32; 3],
+    pub z_index: i32,
+    pub text_2: String,
+    pub style: EnumSet<HudStyleFlag>,
+}
+
+impl HudElement {
+    pub fn apply_change(&mut self, change: HudChange) {
+        use HudChange::*;
+
+        match change {
+            Pos(v) => self.pos = v,
+            Name(v) => self.name = v,
+            Scale(v) => self.scale = v,
+            Text(v) => self.text = v,
+            Number(v) => self.number = v,
+            Item(v) => self.item = v,
+            Dir(v) => self.dir = v,
+            Align(v) => self.align = v,
+            Offset(v) => self.offset = v,
+            WorldPos(v) => self.world_pos = v,
+            ZIndex(v) => self.z_index = v,
+            Text2(v) => self.text_2 = v,
+            Style(v) => self.style = v,
+        }
+    }
+}
+
+#[mt_derive(to = "clt", repr = "u32", enumset)]
+pub enum HudFlag {
+    Hotbar,
+    HealthBar,
+    Crosshair,
+    WieldedItem,
+    BreathBar,
+    Minimap,
+    RadarMinimap,
+}
+
+#[mt_derive(to = "clt", repr = "u16", tag = "attribute", content = "value")]
+pub enum HotbarParam {
+    Size(#[mt(const16 = 4)] u32) = 0,
+    Image(String),
+    SelectionImage(String),
+}
+
+#[mt_derive(to = "clt", repr = "u16")]
+pub enum MinimapType {
+    None = 0,
+    Surface,
+    Radar,
+    Texture,
+}
+
+#[mt_derive(to = "clt")]
+pub struct MinimapMode {
+    pub minimap_type: MinimapType,
+    pub label: String,
+    pub size: u16,
+    pub texture: String,
+    pub scale: u16,
+}
+
+#[mt_derive(to = "clt", custom)]
+pub struct MinimapModePkt {
+    current: u16,
+    modes: Vec<MinimapMode>,
+}
+
+#[cfg(feature = "server")]
+impl MtSerialize for MinimapModePkt {
+    fn mt_serialize<C: MtCfg>(
+        &self,
+        writer: &mut impl std::io::Write,
+    ) -> Result<(), SerializeError> {
+        DefCfg::write_len(self.modes.len(), writer)?;
+        self.current.mt_serialize::<DefCfg>(writer)?;
+        self.modes.mt_serialize::<()>(writer)?;
+
+        Ok(())
+    }
+}
+
+#[cfg(feature = "client")]
+impl MtDeserialize for MinimapModePkt {
+    fn mt_deserialize<C: MtCfg>(reader: &mut impl std::io::Read) -> Result<Self, DeserializeError> {
+        let len = DefCfg::read_len(reader)?;
+        let current = MtDeserialize::mt_deserialize::<DefCfg>(reader)?;
+        let modes = mt_ser::mt_deserialize_sized_seq(&len, reader)?.try_collect()?;
+
+        Ok(Self { current, modes })
+    }
+}
+
+/*
+TODO: rustify this
+
+var DefaultMinimap = []MinimapMode{
+    {Type: NoMinimap},
+    {Type: SurfaceMinimap, Size: 256},
+    {Type: SurfaceMinimap, Size: 128},
+    {Type: SurfaceMinimap, Size: 64},
+    {Type: RadarMinimap, Size: 512},
+    {Type: RadarMinimap, Size: 256},
+    {Type: RadarMinimap, Size: 128},
+}
+*/
diff --git a/src/to_clt/media.rs b/src/to_clt/media.rs
new file mode 100644 (file)
index 0000000..0dd1f3d
--- /dev/null
@@ -0,0 +1,33 @@
+use super::*;
+
+#[mt_derive(to = "clt")]
+pub struct MediaAnnounce {
+    pub name: String,
+    pub base64_sha1: String,
+}
+
+#[mt_derive(to = "clt")]
+pub struct MediaPayload {
+    pub name: String,
+    #[mt(len32)]
+    pub data: Vec<u8>,
+}
+
+#[mt_derive(to = "clt")]
+pub struct TileAnim; // TODO
+
+#[mt_derive(to = "clt")]
+pub struct ItemDef; // TODO
+
+#[mt_derive(to = "clt")]
+pub struct NodeDef; // TODO
+
+#[mt_derive(to = "clt")]
+pub struct NodeMeta; // TODO
+
+#[mt_derive(to = "clt", repr = "u16")]
+pub enum SoundSrcType {
+    Nowhere = 0,
+    Pos,
+    Obj,
+}
diff --git a/src/to_clt/status.rs b/src/to_clt/status.rs
new file mode 100644 (file)
index 0000000..54adb45
--- /dev/null
@@ -0,0 +1,80 @@
+use super::*;
+
+#[mt_derive(to = "clt", repr = "u8", tag = "reason")]
+pub enum KickReason {
+    WrongPasswd,
+    UnexpectedData,
+    SrvIsSingleplayer,
+    UnsupportedVersion,
+    BadNameChars,
+    BadName,
+    TooManyClts,
+    EmptyPasswd,
+    AlreadyConnected,
+    SrvErr,
+    Custom { custom: String },
+    Shutdown { custom: String, reconnect: bool },
+    Crash { custom: String, reconnect: bool },
+}
+
+impl KickReason {
+    pub fn reconnect(&self) -> bool {
+        use KickReason::*;
+
+        match self {
+            Shutdown { reconnect, .. } | Crash { reconnect, .. } => *reconnect,
+            _ => false,
+        }
+    }
+}
+
+impl fmt::Display for KickReason {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        use KickReason::*;
+
+        match self {
+            WrongPasswd => write!(f, "wrong password"),
+            UnexpectedData => write!(f, "unexpected data"),
+            SrvIsSingleplayer => write!(f, "server is singleplayer"),
+            UnsupportedVersion => write!(f, "unsupported client version"),
+            BadNameChars => write!(f, "disallowed character(s) in player name"),
+            BadName => write!(f, "disallowed player name"),
+            TooManyClts => write!(f, "too many clients"),
+            EmptyPasswd => write!(f, "empty password"),
+            AlreadyConnected => write!(f, "another client is already connected with the same name"),
+            SrvErr => write!(f, "unsupported client version"),
+            Custom { custom } => write!(f, "{custom}"),
+            Shutdown { custom, .. } => {
+                if custom.is_empty() {
+                    write!(f, "server shutdown")
+                } else {
+                    write!(f, "server shutdown: {custom}")
+                }
+            }
+            Crash { custom, .. } => {
+                if custom.is_empty() {
+                    write!(f, "server crash")
+                } else {
+                    write!(f, "server crash: {custom}")
+                }
+            }
+        }
+    }
+}
+
+#[mt_derive(to = "clt", repr = "u32", enumset)]
+pub enum AuthMethod {
+    LegacyPasswd,
+    Srp,
+    FirstSrp,
+}
+
+#[mt_derive(to = "clt", repr = "u64", enumset)]
+pub enum CsmRestrictionFlag {
+    NoCsms,
+    NoChatMsgs,
+    NoItemDefs,
+    NoNodeDefs,
+    LimitMapRange,
+    NoPlayerList,
+}
diff --git a/src/to_srv.rs b/src/to_srv.rs
new file mode 100644 (file)
index 0000000..03cb5a4
--- /dev/null
@@ -0,0 +1,133 @@
+use super::*;
+
+#[mt_derive(to = "srv", repr = "u32", enumset)]
+pub enum Key {
+    Forward,
+    Backward,
+    Left,
+    Right,
+    Jump,
+    Special,
+    Sneak,
+    Dig,
+    Place,
+    Zoom,
+}
+
+#[mt_derive(to = "srv")]
+pub struct PlayerPos {
+    pub pos_100: [i32; 3],
+    pub vel_100: [i32; 3],
+    pub pitch_100: i32,
+    pub yaw_100: i32,
+    pub keys: EnumSet<Key>,
+    pub fov_80: u8,
+    pub wanted_range: u8,
+}
+
+#[mt_derive(to = "srv", repr = "u8")]
+pub enum Interaction {
+    Dig = 0,
+    StopDigging,
+    Dug,
+    Place,
+    Use,
+    Activate,
+}
+
+#[mt_derive(to = "srv")]
+pub struct PointedThing; // TODO
+
+#[mt_derive(to = "srv", repr = "u16", tag = "type", content = "data")]
+pub enum ToSrvPkt {
+    Nil = 0,
+    Init {
+        serialize_version: u8,
+        #[mt(const16 = 1)] // supported compression
+        min_proto_version: u16,
+        max_proto_version: u16,
+        player_name: String,
+        #[mt(default)]
+        send_full_item_meta: bool,
+    } = 2,
+    Init2 {
+        lang: String,
+    } = 17,
+    JoinModChan {
+        channel: String,
+    } = 23,
+    LeaveModChan {
+        channel: String,
+    } = 24,
+    MsgModChan {
+        channel: String,
+        msg: String,
+    } = 25,
+    PlayerPos(PlayerPos) = 35,
+    GotBlocks {
+        #[mt(len8)]
+        blocks: Vec<[i16; 3]>,
+    } = 36,
+    DeletedBlocks {
+        #[mt(len8)]
+        blocks: Vec<[i16; 3]>,
+    } = 37,
+    InvAction {
+        #[mt(len0)]
+        action: String,
+    } = 49,
+    ChatMsg {
+        #[mt(utf16)]
+        msg: String,
+    } = 50,
+    FallDmg {
+        amount: u16,
+    } = 53,
+    SelectItem {
+        select_item: u16,
+    } = 55,
+    Respawn = 56,
+    Interact {
+        action: Interaction,
+        item_slot: u16,
+        #[mt(size32)]
+        pointed: PointedThing,
+        pos: PlayerPos,
+    } = 57,
+    RemovedSounds {
+        ids: Vec<i32>,
+    } = 58,
+    NodeMetaFields {
+        pos: [i16; 3],
+        formname: String,
+        fields: HashMap<String, String>,
+    } = 59,
+    InvFields {
+        formname: String,
+        fields: HashMap<String, String>,
+    } = 60,
+    ReqMedia {
+        filenames: Vec<String>,
+    } = 64,
+    CltReady {
+        major: u8,
+        minor: u8,
+        patch: u8,
+        reserved: u8,
+        version: String,
+        formspec: u16,
+    } = 67,
+    FirstSrp {
+        salt: Vec<u8>,
+        verifier: Vec<u8>,
+        empty_passwd: bool,
+    } = 80,
+    SrpBytesA {
+        a: Vec<u8>,
+        no_sha1: bool,
+    } = 81,
+    SrpBytesM {
+        m: Vec<u8>,
+    } = 82,
+    Disco = 0xffff,
+}
diff --git a/tests/.gitignore b/tests/.gitignore
new file mode 100644 (file)
index 0000000..4fffb2f
--- /dev/null
@@ -0,0 +1,2 @@
+/target
+/Cargo.lock
diff --git a/tests/random.rs b/tests/random.rs
new file mode 100644 (file)
index 0000000..4d2ad42
--- /dev/null
@@ -0,0 +1,49 @@
+use libtest_mimic::{Arguments, Failed, Trial};
+
+use mt_net::{generate_random::GenerateRandomVariant, rand, ToCltPkt, ToSrvPkt};
+use mt_ser::{DefCfg, MtDeserialize, MtSerialize};
+use std::{error::Error, fmt::Debug};
+
+fn test_reserialize<T>(type_name: &'static str) -> impl Iterator<Item = Trial>
+where
+    T: MtSerialize + MtDeserialize + GenerateRandomVariant + PartialEq + Debug,
+{
+    (0..T::num_variants()).map(move |i| {
+        Trial::test(format!("{type_name}::{}", T::variant_name(i)), move || {
+            let mut rng = rand::thread_rng();
+
+            for _ in 0..100 {
+                let input = T::generate_random_variant(&mut rng, i);
+
+                let mut writer = Vec::new();
+                input
+                    .mt_serialize::<DefCfg>(&mut writer)
+                    .map_err(|e| format!("serialize error: {e}\ninput: {input:?}"))?;
+
+                let mut reader = std::io::Cursor::new(writer);
+                let output = T::mt_deserialize::<DefCfg>(&mut reader)
+                    .map_err(|e| format!("deserialize error: {e}\ninput: {input:?}"))?;
+
+                if input != output {
+                    return Err(format!(
+                        "output did not match input\n\
+                                               input: {input:?}\n\
+                                               output: {output:?}",
+                    )
+                    .into());
+                }
+            }
+
+            Ok(())
+        })
+        .with_kind("random")
+    })
+}
+
+fn main() -> Result<(), Box<dyn Error>> {
+    let args = Arguments::from_args();
+    let tests = test_reserialize::<ToSrvPkt>("ToSrvPkt")
+        .chain(test_reserialize::<ToCltPkt>("ToCltPkt"))
+        .collect();
+    libtest_mimic::run(&args, tests).exit();
+}
diff --git a/tests/reserialize/.gitignore b/tests/reserialize/.gitignore
new file mode 100644 (file)
index 0000000..49a0751
--- /dev/null
@@ -0,0 +1 @@
+validate
diff --git a/tests/reserialize/go.mod b/tests/reserialize/go.mod
new file mode 100644 (file)
index 0000000..414ffb6
--- /dev/null
@@ -0,0 +1,10 @@
+module github.com/minetest-rust/mt_net/tests/reserialize
+
+go 1.20
+
+replace github.com/dragonfireclient/mt => /home/fleckenstein/src/mt
+
+require (
+       github.com/dragonfireclient/mt v0.0.1 // indirect
+       github.com/klauspost/compress v1.15.15 // indirect
+)
diff --git a/tests/reserialize/go.sum b/tests/reserialize/go.sum
new file mode 100644 (file)
index 0000000..c92cbb7
--- /dev/null
@@ -0,0 +1,6 @@
+github.com/dragonfireclient/mt v0.0.2-0.20220709120709-173ad6e339cf h1:0CY1XyRPpNTgPQJjgsqvBzBgXdf6NN6deKw81G0qeHQ=
+github.com/dragonfireclient/mt v0.0.2-0.20220709120709-173ad6e339cf/go.mod h1:3oHbcSQytW21mTF7ozw3Il3UzdOAG30gPzO2XUAqvGs=
+github.com/klauspost/compress v1.15.5 h1:qyCLMz2JCrKADihKOh9FxnW3houKeNsp2h5OEz0QSEA=
+github.com/klauspost/compress v1.15.5/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
+github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw=
+github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4=
diff --git a/tests/reserialize/main.go b/tests/reserialize/main.go
new file mode 100644 (file)
index 0000000..101f3bf
--- /dev/null
@@ -0,0 +1,16 @@
+package main
+
+import (
+       "github.com/dragonfireclient/mt"
+       "os"
+)
+
+// WIP: test against the Go mt package
+func main() {
+       pkt, err := mt.DeserializePkt(os.Stdin, false)
+       if err != nil {
+               panic(err)
+       }
+
+       mt.SerializePkt(*pkt, os.Stdout, false)
+}