smb_server_proto_smb1/query.rs
1//! QUERY_INFORMATION payload encoders for the information levels
2//! ([MS-SMB] §2.2.8.2–4).
3
4use smb_server_proto::buf::Writer;
5use smb_server_proto::types::AttrFlags;
6
7/// Neutral metadata snapshot used by query payload encoders.
8#[derive(Debug, Clone, Default)]
9pub struct QueryMeta {
10 /// Creation / access / write / change FILETIMEs.
11 pub times: [u64; 4],
12 /// Attribute flags.
13 pub attrs: AttrFlags,
14 /// End-of-file size.
15 pub eof: u64,
16 /// Allocation size.
17 pub alloc: u64,
18 /// Directory flag.
19 pub is_dir: bool,
20}
21
22/// Encode a QUERY_PATH/FILE information payload for `level` from neutral
23/// metadata. Returns `None` for levels this server does not implement —
24/// callers translate that to `INVALID_PARAMETER`.
25///
26/// `name` is required by the NAME/ALT_NAME levels.
27pub fn encode_payload(
28 level: u16,
29 m: &QueryMeta,
30 name: &str,
31) -> Option<Vec<u8>> {
32 let times = &m.times;
33 let eof = if m.is_dir { 0 } else { m.eof };
34 let alloc = eof.div_ceil(4096) * 4096;
35
36 let mut d = Writer::new(0);
37 match level {
38 0x004 | 0x1004 | 0x101 => {
39 // BASIC: creation, access, write, change + attributes.
40 for t in ×[..3] {
41 d.push_u64(*t);
42 }
43 d.push_u64(times[3]);
44 d.raw(&m.attrs.0.to_le_bytes());
45 }
46 0x005 => {
47 // STANDARD (22-byte non-passthrough form).
48 d.push_u64(alloc);
49 d.push_u64(eof);
50 d.push_u32(1); // links
51 d.push(if m.attrs.0 & 0x1 != 0 { 1 } else { 0 }); // delete pending
52 d.push(m.is_dir as u8);
53 }
54 0x1005 => {
55 // STANDARD pass-through (24-byte form with reserved word).
56 d.push_u64(alloc);
57 d.push_u64(eof);
58 d.push_u32(1); // links
59 d.push(0); // delete pending
60 d.push(m.is_dir as u8);
61 d.push_u16(0); // reserved
62 }
63 0x006 | 0x1007 => {
64 d.push_u32(0); // EA size
65 }
66 0x007 => {
67 d.push_u32(4); // all-EA list length
68 d.push_u32(0); // full list length
69 }
70 0x1006 => {
71 d.push_u64(0); // index number placeholder
72 }
73 0x1008 => {
74 d.push_u32(0x0012_0089 | 0x0000_0016); // read+write access flags
75 }
76 0x1009 | 0x1015 => {
77 let units: Vec<u16> = name.encode_utf16().collect();
78 d.push_u32((units.len() * 2) as u32);
79 for u in units {
80 d.push_u16(u);
81 }
82 }
83 0x100B | 0x103 => {
84 d.push_u64(alloc);
85 }
86 0x100C | 0x104 => {
87 d.push_u64(eof);
88 }
89 0x100D => {
90 d.push(0);
91 }
92 0x1012 => {
93 for t in times.iter() {
94 d.push_u64(*t);
95 }
96 d.push_u64(alloc);
97 d.push_u64(eof);
98 d.push_u32(1); // links
99 d.push(0); // delete pending
100 d.push(m.is_dir as u8);
101 d.push_u16(0); // reserved
102 d.push_u32(0); // EA size
103 d.push_u32(0x0012_0089 | 0x16); // access flags
104 d.push_u64(0); // current byte offset
105 d.raw(&m.attrs.0.to_le_bytes());
106 }
107 0x010 | 0x1016 => {
108 d.push_u32(0); // empty stream list
109 }
110 0x107 => {
111 // Composite getattr: times, attrs, rsvd, alloc, eof, links,
112 // del/dir flags, pad, index, namelen, name.
113 for t in times.iter() {
114 d.push_u64(*t);
115 }
116 d.raw(&m.attrs.0.to_le_bytes());
117 d.push_u32(0); // reserved
118 d.push_u64(alloc);
119 d.push_u64(eof);
120 d.push_u32(1); // links
121 d.push(0); // delete pending
122 d.push(m.is_dir as u8);
123 d.push_u16(0); // pad
124 d.push_u32(0); // index
125 let units: Vec<u16> = name.encode_utf16().collect();
126 d.push_u32((units.len() * 2) as u32);
127 for u in units {
128 d.push_u16(u);
129 }
130 }
131 _ => return None,
132 }
133 Some(d.into_inner())
134}