Skip to main content

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}