Skip to main content

netget/server/nbd/
wire.rs

1//! NBD constants (the fixed newstyle handshake, options, transmission) and the read-only export
2//! a handler describes: a size, data extents, fill extents and error regions; everything else
3//! reads as zeroes.
4use anyhow::{bail, ensure, Context, Result};
5use serde_json::Value as Json;
6
7pub const NBDMAGIC: u64 = 0x4e42444d41474943;
8pub const IHAVEOPT: u64 = 0x49484156454F5054;
9pub const REPLY_MAGIC: u64 = 0x0003e889045565a9;
10pub const REQUEST_MAGIC: u32 = 0x25609513;
11pub const SIMPLE_REPLY_MAGIC: u32 = 0x67446698;
12pub const STRUCTURED_REPLY_MAGIC: u32 = 0x668e33ef;
13
14pub const FLAG_FIXED_NEWSTYLE: u16 = 1;
15pub const FLAG_NO_ZEROES: u16 = 2;
16
17pub const OPT_EXPORT_NAME: u32 = 1;
18pub const OPT_ABORT: u32 = 2;
19pub const OPT_LIST: u32 = 3;
20pub const OPT_STARTTLS: u32 = 5;
21pub const OPT_INFO: u32 = 6;
22pub const OPT_GO: u32 = 7;
23pub const OPT_STRUCTURED_REPLY: u32 = 8;
24pub const OPT_LIST_META_CONTEXT: u32 = 9;
25pub const OPT_SET_META_CONTEXT: u32 = 10;
26
27pub const REP_ACK: u32 = 1;
28pub const REP_SERVER: u32 = 2;
29pub const REP_INFO: u32 = 3;
30pub const REP_META_CONTEXT: u32 = 4;
31pub const REP_ERR_UNSUP: u32 = 0x8000_0001;
32pub const REP_ERR_POLICY: u32 = 0x8000_0002;
33pub const REP_ERR_INVALID: u32 = 0x8000_0003;
34pub const REP_ERR_UNKNOWN: u32 = 0x8000_0006;
35pub const REP_ERR_TOO_BIG: u32 = 0x8000_0009;
36
37pub const INFO_EXPORT: u16 = 0;
38pub const INFO_NAME: u16 = 1;
39pub const INFO_DESCRIPTION: u16 = 2;
40pub const INFO_BLOCK_SIZE: u16 = 3;
41
42pub const TFLAG_HAS_FLAGS: u16 = 1;
43pub const TFLAG_READ_ONLY: u16 = 1 << 1;
44pub const TFLAG_SEND_FLUSH: u16 = 1 << 2;
45pub const TFLAG_SEND_DF: u16 = 1 << 7;
46pub const TFLAG_CAN_MULTI_CONN: u16 = 1 << 8;
47pub const TFLAG_SEND_CACHE: u16 = 1 << 10;
48
49pub const CMD_READ: u16 = 0;
50pub const CMD_WRITE: u16 = 1;
51pub const CMD_DISC: u16 = 2;
52pub const CMD_FLUSH: u16 = 3;
53pub const CMD_TRIM: u16 = 4;
54pub const CMD_CACHE: u16 = 5;
55pub const CMD_WRITE_ZEROES: u16 = 6;
56pub const CMD_BLOCK_STATUS: u16 = 7;
57pub const CMD_FLAG_DF: u16 = 1 << 2;
58
59pub const REPLY_FLAG_DONE: u16 = 1;
60pub const REPLY_NONE: u16 = 0;
61pub const REPLY_OFFSET_DATA: u16 = 1;
62pub const REPLY_OFFSET_HOLE: u16 = 2;
63pub const REPLY_BLOCK_STATUS: u16 = 5;
64pub const REPLY_ERROR: u16 = 0x8001;
65pub const REPLY_ERROR_OFFSET: u16 = 0x8002;
66
67pub const EPERM: u32 = 1;
68pub const EIO: u32 = 5;
69pub const ENOMEM: u32 = 12;
70pub const EINVAL: u32 = 22;
71pub const ENOSPC: u32 = 28;
72pub const ENOTSUP: u32 = 95;
73pub const ESHUTDOWN: u32 = 108;
74
75pub const BASE_ALLOCATION: &str = "base:allocation";
76pub const STATE_HOLE: u32 = 1;
77pub const STATE_ZERO: u32 = 2;
78
79/// One request or option payload at most; a read or block status at most.
80pub const MAX_OPTION: usize = 64 * 1024;
81pub const MAX_REQUEST: u32 = 32 * 1024 * 1024;
82/// Bytes of explicit content one export may carry, and extents.
83pub const MAX_CONTENT: usize = 16 * 1024 * 1024;
84pub const MAX_EXTENTS: usize = 4096;
85
86pub fn errno(name: &str) -> Option<u32> {
87    Some(match name {
88        "EPERM" => EPERM,
89        "EIO" => EIO,
90        "ENOMEM" => ENOMEM,
91        "EINVAL" => EINVAL,
92        "ENOSPC" => ENOSPC,
93        _ => return None,
94    })
95}
96
97pub fn errno_name(n: u32) -> String {
98    match n {
99        0 => "OK".into(),
100        EPERM => "EPERM".into(),
101        EIO => "EIO".into(),
102        ENOMEM => "ENOMEM".into(),
103        EINVAL => "EINVAL".into(),
104        ENOSPC => "ENOSPC".into(),
105        75 => "EOVERFLOW".into(),
106        ENOTSUP => "ENOTSUP".into(),
107        ESHUTDOWN => "ESHUTDOWN".into(),
108        n => format!("errno {n}"),
109    }
110}
111
112#[derive(Clone, Debug, PartialEq)]
113pub enum Content {
114    Bytes(Vec<u8>),
115    Fill(u8),
116}
117
118#[derive(Clone, Debug, PartialEq)]
119pub struct Extent {
120    pub offset: u64,
121    pub length: u64,
122    pub content: Content,
123}
124
125#[derive(Clone, Debug, PartialEq)]
126pub struct Export {
127    pub size: u64,
128    pub description: Option<String>,
129    pub extents: Vec<Extent>,
130    pub errors: Vec<(u64, u64, u32)>,
131    pub block_min: u32,
132    pub block_preferred: u32,
133    pub block_max: u32,
134}
135
136/// A piece of a read: data, or a run of zeroes.
137#[derive(Clone, Debug, PartialEq)]
138pub enum Piece {
139    Data(u64, Vec<u8>),
140    Hole(u64, u32),
141}
142
143fn u64_of(v: &Json, key: &str) -> Result<u64> {
144    v.get(key)
145        .and_then(Json::as_u64)
146        .with_context(|| format!("{key} is a non-negative number"))
147}
148
149impl Export {
150    /// The export an `nbd_export` action describes.
151    pub fn from_action(v: &Json) -> Result<Self> {
152        let size = u64_of(v, "size")?;
153        ensure!(size <= 1 << 40, "size is at most 1 TiB");
154        let mut extents = Vec::new();
155        let mut content = 0usize;
156        let list = |key: &str| -> Result<Vec<Json>> {
157            match v.get(key) {
158                None | Some(Json::Null) => Ok(vec![]),
159                Some(Json::Array(a)) => {
160                    ensure!(
161                        a.len() <= MAX_EXTENTS,
162                        "{key} has more than {MAX_EXTENTS} entries"
163                    );
164                    Ok(a.clone())
165                }
166                _ => bail!("{key} is an array"),
167            }
168        };
169        for e in list("extents")? {
170            let offset = u64_of(&e, "offset")?;
171            let content_of = if let Some(t) = e.get("text").and_then(Json::as_str) {
172                Content::Bytes(t.as_bytes().to_vec())
173            } else if let Some(h) = e.get("hex").and_then(Json::as_str) {
174                Content::Bytes(hex::decode(h).context("hex is not hex")?)
175            } else if let Some(f) = e.get("fill") {
176                Content::Fill(
177                    f.as_u64()
178                        .and_then(|f| u8::try_from(f).ok())
179                        .context("fill is a byte value 0-255")?,
180                )
181            } else {
182                bail!("each extent has text, hex or fill");
183            };
184            let length = match &content_of {
185                Content::Bytes(b) => {
186                    content += b.len();
187                    b.len() as u64
188                }
189                Content::Fill(_) => u64_of(&e, "length")?,
190            };
191            ensure!(
192                content <= MAX_CONTENT,
193                "extents carry more than {MAX_CONTENT} bytes"
194            );
195            ensure!(
196                offset.checked_add(length).is_some_and(|end| end <= size),
197                "an extent at {offset} runs past the export size {size}"
198            );
199            if length > 0 {
200                extents.push(Extent {
201                    offset,
202                    length,
203                    content: content_of,
204                });
205            }
206        }
207        let mut errors = Vec::new();
208        for e in list("errors")? {
209            let (offset, length) = (u64_of(&e, "offset")?, u64_of(&e, "length")?);
210            ensure!(
211                offset.checked_add(length).is_some_and(|end| end <= size),
212                "an error region runs past the export size"
213            );
214            let name = e.get("error").and_then(Json::as_str).unwrap_or("EIO");
215            errors.push((
216                offset,
217                length,
218                errno(name).context("error is EIO, EPERM, ENOMEM, EINVAL or ENOSPC")?,
219            ));
220        }
221        let bs = v.get("block_size").filter(|b| !b.is_null());
222        let get = |k: &str, d: u32| -> Result<u32> {
223            match bs.and_then(|b| b.get(k)).filter(|x| !x.is_null()) {
224                None => Ok(d),
225                Some(x) => x
226                    .as_u64()
227                    .and_then(|x| u32::try_from(x).ok())
228                    .with_context(|| format!("block_size.{k} is a number")),
229            }
230        };
231        let (block_min, block_preferred, block_max) = (
232            get("minimum", 1)?,
233            get("preferred", 4096)?,
234            get("maximum", MAX_REQUEST)?,
235        );
236        ensure!(
237            block_min.is_power_of_two() && block_min <= 65536,
238            "block_size.minimum is a power of two up to 64 KiB"
239        );
240        ensure!(
241            block_preferred.is_power_of_two()
242                && block_preferred >= block_min
243                && block_preferred <= MAX_REQUEST,
244            "block_size.preferred is a power of two between the minimum and 32 MiB"
245        );
246        ensure!(block_max >= block_preferred && block_max <= MAX_REQUEST && block_max % block_min == 0, "block_size.maximum is a multiple of the minimum, at least the preferred size, at most 32 MiB");
247        let description = v
248            .get("description")
249            .and_then(Json::as_str)
250            .map(str::to_owned);
251        ensure!(
252            description.as_ref().is_none_or(|d| d.len() <= 4096),
253            "description is at most 4096 bytes"
254        );
255        Ok(Self {
256            size,
257            description,
258            extents,
259            errors,
260            block_min,
261            block_preferred,
262            block_max,
263        })
264    }
265
266    /// The first errored byte in a range, with its errno.
267    pub fn error_in(&self, offset: u64, length: u64) -> Option<(u64, u32)> {
268        self.errors
269            .iter()
270            .filter(|(o, l, _)| *o < offset + length && offset < o + l)
271            .map(|(o, _, e)| ((*o).max(offset), *e))
272            .min_by_key(|(o, _)| *o)
273    }
274
275    /// The bytes of a range, zeroes where no extent covers it.
276    pub fn read(&self, offset: u64, length: u32) -> Vec<u8> {
277        let mut out = vec![0u8; length as usize];
278        let end = offset + length as u64;
279        for e in &self.extents {
280            let (s, t) = (e.offset.max(offset), (e.offset + e.length).min(end));
281            if s >= t {
282                continue;
283            }
284            let dst = &mut out[(s - offset) as usize..(t - offset) as usize];
285            match &e.content {
286                Content::Bytes(b) => {
287                    dst.copy_from_slice(&b[(s - e.offset) as usize..(t - e.offset) as usize])
288                }
289                Content::Fill(f) => dst.fill(*f),
290            }
291        }
292        out
293    }
294
295    /// Covered (data) and uncovered (zero) runs of a range, in order.
296    pub fn runs(&self, offset: u64, length: u64) -> Vec<(u64, u64, bool)> {
297        let end = offset + length;
298        let mut cuts = vec![offset, end];
299        for e in &self.extents {
300            for p in [e.offset, e.offset + e.length] {
301                if p > offset && p < end {
302                    cuts.push(p);
303                }
304            }
305        }
306        cuts.sort_unstable();
307        cuts.dedup();
308        let mut runs: Vec<(u64, u64, bool)> = Vec::new();
309        for w in cuts.windows(2) {
310            let data = self
311                .extents
312                .iter()
313                .any(|e| e.offset < w[1] && w[0] < e.offset + e.length);
314            match runs.last_mut() {
315                Some(last) if last.2 == data => last.1 += w[1] - w[0],
316                _ => runs.push((w[0], w[1] - w[0], data)),
317            }
318        }
319        runs
320    }
321
322    /// A read as structured-reply pieces: data where extents are, holes elsewhere.
323    pub fn pieces(&self, offset: u64, length: u32) -> Vec<Piece> {
324        self.runs(offset, length as u64)
325            .into_iter()
326            .map(|(o, l, data)| {
327                if data {
328                    Piece::Data(o, self.read(o, l as u32))
329                } else {
330                    Piece::Hole(o, l as u32)
331                }
332            })
333            .collect()
334    }
335}