minetest_protocol/wire/
audit.rs1use anyhow::bail;
13use anyhow::Result;
14
15use super::command::serialize_commandref;
16use super::command::CommandRef;
17use super::command::ToClientCommand;
18use super::ser::VecSerializer;
19use super::types::ProtocolContext;
20use super::util::decompress_zlib;
21use super::util::zstd_decompress;
22use std::sync::atomic::AtomicBool;
23
24static AUDIT_ENABLED: AtomicBool = AtomicBool::new(false);
25
26pub fn audit_on() {
27 AUDIT_ENABLED.store(true, std::sync::atomic::Ordering::SeqCst);
28}
29
30pub fn audit_command<Cmd: CommandRef>(context: ProtocolContext, orig: &[u8], command: &Cmd) {
31 if !AUDIT_ENABLED.load(std::sync::atomic::Ordering::Relaxed) {
32 return;
33 }
34 let mut ser = VecSerializer::new(context, 2 * orig.len());
35 match serialize_commandref(command, &mut ser) {
36 Ok(_) => (),
37 Err(err) => {
38 println!("AUDIT: Reserialization failed");
39 println!("AUDIT: ORIGINAL = {:?}", orig);
40 println!("AUDIT: PARSED = {:?}", command);
41 println!("ERR = {:?}", err);
42 std::process::exit(1);
43 }
44 }
45 let reser = ser.take();
46 let reser = reser.as_slice();
47 match audit_command_inner(context, orig, reser, command) {
48 Ok(_) => (),
49 Err(err) => {
50 println!("AUDIT: Unknown error occurred auditing of command");
51 println!("AUDIT: PARSED = {:?}", command);
52 println!("AUDIT: ORIGINAL = {:?}", orig);
53 println!("AUDIT: RESERIALIZED = {:?}", reser);
54 println!("ERR = {:?}", err);
55 std::process::exit(1);
56 }
57 }
58}
59
60fn audit_command_inner<Cmd: CommandRef>(
61 context: ProtocolContext,
62 orig: &[u8],
63 reser: &[u8],
64 command: &Cmd,
65) -> Result<()> {
66 match command.toclient_ref() {
69 Some(ToClientCommand::Blockdata(_)) => {
70 if context.ser_fmt >= 29 {
71 do_compare(
78 "BlockData prefix (ver>=29)",
79 &reser[..8],
80 &orig[..8],
81 command,
82 );
83 do_compare(
84 "BlockData suffix (ver>=29)",
85 &reser[reser.len() - 1..reser.len()],
86 &orig[orig.len() - 1..orig.len()],
87 command,
88 );
89 let reser = zstd_decompress_to_vec(&reser[8..reser.len() - 1])?;
90 let orig = zstd_decompress_to_vec(&orig[8..orig.len() - 1])?;
91 do_compare("Blockdata contents (ver>=29)", &reser, &orig, command);
92 } else {
93 do_compare(
105 "BlockData prefix (ver==28)",
106 &reser[..13],
107 &orig[..13],
108 command,
109 );
110 do_compare(
111 "BlockData suffix (ver==28)",
112 &reser[reser.len() - 1..],
113 &orig[orig.len() - 1..],
114 command,
115 );
116
117 let reser_contents = {
118 let (consumed1, nodes_raw) = decompress_zlib(&reser[13..])?;
119 let (consumed2, metadata_raw) = decompress_zlib(&reser[13 + consumed1..])?;
120 if 13 + consumed1 + consumed2 + 1 != reser.len() {
121 bail!("Reserialized command does not have the right size")
122 }
123 (nodes_raw, metadata_raw)
124 };
125 let orig_contents = {
126 let (consumed1, nodes_raw) = decompress_zlib(&orig[13..])?;
127 let (consumed2, metadata_raw) = decompress_zlib(&orig[13 + consumed1..])?;
128 if 13 + consumed1 + consumed2 + 1 != orig.len() {
129 bail!("Original command does not seem to have the right size")
130 }
131 (nodes_raw, metadata_raw)
132 };
133 do_compare(
134 "Uncompressed nodes (ver 28)",
135 &reser_contents.0,
136 &orig_contents.0,
137 command,
138 );
139 do_compare(
140 "Uncompressed node metadata (ver 28)",
141 &reser_contents.1,
142 &orig_contents.1,
143 command,
144 );
145 }
146 }
147 Some(ToClientCommand::NodemetaChanged(_))
148 | Some(ToClientCommand::Itemdef(_))
149 | Some(ToClientCommand::Nodedef(_)) => {
150 let reser = zlib_decompress_to_vec(&reser[6..]);
153 let orig = zlib_decompress_to_vec(&orig[6..]);
154 do_compare("zlib decompressed body", &reser, &orig, command);
155 }
156 _ => {
157 do_compare("default", reser, orig, command);
158 }
159 };
160 Ok(())
161}
162
163fn do_compare<Cmd: CommandRef>(what: &str, reser: &[u8], orig: &[u8], command: &Cmd) {
164 if reser != orig {
165 println!(
166 "AUDIT: Mismatch between original and re-serialized ({})",
167 what
168 );
169 println!("AUDIT: ORIGINAL = {:?}", orig);
170 println!("AUDIT: RESERIALIZED = {:?}", reser);
171 println!("AUDIT: PARSED = {:?}", command);
172 std::process::exit(1);
173 }
174}
175
176fn zlib_decompress_to_vec(compressed: &[u8]) -> Vec<u8> {
177 match miniz_oxide::inflate::decompress_to_vec_zlib(compressed) {
178 Ok(uncompressed) => uncompressed,
179 Err(_) => {
180 println!("AUDIT: Decompression failed unexpectedly");
181 std::process::exit(1);
182 }
183 }
184}
185
186fn zstd_decompress_to_vec(compressed: &[u8]) -> Result<Vec<u8>> {
187 let mut result = Vec::new();
188 zstd_decompress(compressed, |chunk| {
189 result.extend(chunk);
190 Ok(())
191 })?;
192 Ok(result)
193}