smb_msg/
compressed.rs

1//! Compressed messages
2
3use std::io::SeekFrom;
4
5use smb_dtyp::binrw_util::prelude::*;
6use smb_msg_derive::smb_message_binrw;
7
8use super::negotiate::CompressionAlgorithm;
9use binrw::io::TakeSeekExt;
10use binrw::prelude::*;
11
12/// SMB2 compression transform header variants for compressed messages.
13///
14/// Used by client or server when sending compressed messages in SMB 3.1.1 dialect.
15/// The variant is determined by the compression flags.
16///
17/// MS-SMB2 2.2.42
18#[smb_message_binrw]
19#[brw(little)]
20pub enum CompressedMessage {
21    Unchained(CompressedUnchainedMessage),
22    Chained(CompressedChainedMessage),
23}
24
25impl CompressedMessage {
26    /// Calculates the total size of the compressed message including headers and data.
27    pub fn total_size(&self) -> usize {
28        match self {
29            CompressedMessage::Unchained(m) => {
30                m.data.len() + CompressedUnchainedMessage::STRUCT_SIZE
31            }
32            CompressedMessage::Chained(m) => {
33                m.items.iter().map(|i| i.payload_data.len()).sum::<usize>()
34                    + m.items.len() * 4
35                    + CompressedChainedMessage::STRUCT_SIZE
36            }
37        }
38    }
39}
40
41/// SMB2 compression transform header for unchained compressed messages.
42///
43/// Used when sending unchained compressed messages where the flags field is zero.
44/// Only valid for SMB 3.1.1 dialect.
45///
46/// MS-SMB2 2.2.42.1
47#[smb_message_binrw]
48#[brw(magic(b"\xfcSMB"), little)]
49pub struct CompressedUnchainedMessage {
50    /// Size of the uncompressed data segment
51    pub original_size: u32,
52    /// Compression algorithm used (cannot be None for compressed messages)
53    #[brw(assert(!matches!(compression_algorithm, CompressionAlgorithm::None)))]
54    pub compression_algorithm: CompressionAlgorithm,
55    /// Must be set to SMB2_COMPRESSION_FLAG_NONE (0x0000)
56    #[br(assert(flags == 0))]
57    #[bw(calc = 0)]
58    flags: u16,
59    /// Offset from end of structure to start of compressed data segment
60    #[bw(calc = 0)]
61    offset: u32,
62    /// Compressed data payload
63    #[br(seek_before = SeekFrom::Current(offset as i64))]
64    #[br(parse_with = binrw::helpers::until_eof)]
65    pub data: Vec<u8>,
66}
67
68impl CompressedUnchainedMessage {
69    /// Size of the protocol identifier magic bytes
70    const MAGIC_SIZE: usize = 4;
71    /// Total size of the unchained compression header structure (excluding data)
72    pub const STRUCT_SIZE: usize = Self::MAGIC_SIZE
73        + std::mem::size_of::<u32>() * 2
74        + std::mem::size_of::<CompressionAlgorithm>()
75        + std::mem::size_of::<u16>();
76}
77
78/// SMB2 compression transform header for chained compressed messages.
79///
80/// Used when sending compressed and chained SMB2 messages where the flags field
81/// is SMB2_COMPRESSION_FLAG_CHAINED (0x0001). Only valid for SMB 3.1.1 dialect.
82///
83/// MS-SMB2 2.2.42.2
84#[smb_message_binrw]
85#[brw(magic(b"\xfcSMB"), little)]
86pub struct CompressedChainedMessage {
87    /// Size of the uncompressed data segment
88    pub original_size: u32,
89    /// Variable length array of compression payload headers
90    #[br(parse_with = binrw::helpers::until_eof)]
91    pub items: Vec<CompressedChainedItem>,
92}
93
94impl CompressedChainedMessage {
95    /// Total size of the chained compression header structure (excluding payload headers)
96    pub const STRUCT_SIZE: usize = std::mem::size_of::<u32>() + 4;
97}
98
99/// Calculates additional bytes to include in length field when OriginalPayloadSize is present.
100fn add_original_size_to_total_length(algo: &CompressionAlgorithm) -> u64 {
101    if algo.original_size_required() {
102        std::mem::size_of::<u32>() as u64
103    } else {
104        0
105    }
106}
107
108/// SMB2 compression chained payload header.
109///
110/// Used when sending chained compressed payloads. This structure is added for each
111/// compressed payload in a chained message. Only valid for SMB 3.1.1 dialect.
112///
113/// MS-SMB2 2.2.42.2.1
114#[smb_message_binrw]
115pub struct CompressedChainedItem {
116    /// Compression algorithm used for this payload
117    pub compression_algorithm: CompressionAlgorithm,
118    /// Compression flags (SMB2_COMPRESSION_FLAG_NONE or SMB2_COMPRESSION_FLAG_CHAINED)
119    pub flags: u16,
120    /// Length of compressed payload including OriginalPayloadSize if present
121    #[bw(calc = PosMarker::default())]
122    #[br(temp)]
123    length: PosMarker<u32>,
124    /// Size of uncompressed payload (present only for LZNT1, LZ77, LZ77+Huffman, or LZ4)
125    #[brw(if(compression_algorithm.original_size_required()))]
126    #[bw(assert(original_size.is_none() ^ compression_algorithm.original_size_required()))]
127    pub original_size: Option<u32>,
128    /// Compressed payload data
129    #[br(map_stream = |s| s.take_seek(length.value as u64 - (add_original_size_to_total_length(&compression_algorithm))), parse_with = binrw::helpers::until_eof)]
130    #[bw(write_with = PosMarker::write_size_plus, args(&length, add_original_size_to_total_length(compression_algorithm)))]
131    pub payload_data: Vec<u8>,
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137    use smb_tests::*;
138
139    // TODO(TEST): unchained
140
141    test_binrw! {
142        CompressedMessage => chained0: CompressedMessage::Chained(CompressedChainedMessage {
143                original_size: 368,
144                items: vec![
145                    CompressedChainedItem {
146                        compression_algorithm: CompressionAlgorithm::None,
147                        flags: 1,
148                        original_size: None,
149                        payload_data: vec![
150                            0xfe, 0x53, 0x4d, 0x42, 0x40, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10,
151                            0x0, 0x1, 0x0, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x91, 0x0, 0x0,
152                            0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xfe, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0,
153                            0x7d, 0x0, 0x0, 0x28, 0x0, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
154                            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x29, 0x0, 0x1,
155                            0xf, 0x2a, 0x2, 0x0, 0x0, 0x68, 0x0, 0x0, 0x0, 0x8, 0x1, 0x0, 0x0, 0x0,
156                            0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0xee, 0x5, 0x0, 0x0, 0xc, 0x0, 0x0,
157                            0x0, 0x8d, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0
158                        ],
159                    },
160                    CompressedChainedItem {
161                        compression_algorithm: CompressionAlgorithm::None,
162                        flags: 0xb975,
163                        original_size: None,
164                        payload_data: vec![
165                            0x0, 0x0, 0x0, 0x0, 0x15, 0x24, 0x4d, 0x70, 0x45, 0x61, 0x5f, 0x44,
166                            0x32, 0x36, 0x32, 0x41, 0x43, 0x36, 0x32, 0x34, 0x34, 0x35, 0x31, 0x32,
167                            0x39, 0x35
168                        ],
169                    },
170                    CompressedChainedItem {
171                        compression_algorithm: CompressionAlgorithm::PatternV1,
172                        flags: 0,
173                        original_size: None,
174                        payload_data: vec![0x0, 0x0, 0x0, 0x0, 0xee, 0x0, 0x0, 0x0]
175                    }
176                ]
177            }) => "fc534d42700100000000010068000000fe534d4240000100000000001000010030000000000000009100000000000000fffe0000010000007d00002800300000000000000000000000000000000000002900010f2a02000068000000080100000000000003000000ee0500000c0000008d0000000c000000000075b91a0000000000000015244d7045615f443236324143363234343531323935040000000800000000000000ee000000"
178    }
179
180    /// I do it for the sake of some real, big data.
181    const CHAINED1_ITEM2_DATA: &'static str = "f2034d5a90000300000004000000ffff0000b8000\
182    100124007000f02000af32e200100000e1fba0e00b409cd21b8014ccd21546869732070726f677\
183    2616d2063616e6e6f742062652072756e20696e20444f53206d6f64652e0d0d0a245a0084a98ee\
184    eb9edef80ea0400d1996e86ebddef80ea9f6e83ebe81000b181ebe1ef80eabe9084ebec0800318\
185    3ebe320003181ebef3800b181eaadef80eae49713eaf010003180ea7f38004088eb5ee94000318\
186    5ebf310003184eb9008003183eba908001180500040996e7fea580031996e82100040526963684\
187    400039f00d4005045000064862400bbf4ba231400f10bf00022000b020e260090a60000e01d000\
188    0f061009002b100001022002040010700000c00561000000a00040001020030f044012800b126d\
189    1c2000100604100000817002200200700013500000200004000030200010b00c17014008fb3010\
190    028471400b86100f3144001288e030000700d00b4cf06000070c200b825000000904301dc5f000\
191    0101004007040000f02000120a05b4200071a0057401400f80610000b0200b22e7264617461000\
192    0305c0d91001360080007020062400000482e702800029400400d0000d09c00170d26000328001\
193    26928002224267c0023003008000702000150001265280000fc00630070140000c00800070200c\
194    14000004050524f54444154411b01223016a400000800070200005000a047464944530000002ca\
195    9700043160000b0080007020081400000425061643113006110090000f0160b000c0200f200800\
196    000422e746578740000000ba14cc5012db04c30008020000068504147454000f0008042440000b\
197    06c000050440000a063130005020040200000602800f5044c4b00001c6402000000b1000070020\
198    000f0a72400000200012800804f4f4c434f4445bef4012270b340012060aa1f00050200047800e\
199    04b440000ea5d000000a0b300006024020d2800017800605652465919150e048fb400002003000\
200    0f02800035048444c53760e032220b778002510ae740000020001a000904147454247465868694\
201    50321b7008d021e402800f1005452414345535550a319000000c0b73d002d00b02800014001b24\
202    34d5243f30e000000e0b7e0011dd0280050604b5641531801107e610413f0a0001de0280050684\
203    b534350ac0010607f032220b850002010af1300050200004001904452565052580000b71600133\
204    028001e20280050666f74686b240000ad03134028001e302800e0494e49544b444247a6f101000\
205    0506e054e020000402800904d494e4945580000bc20032250ba68012040b162000502004020000\
206    0625000001100ee1be009000080ba0000f0090000702800405061643228007100901b000070c40\
207    b000c020052800000622e6f03400080291c690100ed0449000060bb2b00f104400000c8414c4d4\
208    f5354524f409c00000030fc8d003d0050bc2800f2004341434845414c49008e000000d0fc20011\
209    e70280000f80200c0037250b401000060fd50001d80280010c02800e056524644503c01000020f\
210    f0000a0d8020e280000180100500030b41402f402017405390040bda00081200000c2506164331\
211    50061801d000080023f040c020090800000c2434647524fe30001a80521200170031a505000001\
212    8014150616434400050d01f00003022070e020080800000ca2e727372fe0301c4059d004001009\
213    0030000805000c1422e72656c6f630000dc5501dc0578006001000010c15500500040000042";
214
215    const CHAINED1_TEST_DATA: &'static str = const_format::concatcp!(
216        "fc534d42501000000000010050000000fe534d424000010000000000080001001900000000000000070000000000000000000000010000001d00000000600000251698bc898e3e86aeb713557cfaf1bb1100500000100000000000000000000005000000f7040000c8070000",
217        CHAINED1_ITEM2_DATA,
218        "04000000080000000000000038080000"
219    );
220
221    test_binrw! {
222        CompressedMessage => chained1: CompressedMessage::Chained(CompressedChainedMessage {
223                original_size: 4176,
224                items: vec![
225                    CompressedChainedItem {
226                        compression_algorithm: CompressionAlgorithm::None,
227                        flags: 1,
228                        original_size: None,
229                        payload_data: vec![
230                            0xfe, 0x53, 0x4d, 0x42, 0x40, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8,
231                            0x0, 0x1, 0x0, 0x19, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0x0, 0x0,
232                            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1d,
233                            0x0, 0x0, 0x0, 0x0, 0x60, 0x0, 0x0, 0x25, 0x16, 0x98, 0xbc, 0x89, 0x8e,
234                            0x3e, 0x86, 0xae, 0xb7, 0x13, 0x55, 0x7c, 0xfa, 0xf1, 0xbb, 0x11, 0x0,
235                            0x50, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0
236                        ],
237                    },
238                    CompressedChainedItem {
239                        compression_algorithm: CompressionAlgorithm::LZ4,
240                        flags: 0,
241                        original_size: Some(0x7c8),
242                        payload_data: smb_tests::hex_to_u8_array! {CHAINED1_ITEM2_DATA}
243                    },
244                    CompressedChainedItem {
245                        compression_algorithm: CompressionAlgorithm::PatternV1,
246                        flags: 0,
247                        original_size: None,
248                        payload_data: vec![0x0, 0x0, 0x0, 0x0, 0x38, 0x8, 0x0, 0x0]
249                    },
250                ]
251            }) => CHAINED1_TEST_DATA
252    }
253
254    test_binrw! {
255        CompressedMessage => multiple2: CompressedMessage::Chained(CompressedChainedMessage {
256            original_size: 368,
257            items: vec![
258                CompressedChainedItem {
259                    compression_algorithm: CompressionAlgorithm::None,
260                    flags: 1,
261                    original_size: None,
262                    payload_data: vec![
263                        0xfe, 0x53, 0x4d, 0x42, 0x40, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x0,
264                        0x1, 0x0, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1e, 0x3, 0x0, 0x0,
265                        0x0, 0x0, 0x0, 0x0, 0xff, 0xfe, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x9, 0x0,
266                        0x0, 0x2c, 0x0, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
267                        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x29, 0x0, 0x1, 0xf, 0x2a, 0x2,
268                        0x0, 0x0, 0x68, 0x0, 0x0, 0x0, 0x8, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3,
269                        0x0, 0x0, 0x0, 0x11, 0x7, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0x69, 0x0, 0x20,
270                        0x0, 0xc, 0x0, 0x0, 0x0,
271                    ],
272                },
273                CompressedChainedItem {
274                    compression_algorithm: CompressionAlgorithm::None,
275                    flags: 0,
276                    original_size: None,
277                    payload_data: vec![
278                        0x0, 0x0, 0x0, 0x0, 0x15, 0x24, 0x4d, 0x70, 0x45, 0x61, 0x5f, 0x44, 0x32,
279                        0x36, 0x32, 0x41, 0x43, 0x36, 0x32, 0x34, 0x34, 0x35, 0x31, 0x32, 0x39,
280                        0x35,
281                    ],
282                },
283                CompressedChainedItem {
284                    compression_algorithm: CompressionAlgorithm::PatternV1,
285                    flags: 0,
286                    original_size: None,
287                    payload_data: vec![0x0, 0x0, 0x0, 0x0, 0xee, 0x0, 0x0, 0x0],
288                },
289            ],
290        }) => "fc534d42700100000000010068000000fe534d4240000100000000001000010030000000000000001e03000000000000fffe0000050000000900002c00300000000000000000000000000000000000002900010f2a02000068000000080100000000000003000000110700000c000000690020000c000000000000001a0000000000000015244d7045615f443236324143363234343531323935040000000800000000000000ee000000"
291    }
292}