tokio_nbd/flags.rs
1//! Flag definitions for the NBD (Network Block Device) protocol.
2//!
3//! This module contains the various flag types used in the Network Block Device protocol:
4//!
5//! - [`CommandFlags`]: Used with command requests to modify their behavior
6//! - [`ServerFeatures`]: Public interface for NBD drivers to expose their capabilities
7//!
8//! All flags are implemented using the [`bitflags`](https://docs.rs/bitflags) crate.
9//!
10//! # Examples
11//!
12//! ```
13//! use tokio_nbd::flags::{CommandFlags, ServerFeatures};
14//!
15//! // Create a flags value with multiple flags set
16//! let cmd_flags = CommandFlags::FUA | CommandFlags::DF;
17//!
18//! // Testing if a flag is set
19//! if cmd_flags.contains(CommandFlags::FUA) {
20//! println!("Force Unit Access flag is set");
21//! }
22//!
23//! // Create server features
24//! let features = ServerFeatures::SEND_FLUSH | ServerFeatures::SEND_FUA | ServerFeatures::SEND_TRIM;
25//! ```
26
27/// Shared flag bit values used in both `ServerFeatures` and `TransmissionFlags`.
28///
29/// These constants define the bit patterns for NBD protocol features.
30/// Using shared constants ensures consistency between the two flag types.
31///
32/// The documentation here provides detailed explanations for each flag used in the NBD protocol.
33mod flag_bits {
34 // Administrative flags (TransmissionFlags only)
35
36 /// MUST always be 1 in valid NBD protocol communications.
37 pub(crate) const HAS_FLAGS: u16 = 0b00000001;
38
39 /// Indicates the export is read-only. If set, the server MUST error on write operations.
40 pub(crate) const READ_ONLY: u16 = 0b00000010;
41
42 // Feature flags (shared between ServerFeatures and TransmissionFlags)
43
44 /// Exposes support for `NBD_CMD_FLUSH`.
45 pub(crate) const SEND_FLUSH: u16 = 0b00000100;
46
47 /// Exposes support for `NBD_CMD_FLAG_FUA` (Force Unit Access).
48 pub(crate) const SEND_FUA: u16 = 0b00001000;
49
50 /// Indicates the export has characteristics of a rotational medium.
51 /// The client MAY schedule I/O accesses accordingly.
52 pub(crate) const ROTATIONAL: u16 = 0b00010000;
53
54 /// Exposes support for `NBD_CMD_TRIM`.
55 pub(crate) const SEND_TRIM: u16 = 0b00100000;
56
57 /// Exposes support for `NBD_CMD_WRITE_ZEROES` and `NBD_CMD_FLAG_NO_HOLE`.
58 pub(crate) const SEND_WRITE_ZEROES: u16 = 0b01000000;
59
60 /// Do not fragment a structured reply.
61 /// Indicates the server supports the `NBD_CMD_FLAG_DF` request flag.
62 pub(crate) const SEND_DF: u16 = 0b10000000;
63
64 /// Indicates that the server operates without cache or with a shared cache,
65 /// making `FLUSH` and `FUA` operations visible across all connections.
66 /// Without this flag, clients SHOULD NOT multiplex commands over multiple connections.
67 pub(crate) const CAN_MULTI_CONN: u16 = 0b00000001_00000000;
68
69 /// Exposes support for the experimental RESIZE extension.
70 pub(crate) const SEND_RESIZE: u16 = 0b00000010_00000000;
71
72 /// Documents that the server understands `NBD_CMD_CACHE`.
73 /// Note that some servers may support the command without this bit,
74 /// and this flag doesn't guarantee the command will succeed.
75 pub(crate) const SEND_CACHE: u16 = 0b00000100_00000000;
76
77 /// Allows clients to detect if `NBD_CMD_WRITE_ZEROES` is faster than
78 /// a corresponding write via the `NBD_CMD_FLAG_FAST_ZERO` request flag.
79 pub(crate) const SEND_FAST_ZERO: u16 = 0b00001000_00000000;
80
81 /// Defined by the experimental EXTENDED_HEADERS extension.
82 pub(crate) const BLOCK_STATUS_PAYLOAD: u16 = 0b00010000_00000000;
83}
84
85bitflags::bitflags! {
86 /// Handshake flags used during the initial NBD protocol negotiation.
87 ///
88 /// This 16-bit field is sent by the server after the `INIT_PASSWD` and the first magic number.
89 ///
90 /// According to the NBD protocol specification:
91 /// - The server MUST NOT set any flags other than those defined here
92 /// - The server SHOULD NOT change behavior unless the client responds with a corresponding flag
93 /// - The server MUST NOT set any of these flags during oldstyle negotiation
94 ///
95 /// Additional capability flags are unlikely to be defined in the NBD protocol since
96 /// this phase is susceptible to MitM downgrade attacks when using TLS. Additional features
97 /// are best negotiated using protocol options.
98 #[derive(Debug)]
99 pub(crate) struct HandshakeFlags: u16 {
100 /// MUST be set by servers that support the fixed newstyle protocol.
101 const FIXED_NEWSTYLE = 0b00000001;
102
103 /// If set, and if the client replies with `NBD_FLAG_C_NO_ZEROES` in the client flags field,
104 /// the server MUST NOT send the 124 bytes of zero when the client ends negotiation with
105 /// `NBD_OPT_EXPORT_NAME`.
106 const NO_ZEROES = 0b00000010;
107 }
108
109}
110impl Default for HandshakeFlags {
111 fn default() -> Self {
112 Self::FIXED_NEWSTYLE | Self::NO_ZEROES
113 }
114}
115
116bitflags::bitflags! {
117 /// Command flags sent with NBD command requests to modify their behavior.
118 ///
119 /// These flags are used to specify special handling for individual command requests,
120 /// such as forced unit access, handling of write zeroes, and structured reply options.
121 ///
122 /// Available flags:
123 /// - `CommandFlags::FUA` (0x0001): Force Unit Access - ensures data is written to stable storage before reply.
124 /// - `CommandFlags::NO_HOLE` (0x0002): When set on a write zeroes command, the server should ensure that the operation
125 /// creates a hole (i.e., will read back as zeroes) but need not guarantee allocation. If clear, the server may
126 /// punch a hole or write zeroes as it sees fit.
127 /// - `CommandFlags::DF` (0x0004): Don't Fragment - indicates that structured replies should not be split
128 /// across multiple reply chunks.
129 /// - `CommandFlags::REQ_ONE` (0x0008): Request that the server only provides one (i.e., the first) content
130 /// range when replying to a block status command.
131 /// - `CommandFlags::FAST_ZERO` (0x0010): Fast Zero - indicates that the client would prefer the server to fail
132 /// the request rather than perform a time-consuming write of zeroes.
133 /// - `CommandFlags::PAYLOAD_LEN` (0x0020): Indicates that the command carries a payload whose length is encoded
134 /// as part of the extended header.
135 #[derive(Debug)]
136 pub struct CommandFlags: u16 {
137 /// Force Unit Access (FUA) - ensures data is written to stable storage before reply.
138 const FUA = 0b00000001;
139
140 /// When set on a write zeroes command, the server should ensure that the operation
141 /// creates a hole (i.e., will read back as zeroes) but need not guarantee allocation.
142 /// If clear, the server may punch a hole or write zeroes as it sees fit.
143 const NO_HOLE = 0b00000010;
144
145 /// Don't Fragment - indicates that structured replies should not be split
146 /// across multiple reply chunks.
147 const DF = 0b00000100;
148
149 /// Request that the server only provides one (i.e., the first) content range
150 /// when replying to a block status command.
151 const REQ_ONE = 0b00001000;
152
153 /// Fast Zero - indicates that the client would prefer the server to fail
154 /// the request rather than perform a time-consuming write of zeroes.
155 const FAST_ZERO = 0b00010000;
156
157 /// Indicates that the command carries a payload whose length is encoded
158 /// as part of the extended header.
159 const PAYLOAD_LEN = 0b00100000;
160 }
161}
162impl TryFrom<u16> for CommandFlags {
163 type Error = u16;
164
165 /// Attempts to convert a raw u16 value into CommandFlags.
166 ///
167 /// # Returns
168 /// - `Ok(CommandFlags)` if all bits in the value represent valid flags
169 /// - `Err(value)` if any bits in the value don't correspond to defined flags
170 fn try_from(value: u16) -> Result<Self, Self::Error> {
171 match Self::from_bits(value) {
172 Some(flags) => Ok(flags),
173 None => Err(value),
174 }
175 }
176}
177
178bitflags::bitflags! {
179 /// Features supported by an NBD server implementation.
180 ///
181 /// Derived from transmission flags but excluding administrative flags like HAS_FLAGS and READ_ONLY.
182 /// These flags allow the server to advertise which features it supports.
183 ///
184 /// For each flag, the server:
185 /// - MAY set the flag for features it supports
186 /// - MUST NOT set the flag for features it does not support
187 /// - The client MUST NOT use a feature documented as 'exposed' by a flag unless that flag was set
188 ///
189 /// Available flags:
190 /// - `ServerFeatures::SEND_FLUSH` (0x0004): Exposes support for `NBD_CMD_FLUSH`.
191 /// - `ServerFeatures::SEND_FUA` (0x0008): Exposes support for `NBD_CMD_FLAG_FUA` (Force Unit Access).
192 /// - `ServerFeatures::ROTATIONAL` (0x0010): Indicates the export has characteristics of a rotational medium.
193 /// The client MAY schedule I/O accesses accordingly.
194 /// - `ServerFeatures::SEND_TRIM` (0x0020): Exposes support for `NBD_CMD_TRIM`.
195 /// - `ServerFeatures::SEND_WRITE_ZEROES` (0x0040): Exposes support for `NBD_CMD_WRITE_ZEROES` and `NBD_CMD_FLAG_NO_HOLE`.
196 /// - `ServerFeatures::SEND_DF` (0x0080): Do not fragment a structured reply.
197 /// Indicates the server supports the `NBD_CMD_FLAG_DF` request flag.
198 /// - `ServerFeatures::CAN_MULTI_CONN` (0x0100): Indicates that the server operates without cache or with a shared cache,
199 /// making `FLUSH` and `FUA` operations visible across all connections.
200 /// Without this flag, clients SHOULD NOT multiplex commands over multiple connections.
201 /// - `ServerFeatures::SEND_RESIZE` (0x0200): Exposes support for the experimental RESIZE extension.
202 /// - `ServerFeatures::SEND_CACHE` (0x0400): Documents that the server understands `NBD_CMD_CACHE`.
203 /// Note that some servers may support the command without this bit,
204 /// and this flag doesn't guarantee the command will succeed.
205 /// - `ServerFeatures::SEND_FAST_ZERO` (0x0800): Allows clients to detect if `NBD_CMD_WRITE_ZEROES` is faster than
206 /// a corresponding write via the `NBD_CMD_FLAG_FAST_ZERO` request flag.
207 /// - `ServerFeatures::BLOCK_STATUS_PAYLOAD` (0x1000): Defined by the experimental EXTENDED_HEADERS extension.
208 #[derive(Debug)]
209 pub struct ServerFeatures: u16 {
210 const SEND_FLUSH = flag_bits::SEND_FLUSH;
211 const SEND_FUA = flag_bits::SEND_FUA;
212 const ROTATIONAL = flag_bits::ROTATIONAL;
213 const SEND_TRIM = flag_bits::SEND_TRIM;
214 const SEND_WRITE_ZEROES = flag_bits::SEND_WRITE_ZEROES;
215 const SEND_DF = flag_bits::SEND_DF;
216 const CAN_MULTI_CONN = flag_bits::CAN_MULTI_CONN;
217 const SEND_RESIZE = flag_bits::SEND_RESIZE;
218 const SEND_CACHE = flag_bits::SEND_CACHE;
219 const SEND_FAST_ZERO = flag_bits::SEND_FAST_ZERO;
220 const BLOCK_STATUS_PAYLOAD = flag_bits::BLOCK_STATUS_PAYLOAD;
221 }
222}
223
224bitflags::bitflags! {
225 /// Transmission flags sent by the server after option haggling, or
226 /// immediately after the handshake flags field in oldstyle negotiation.
227 ///
228 /// This 16-bit field includes both administrative flags (like HAS_FLAGS and READ_ONLY)
229 /// and feature flags that describe the server's capabilities.
230 ///
231 /// See `flag_bits` module for detailed documentation of each flag.
232 #[derive(Debug)]
233 pub(crate) struct TransmissionFlags: u16 {
234 const HAS_FLAGS = flag_bits::HAS_FLAGS;
235 const READ_ONLY = flag_bits::READ_ONLY;
236 const SEND_FLUSH = flag_bits::SEND_FLUSH;
237 const SEND_FUA = flag_bits::SEND_FUA;
238 const ROTATIONAL = flag_bits::ROTATIONAL;
239 const SEND_TRIM = flag_bits::SEND_TRIM;
240 const SEND_WRITE_ZEROES = flag_bits::SEND_WRITE_ZEROES;
241 const SEND_DF = flag_bits::SEND_DF;
242 const CAN_MULTI_CONN = flag_bits::CAN_MULTI_CONN;
243 const SEND_RESIZE = flag_bits::SEND_RESIZE;
244 const SEND_CACHE = flag_bits::SEND_CACHE;
245 const SEND_FAST_ZERO = flag_bits::SEND_FAST_ZERO;
246 const BLOCK_STATUS_PAYLOAD = flag_bits::BLOCK_STATUS_PAYLOAD;
247 }
248}
249
250impl From<ServerFeatures> for TransmissionFlags {
251 /// Converts ServerFeatures to TransmissionFlags.
252 ///
253 /// This implementation automatically adds the required HAS_FLAGS bit,
254 /// which must always be set in valid NBD protocol communications.
255 fn from(features: ServerFeatures) -> Self {
256 Self::HAS_FLAGS | Self::from_bits_truncate(features.bits())
257 }
258}
259
260#[cfg(test)]
261mod tests {
262 use super::*;
263
264 #[test]
265 fn test_handshake_flags() {
266 // Test individual flags
267 assert_eq!(HandshakeFlags::FIXED_NEWSTYLE.bits(), 0b00000001);
268 assert_eq!(HandshakeFlags::NO_ZEROES.bits(), 0b00000010);
269
270 // Test default implementation
271 let default_flags = HandshakeFlags::default();
272 assert!(default_flags.contains(HandshakeFlags::FIXED_NEWSTYLE));
273 assert!(default_flags.contains(HandshakeFlags::NO_ZEROES));
274 assert_eq!(default_flags.bits(), 0b00000011);
275
276 // Test from_bits and contains
277 let flags = HandshakeFlags::from_bits(0b00000001).unwrap();
278 assert!(flags.contains(HandshakeFlags::FIXED_NEWSTYLE));
279 assert!(!flags.contains(HandshakeFlags::NO_ZEROES));
280
281 // Test invalid bits are rejected
282 assert!(HandshakeFlags::from_bits(0b11111100).is_none());
283
284 // Test union operation
285 let flags1 = HandshakeFlags::FIXED_NEWSTYLE;
286 let flags2 = HandshakeFlags::NO_ZEROES;
287 let combined = flags1 | flags2;
288 let default = HandshakeFlags::default();
289 assert_eq!(combined.bits(), default.bits());
290 }
291
292 #[test]
293 fn test_command_flags() {
294 // Test individual flags
295 assert_eq!(CommandFlags::FUA.bits(), 0b00000001);
296 assert_eq!(CommandFlags::NO_HOLE.bits(), 0b00000010);
297 assert_eq!(CommandFlags::DF.bits(), 0b00000100);
298 assert_eq!(CommandFlags::REQ_ONE.bits(), 0b00001000);
299 assert_eq!(CommandFlags::FAST_ZERO.bits(), 0b00010000);
300 assert_eq!(CommandFlags::PAYLOAD_LEN.bits(), 0b00100000);
301
302 // Test bitwise operations
303 let fua_and_df = CommandFlags::FUA | CommandFlags::DF;
304 assert!(fua_and_df.contains(CommandFlags::FUA));
305 assert!(fua_and_df.contains(CommandFlags::DF));
306 assert!(!fua_and_df.contains(CommandFlags::NO_HOLE));
307 assert_eq!(fua_and_df.bits(), 0b00000101);
308
309 // Test try_from
310 let fua_flag = CommandFlags::try_from(0b00000001).unwrap();
311 assert!(fua_flag.contains(CommandFlags::FUA));
312 assert!(!fua_flag.contains(CommandFlags::NO_HOLE));
313
314 let combined_flags = CommandFlags::try_from(0b00000011).unwrap();
315 assert!(combined_flags.contains(CommandFlags::FUA));
316 assert!(combined_flags.contains(CommandFlags::NO_HOLE));
317 assert!(!combined_flags.contains(CommandFlags::DF));
318
319 assert!(CommandFlags::try_from(0b11000000).is_err());
320 }
321
322 #[test]
323 fn test_server_features() {
324 // Test individual flags
325 assert_eq!(ServerFeatures::SEND_FLUSH.bits(), 0b00000100);
326 assert_eq!(ServerFeatures::SEND_FUA.bits(), 0b00001000);
327 assert_eq!(ServerFeatures::ROTATIONAL.bits(), 0b00010000);
328 assert_eq!(ServerFeatures::SEND_TRIM.bits(), 0b00100000);
329 assert_eq!(ServerFeatures::SEND_WRITE_ZEROES.bits(), 0b01000000);
330 assert_eq!(ServerFeatures::SEND_DF.bits(), 0b10000000);
331 assert_eq!(ServerFeatures::CAN_MULTI_CONN.bits(), 0b00000001_00000000);
332 assert_eq!(ServerFeatures::SEND_RESIZE.bits(), 0b00000010_00000000);
333 assert_eq!(ServerFeatures::SEND_CACHE.bits(), 0b00000100_00000000);
334 assert_eq!(ServerFeatures::SEND_FAST_ZERO.bits(), 0b00001000_00000000);
335 assert_eq!(
336 ServerFeatures::BLOCK_STATUS_PAYLOAD.bits(),
337 0b00010000_00000000
338 );
339
340 // Test bitwise operations
341 let features =
342 ServerFeatures::SEND_FLUSH | ServerFeatures::SEND_FUA | ServerFeatures::SEND_TRIM;
343 assert!(features.contains(ServerFeatures::SEND_FLUSH));
344 assert!(features.contains(ServerFeatures::SEND_FUA));
345 assert!(features.contains(ServerFeatures::SEND_TRIM));
346 assert!(!features.contains(ServerFeatures::ROTATIONAL));
347 assert_eq!(features.bits(), 0b00101100);
348 }
349
350 #[test]
351 fn test_transmission_flags() {
352 // Test individual flags
353 assert_eq!(TransmissionFlags::HAS_FLAGS.bits(), 0b00000001);
354 assert_eq!(TransmissionFlags::READ_ONLY.bits(), 0b00000010);
355 assert_eq!(TransmissionFlags::SEND_FLUSH.bits(), 0b00000100);
356 assert_eq!(TransmissionFlags::SEND_FUA.bits(), 0b00001000);
357 assert_eq!(TransmissionFlags::ROTATIONAL.bits(), 0b00010000);
358 assert_eq!(TransmissionFlags::SEND_TRIM.bits(), 0b00100000);
359 assert_eq!(TransmissionFlags::SEND_WRITE_ZEROES.bits(), 0b01000000);
360 assert_eq!(TransmissionFlags::SEND_DF.bits(), 0b10000000);
361 assert_eq!(
362 TransmissionFlags::CAN_MULTI_CONN.bits(),
363 0b00000001_00000000
364 );
365 assert_eq!(TransmissionFlags::SEND_RESIZE.bits(), 0b00000010_00000000);
366 assert_eq!(TransmissionFlags::SEND_CACHE.bits(), 0b00000100_00000000);
367 assert_eq!(
368 TransmissionFlags::SEND_FAST_ZERO.bits(),
369 0b00001000_00000000
370 );
371 assert_eq!(
372 TransmissionFlags::BLOCK_STATUS_PAYLOAD.bits(),
373 0b00010000_00000000
374 );
375
376 // Test bitwise operations
377 let basic_flags = TransmissionFlags::HAS_FLAGS
378 | TransmissionFlags::READ_ONLY
379 | TransmissionFlags::SEND_FLUSH;
380 assert!(basic_flags.contains(TransmissionFlags::HAS_FLAGS));
381 assert!(basic_flags.contains(TransmissionFlags::READ_ONLY));
382 assert!(basic_flags.contains(TransmissionFlags::SEND_FLUSH));
383 assert!(!basic_flags.contains(TransmissionFlags::SEND_FUA));
384 assert_eq!(basic_flags.bits(), 0b00000111);
385
386 // Test insertion and removal
387 let mut flags = TransmissionFlags::HAS_FLAGS;
388 flags.insert(TransmissionFlags::SEND_TRIM);
389 assert!(flags.contains(TransmissionFlags::SEND_TRIM));
390 assert_eq!(flags.bits(), 0b00100001);
391
392 flags.remove(TransmissionFlags::SEND_TRIM);
393 assert!(!flags.contains(TransmissionFlags::SEND_TRIM));
394 assert_eq!(flags.bits(), 0b00000001);
395 }
396
397 #[test]
398 fn test_server_features_to_transmission_flags() {
399 // Test conversion from ServerFeatures to TransmissionFlags
400 let features_bits =
401 ServerFeatures::SEND_FLUSH | ServerFeatures::SEND_FUA | ServerFeatures::SEND_TRIM;
402 let features_bits_value = features_bits.bits();
403
404 let transmission_flags: TransmissionFlags = features_bits.into();
405
406 // HAS_FLAGS should always be set
407 assert!(transmission_flags.contains(TransmissionFlags::HAS_FLAGS));
408
409 // The converted flags should contain all the original feature flags
410 assert!(transmission_flags.contains(TransmissionFlags::SEND_FLUSH));
411 assert!(transmission_flags.contains(TransmissionFlags::SEND_FUA));
412 assert!(transmission_flags.contains(TransmissionFlags::SEND_TRIM));
413
414 // Flags that weren't in the original ServerFeatures shouldn't be set
415 assert!(!transmission_flags.contains(TransmissionFlags::READ_ONLY));
416 assert!(!transmission_flags.contains(TransmissionFlags::ROTATIONAL));
417
418 // The bits should be the original features + HAS_FLAGS
419 assert_eq!(
420 transmission_flags.bits(),
421 features_bits_value | TransmissionFlags::HAS_FLAGS.bits()
422 );
423 }
424
425 #[test]
426 fn test_empty_server_features_conversion() {
427 // Test conversion of empty ServerFeatures
428 let empty_features = ServerFeatures::empty();
429 let transmission_flags: TransmissionFlags = empty_features.into();
430
431 // Only HAS_FLAGS should be set
432 assert!(transmission_flags.contains(TransmissionFlags::HAS_FLAGS));
433 assert_eq!(
434 transmission_flags.bits(),
435 TransmissionFlags::HAS_FLAGS.bits()
436 );
437 }
438}