Skip to main content

netget/server/dicom/
dataset.rs

1//! DICOM datasets (PS3.5): Implicit and Explicit VR Little Endian element encoding, sequences
2//! with defined and undefined lengths, and conversion to and from the DICOM JSON model
3//! (PS3.18 F) the handler works with. Bulk binary values are never handed to the handler as
4//! bytes: they appear as `{"vr": "OB", "length": n}`; values a client sends may be `hex`.
5use anyhow::{bail, ensure, Context, Result};
6use serde_json::{json, Map, Value};
7
8pub const IMPLICIT_LE: &str = "1.2.840.10008.1.2";
9pub const EXPLICIT_LE: &str = "1.2.840.10008.1.2.1";
10pub const MAX_DATASET: usize = 16 * 1024 * 1024;
11const MAX_DEPTH: usize = 8;
12const MAX_ELEMENTS: usize = 50_000;
13const MAX_HEX: usize = 64 * 1024;
14
15/// VRs whose values are text.
16pub fn is_string(vr: &str) -> bool {
17    matches!(
18        vr,
19        "AE" | "AS"
20            | "CS"
21            | "DA"
22            | "DS"
23            | "DT"
24            | "IS"
25            | "LO"
26            | "LT"
27            | "PN"
28            | "SH"
29            | "ST"
30            | "TM"
31            | "UI"
32            | "UT"
33            | "UC"
34            | "UR"
35    )
36}
37
38fn is_binary(vr: &str) -> bool {
39    matches!(vr, "OB" | "OW" | "OF" | "OD" | "OL" | "OV" | "UN")
40}
41
42/// Explicit VR elements with a 4-byte length (PS3.5 7.1.2).
43fn long_length(vr: &str) -> bool {
44    matches!(
45        vr,
46        "OB" | "OW" | "OF" | "OD" | "OL" | "OV" | "SQ" | "UN" | "UC" | "UR" | "UT" | "SV" | "UV"
47    )
48}
49
50/// The VR of tags the services here use, for Implicit VR datasets; anything else decodes as UN.
51pub fn dictionary_vr(tag: u32) -> &'static str {
52    if tag & 0xFFFF == 0 {
53        return "UL";
54    }
55    match tag {
56        0x0000_0002 | 0x0000_0003 | 0x0000_1000 | 0x0000_1001 => "UI",
57        0x0000_0100
58        | 0x0000_0110
59        | 0x0000_0120
60        | 0x0000_0700
61        | 0x0000_0800
62        | 0x0000_0900
63        | 0x0000_1020..=0x0000_1023
64        | 0x0000_1031 => "US",
65        0x0000_0600 | 0x0000_1030 => "AE",
66        0x0000_0901 => "AT",
67        0x0000_0902 => "LO",
68        0x0008_0005 | 0x0008_0052 | 0x0008_0060 | 0x0008_0061 | 0x0010_0040 | 0x0018_0015
69        | 0x0028_0004 | 0x0008_0008 => "CS",
70        0x0008_0016 | 0x0008_0018 | 0x0008_1150 | 0x0008_1155 | 0x0020_000D | 0x0020_000E
71        | 0x0008_0062 | 0x0020_0052 => "UI",
72        0x0008_0020 | 0x0008_0021 | 0x0008_0022 | 0x0008_0023 | 0x0010_0030 => "DA",
73        0x0008_0030 | 0x0008_0031 | 0x0008_0032 | 0x0008_0033 => "TM",
74        0x0008_0050 | 0x0020_0010 => "SH",
75        0x0008_0054 => "AE",
76        0x0008_0070 | 0x0008_0080 | 0x0008_1030 | 0x0008_103E | 0x0010_0020 | 0x0008_1090 => "LO",
77        0x0008_0090 | 0x0010_0010 | 0x0008_1050 => "PN",
78        0x0008_1110 | 0x0008_1115 | 0x0008_1140 | 0x0008_1199 => "SQ",
79        0x0010_1010 => "AS",
80        0x0018_0050 | 0x0028_0030 => "DS",
81        0x0020_0011 | 0x0020_0013 | 0x0020_1200 | 0x0020_1202 | 0x0020_1204 | 0x0020_1206
82        | 0x0020_1208 | 0x0020_1209 => "IS",
83        0x0028_0002 | 0x0028_0010 | 0x0028_0011 | 0x0028_0100 | 0x0028_0101 | 0x0028_0102
84        | 0x0028_0103 => "US",
85        0x7FE0_0010 => "OW",
86        _ => "UN",
87    }
88}
89
90pub fn tag_key(tag: u32) -> String {
91    format!("{tag:08X}")
92}
93
94pub fn parse_key(k: &str) -> Result<u32> {
95    ensure!(k.len() == 8, "DICOM JSON keys are 8 hex digits");
96    Ok(u32::from_str_radix(k, 16)?)
97}
98
99struct Reader<'a> {
100    b: &'a [u8],
101    pos: usize,
102    explicit: bool,
103    elements: usize,
104}
105
106impl Reader<'_> {
107    fn u16(&mut self) -> Result<u16> {
108        let v = self
109            .b
110            .get(self.pos..self.pos + 2)
111            .context("truncated dataset")?;
112        self.pos += 2;
113        Ok(u16::from_le_bytes([v[0], v[1]]))
114    }
115    fn u32(&mut self) -> Result<u32> {
116        let v = self
117            .b
118            .get(self.pos..self.pos + 4)
119            .context("truncated dataset")?;
120        self.pos += 4;
121        Ok(u32::from_le_bytes([v[0], v[1], v[2], v[3]]))
122    }
123    fn take(&mut self, n: usize) -> Result<&[u8]> {
124        let v = self
125            .b
126            .get(self.pos..self.pos + n)
127            .context("element longer than the dataset")?;
128        self.pos += n;
129        Ok(v)
130    }
131    fn tag(&mut self) -> Result<u32> {
132        let g = self.u16()? as u32;
133        let e = self.u16()? as u32;
134        Ok(g << 16 | e)
135    }
136
137    /// Elements until `end` (exclusive) or an item delimiter; the flag says which ended it.
138    fn dataset(&mut self, end: usize, depth: usize) -> Result<(Map<String, Value>, bool)> {
139        ensure!(depth <= MAX_DEPTH, "sequences nest too deeply");
140        let mut out = Map::new();
141        while self.pos < end {
142            let start = self.pos;
143            let tag = self.tag()?;
144            if tag == 0xFFFE_E00D {
145                self.u32()?;
146                return Ok((out, true));
147            }
148            self.elements += 1;
149            ensure!(self.elements <= MAX_ELEMENTS, "too many elements");
150            let (vr, len) = if self.explicit && tag >> 16 != 0x0000 {
151                let vr_b = self.take(2)?;
152                let vr = std::str::from_utf8(vr_b)
153                    .ok()
154                    .filter(|v| v.bytes().all(|b| b.is_ascii_uppercase()))
155                    .context("invalid VR")?
156                    .to_owned();
157                let len = if long_length(&vr) {
158                    self.u16()?;
159                    self.u32()?
160                } else {
161                    self.u16()? as u32
162                };
163                (vr, len)
164            } else {
165                (dictionary_vr(tag).to_owned(), self.u32()?)
166            };
167            let value = self.value(tag, &vr, len, depth)?;
168            let _ = start;
169            out.insert(tag_key(tag), value);
170        }
171        Ok((out, false))
172    }
173
174    fn value(&mut self, tag: u32, vr: &str, len: u32, depth: usize) -> Result<Value> {
175        let undefined = len == 0xFFFF_FFFF;
176        if vr == "SQ" || (undefined && vr == "UN") {
177            let mut items = Vec::new();
178            let end = if undefined {
179                self.b.len()
180            } else {
181                self.pos
182                    .checked_add(len as usize)
183                    .filter(|e| *e <= self.b.len())
184                    .context("sequence longer than the dataset")?
185            };
186            // An undefined length ends only at its Sequence Delimitation Item (PS3.5 7.5.2).
187            let mut delimited = !undefined;
188            while self.pos < end {
189                let t = self.tag()?;
190                let ilen = self.u32()?;
191                match t {
192                    0xFFFE_E0DD if undefined => {
193                        delimited = true;
194                        break;
195                    }
196                    0xFFFE_E000 => {
197                        let iend = if ilen == 0xFFFF_FFFF {
198                            self.b.len()
199                        } else {
200                            self.pos
201                                .checked_add(ilen as usize)
202                                .filter(|e| *e <= end)
203                                .context("item longer than its sequence")?
204                        };
205                        let (item, closed) = self.dataset(iend, depth + 1)?;
206                        ensure!(
207                            closed || ilen != 0xFFFF_FFFF,
208                            "item without its delimiter in {}",
209                            tag_key(tag)
210                        );
211                        items.push(Value::Object(item));
212                    }
213                    _ => bail!("expected an item in sequence {}", tag_key(tag)),
214                }
215            }
216            ensure!(delimited, "sequence {} without its delimiter", tag_key(tag));
217            return Ok(json!({"vr": "SQ", "Value": items}));
218        }
219        if undefined {
220            // Encapsulated pixel data: skip fragments up to the sequence delimiter.
221            let mut total = 0usize;
222            loop {
223                let t = self.tag()?;
224                let flen = self.u32()? as usize;
225                if t == 0xFFFE_E0DD {
226                    break;
227                }
228                ensure!(t == 0xFFFE_E000, "malformed encapsulated value");
229                self.take(flen)?;
230                total += flen;
231            }
232            return Ok(json!({"vr": vr, "length": total, "encapsulated": true}));
233        }
234        let raw = self.take(len as usize)?;
235        Ok(decode_value(vr, raw))
236    }
237}
238
239fn decode_value(vr: &str, raw: &[u8]) -> Value {
240    if is_binary(vr) {
241        return json!({"vr": vr, "length": raw.len()});
242    }
243    if is_string(vr) {
244        let text = String::from_utf8(raw.to_vec())
245            .unwrap_or_else(|_| raw.iter().map(|&b| b as char).collect());
246        let text = text.trim_end_matches(['\0', ' ']);
247        let parts: Vec<&str> = if matches!(vr, "LT" | "ST" | "UT" | "UR") {
248            vec![text]
249        } else {
250            text.split('\\').collect()
251        };
252        if text.is_empty() {
253            return json!({"vr": vr});
254        }
255        let values: Vec<Value> = parts
256            .iter()
257            .map(|p| {
258                let p = p.trim();
259                match vr {
260                    "PN" => json!({"Alphabetic": p}),
261                    "IS" => p
262                        .parse::<i64>()
263                        .map(|n| json!(n))
264                        .unwrap_or_else(|_| json!(p)),
265                    "DS" => p
266                        .parse::<f64>()
267                        .map(|n| json!(n))
268                        .unwrap_or_else(|_| json!(p)),
269                    _ => json!(p),
270                }
271            })
272            .collect();
273        return json!({"vr": vr, "Value": values});
274    }
275    let num = |size: usize, f: &dyn Fn(&[u8]) -> Value| -> Vec<Value> {
276        raw.chunks_exact(size).map(f).collect()
277    };
278    let values = match vr {
279        "US" => num(2, &|c| json!(u16::from_le_bytes([c[0], c[1]]))),
280        "SS" => num(2, &|c| json!(i16::from_le_bytes([c[0], c[1]]))),
281        "UL" => num(4, &|c| json!(u32::from_le_bytes([c[0], c[1], c[2], c[3]]))),
282        "SL" => num(4, &|c| json!(i32::from_le_bytes([c[0], c[1], c[2], c[3]]))),
283        "FL" => num(4, &|c| json!(f32::from_le_bytes([c[0], c[1], c[2], c[3]]))),
284        "FD" => num(8, &|c| {
285            json!(f64::from_le_bytes(c.try_into().expect("8 bytes")))
286        }),
287        "AT" => num(4, &|c| {
288            json!(format!(
289                "{:04X}{:04X}",
290                u16::from_le_bytes([c[0], c[1]]),
291                u16::from_le_bytes([c[2], c[3]])
292            ))
293        }),
294        _ => return json!({"vr": vr, "length": raw.len()}),
295    };
296    if values.is_empty() {
297        json!({"vr": vr})
298    } else {
299        json!({"vr": vr, "Value": values})
300    }
301}
302
303/// Decode a dataset in the given transfer syntax into DICOM JSON.
304pub fn decode(bytes: &[u8], transfer_syntax: &str) -> Result<Map<String, Value>> {
305    ensure!(bytes.len() <= MAX_DATASET, "dataset over 16 MiB");
306    let explicit = match transfer_syntax {
307        IMPLICIT_LE => false,
308        EXPLICIT_LE => true,
309        other => bail!("unsupported transfer syntax {other}"),
310    };
311    let mut r = Reader {
312        b: bytes,
313        pos: 0,
314        explicit,
315        elements: 0,
316    };
317    let (m, stray) = r.dataset(bytes.len(), 0)?;
318    ensure!(!stray, "item delimiter outside a sequence");
319    Ok(m)
320}
321
322fn pad(mut v: Vec<u8>, vr: &str) -> Vec<u8> {
323    if v.len() % 2 == 1 {
324        v.push(if vr == "UI" || vr == "OB" { 0 } else { b' ' });
325    }
326    v
327}
328
329fn encode_value(vr: &str, v: &Value, explicit: bool, depth: usize) -> Result<Vec<u8>> {
330    ensure!(depth <= MAX_DEPTH, "sequences nest too deeply");
331    let values = v
332        .get("Value")
333        .and_then(Value::as_array)
334        .cloned()
335        .unwrap_or_default();
336    if vr == "SQ" {
337        let mut out = Vec::new();
338        for item in &values {
339            let body = encode_map(
340                item.as_object().context("a sequence item is an object")?,
341                explicit,
342                depth + 1,
343            )?;
344            out.extend_from_slice(&0xFFFEu16.to_le_bytes());
345            out.extend_from_slice(&0xE000u16.to_le_bytes());
346            out.extend_from_slice(&(body.len() as u32).to_le_bytes());
347            out.extend(body);
348        }
349        return Ok(out);
350    }
351    if is_binary(vr) {
352        let hex_text = v.get("hex").and_then(Value::as_str).unwrap_or("");
353        ensure!(
354            hex_text.len() <= MAX_HEX * 2,
355            "hex values are limited to 64 KiB"
356        );
357        return Ok(pad(hex::decode(hex_text).context("hex is not valid")?, vr));
358    }
359    if is_string(vr) {
360        let parts: Vec<String> = values
361            .iter()
362            .map(|x| match (vr, x) {
363                ("PN", Value::Object(o)) => o
364                    .get("Alphabetic")
365                    .and_then(Value::as_str)
366                    .unwrap_or("")
367                    .to_owned(),
368                (_, Value::String(s)) => s.clone(),
369                (_, Value::Number(n)) => n.to_string(),
370                _ => String::new(),
371            })
372            .collect();
373        let text = parts.join("\\");
374        ensure!(
375            !text
376                .chars()
377                .any(|c| c.is_control() && !matches!(vr, "LT" | "ST" | "UT")),
378            "string values carry no control characters"
379        );
380        return Ok(pad(text.into_bytes(), vr));
381    }
382    let mut out = Vec::new();
383    for x in &values {
384        match vr {
385            "US" => out.extend_from_slice(&(x.as_u64().context("US value")? as u16).to_le_bytes()),
386            "SS" => out.extend_from_slice(&(x.as_i64().context("SS value")? as i16).to_le_bytes()),
387            "UL" => out.extend_from_slice(&(x.as_u64().context("UL value")? as u32).to_le_bytes()),
388            "SL" => out.extend_from_slice(&(x.as_i64().context("SL value")? as i32).to_le_bytes()),
389            "FL" => out.extend_from_slice(&(x.as_f64().context("FL value")? as f32).to_le_bytes()),
390            "FD" => out.extend_from_slice(&x.as_f64().context("FD value")?.to_le_bytes()),
391            "AT" => {
392                let t = parse_key(x.as_str().context("AT value")?)?;
393                out.extend_from_slice(&((t >> 16) as u16).to_le_bytes());
394                out.extend_from_slice(&((t & 0xFFFF) as u16).to_le_bytes());
395            }
396            other => bail!("unsupported VR {other}"),
397        }
398    }
399    Ok(out)
400}
401
402fn encode_map(m: &Map<String, Value>, explicit: bool, depth: usize) -> Result<Vec<u8>> {
403    let mut tags: Vec<(u32, &Value)> = m
404        .iter()
405        .map(|(k, v)| Ok((parse_key(k)?, v)))
406        .collect::<Result<_>>()?;
407    tags.sort_by_key(|(t, _)| *t);
408    let mut out = Vec::new();
409    for (tag, v) in tags {
410        let vr = v
411            .get("vr")
412            .and_then(Value::as_str)
413            .unwrap_or_else(|| dictionary_vr(tag));
414        ensure!(
415            vr.len() == 2 && vr.bytes().all(|b| b.is_ascii_uppercase()),
416            "invalid VR in {}",
417            tag_key(tag)
418        );
419        let body = encode_value(vr, v, explicit, depth)?;
420        out.extend_from_slice(&((tag >> 16) as u16).to_le_bytes());
421        out.extend_from_slice(&((tag & 0xFFFF) as u16).to_le_bytes());
422        if explicit && tag >> 16 != 0 {
423            out.extend_from_slice(vr.as_bytes());
424            if long_length(vr) {
425                out.extend_from_slice(&[0, 0]);
426                out.extend_from_slice(&(body.len() as u32).to_le_bytes());
427            } else {
428                ensure!(
429                    body.len() <= 0xFFFF,
430                    "value of {} too long for its VR",
431                    tag_key(tag)
432                );
433                out.extend_from_slice(&(body.len() as u16).to_le_bytes());
434            }
435        } else {
436            out.extend_from_slice(&(body.len() as u32).to_le_bytes());
437        }
438        out.extend(body);
439    }
440    Ok(out)
441}
442
443/// Encode a DICOM JSON dataset in the given transfer syntax.
444pub fn encode(m: &Map<String, Value>, transfer_syntax: &str) -> Result<Vec<u8>> {
445    let explicit = match transfer_syntax {
446        IMPLICIT_LE => false,
447        EXPLICIT_LE => true,
448        other => bail!("unsupported transfer syntax {other}"),
449    };
450    let out = encode_map(m, explicit, 0)?;
451    ensure!(out.len() <= MAX_DATASET, "dataset over 16 MiB");
452    Ok(out)
453}
454
455/// The first value of an element as text (PN alphabetic, numbers formatted).
456pub fn text(m: &Map<String, Value>, tag: u32) -> Option<String> {
457    let v = m.get(&tag_key(tag))?.get("Value")?.as_array()?.first()?;
458    Some(match v {
459        Value::String(s) => s.clone(),
460        Value::Object(o) => o
461            .get("Alphabetic")
462            .and_then(Value::as_str)
463            .unwrap_or("")
464            .to_owned(),
465        other => other.to_string(),
466    })
467}
468
469fn wildcard(pattern: &str, value: &str) -> bool {
470    let (p, v): (Vec<char>, Vec<char>) = (pattern.chars().collect(), value.chars().collect());
471    let (mut i, mut j, mut star, mut mark) = (0, 0, None, 0);
472    while j < v.len() {
473        if i < p.len() && (p[i] == '?' || p[i] == v[j]) {
474            i += 1;
475            j += 1;
476        } else if i < p.len() && p[i] == '*' {
477            star = Some(i);
478            mark = j;
479            i += 1;
480        } else if let Some(s) = star {
481            i = s + 1;
482            mark += 1;
483            j = mark;
484        } else {
485            return false;
486        }
487    }
488    p[i..].iter().all(|c| *c == '*')
489}
490
491/// PS3.4 C.2.2.2 matching of one key: universal (empty), list of UIDs, range (DA/TM/DT),
492/// wildcard (* and ?), or single value. Sequence keys match universally.
493fn key_matches(vr: &str, key: &Value, record: Option<&Value>) -> bool {
494    let wanted: Vec<String> = key
495        .get("Value")
496        .and_then(Value::as_array)
497        .map(|a| {
498            a.iter()
499                .map(|x| match x {
500                    Value::String(s) => s.clone(),
501                    Value::Object(o) => o
502                        .get("Alphabetic")
503                        .and_then(Value::as_str)
504                        .unwrap_or("")
505                        .to_owned(),
506                    other => other.to_string(),
507                })
508                .collect()
509        })
510        .unwrap_or_default();
511    if vr == "SQ" || wanted.is_empty() || wanted.iter().all(|w| w.is_empty() || w == "*") {
512        return true;
513    }
514    let have: Vec<String> = record
515        .and_then(|r| r.get("Value"))
516        .and_then(Value::as_array)
517        .map(|a| {
518            a.iter()
519                .map(|x| match x {
520                    Value::String(s) => s.clone(),
521                    Value::Object(o) => o
522                        .get("Alphabetic")
523                        .and_then(Value::as_str)
524                        .unwrap_or("")
525                        .to_owned(),
526                    other => other.to_string(),
527                })
528                .collect()
529        })
530        .unwrap_or_default();
531    if have.is_empty() {
532        return false;
533    }
534    if vr == "UI" {
535        return have.iter().any(|h| wanted.contains(h));
536    }
537    let w = &wanted[0];
538    if matches!(vr, "DA" | "TM" | "DT") && w.contains('-') {
539        let (lo, hi) = w.split_once('-').unwrap_or((w, ""));
540        return have
541            .iter()
542            .any(|h| (lo.is_empty() || h.as_str() >= lo) && (hi.is_empty() || h.as_str() <= hi));
543    }
544    if w.contains(['*', '?']) {
545        return have.iter().any(|h| wildcard(w, h));
546    }
547    have.iter().any(|h| h == w)
548}
549
550/// Whether `record` matches every key of the C-FIND identifier.
551pub fn matches(identifier: &Map<String, Value>, record: &Map<String, Value>) -> bool {
552    identifier.iter().all(|(k, key)| {
553        if k == "00080052" || k == "00080005" {
554            return true;
555        }
556        let vr = key.get("vr").and_then(Value::as_str).unwrap_or("UN");
557        key_matches(vr, key, record.get(k))
558    })
559}
560
561/// The response identifier: every requested key with the record's value (empty when the
562/// record lacks it), plus the query level and character set.
563pub fn project(identifier: &Map<String, Value>, record: &Map<String, Value>) -> Map<String, Value> {
564    let mut out = Map::new();
565    for (k, key) in identifier {
566        let value = match record.get(k) {
567            Some(v) if k != "00080052" => v.clone(),
568            _ => key.clone(),
569        };
570        out.insert(k.clone(), value);
571    }
572    out
573}