1use 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
15pub 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
42fn 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
50pub 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 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 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 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
303pub 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
443pub 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
455pub 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
491fn 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
550pub 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
561pub 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}