1use 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
79pub const MAX_OPTION: usize = 64 * 1024;
81pub const MAX_REQUEST: u32 = 32 * 1024 * 1024;
82pub 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#[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 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 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 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 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 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}