1use byteordered::byteorder::{ByteOrder, LittleEndian};
3use dicom_core::dicom_value;
4use dicom_core::header::{DataElement, EmptyObject, HasLength, Header};
5use dicom_core::ops::{ApplyOp, AttributeAction, AttributeOp, AttributeSelectorStep};
6use dicom_core::value::{PrimitiveValue, Value, ValueType};
7use dicom_core::{Length, Tag, VR};
8use dicom_dictionary_std::tags;
9use dicom_encoding::decode::{self, DecodeFrom};
10use dicom_encoding::encode::explicit_le::ExplicitVRLittleEndianEncoder;
11use dicom_encoding::encode::EncoderFor;
12use dicom_encoding::text::{self, TextCodec};
13use dicom_encoding::TransferSyntax;
14use dicom_parser::dataset::{DataSetWriter, IntoTokens};
15use snafu::{ensure, Backtrace, OptionExt, ResultExt, Snafu};
16use std::io::{Read, Write};
17
18use crate::ops::{
19 ApplyError, ApplyResult, IllegalExtendSnafu, IncompatibleTypesSnafu, MandatorySnafu,
20 UnsupportedActionSnafu, UnsupportedAttributeSnafu,
21};
22use crate::{IMPLEMENTATION_CLASS_UID, IMPLEMENTATION_VERSION_NAME};
23
24const DICM_MAGIC_CODE: [u8; 4] = [b'D', b'I', b'C', b'M'];
25
26#[derive(Debug, Snafu)]
27#[non_exhaustive]
28pub enum Error {
29 #[snafu(display("Could not start reading DICOM data"))]
32 ReadMagicCode {
33 backtrace: Backtrace,
34 source: std::io::Error,
35 },
36
37 #[snafu(display("Could not read data value"))]
40 ReadValueData {
41 backtrace: Backtrace,
42 source: std::io::Error,
43 },
44
45 #[snafu(display("Could not allocate memory"))]
48 AllocationSize {
49 backtrace: Backtrace,
50 source: std::collections::TryReserveError,
51 },
52
53 #[snafu(display("Could not decode text in {}", name))]
56 DecodeText {
57 name: std::borrow::Cow<'static, str>,
58 #[snafu(backtrace)]
59 source: dicom_encoding::text::DecodeTextError,
60 },
61
62 #[snafu(display("Invalid DICOM file (magic code check failed)"))]
64 NotDicom { backtrace: Backtrace },
65
66 #[snafu(display("Could not decode data element"))]
69 DecodeElement {
70 #[snafu(backtrace)]
71 source: dicom_encoding::decode::Error,
72 },
73
74 #[snafu(display("Unexpected data element tagged {}", tag))]
78 UnexpectedTag { tag: Tag, backtrace: Backtrace },
79
80 #[snafu(display("Missing data element `{}`", alias))]
82 MissingElement {
83 alias: &'static str,
84 backtrace: Backtrace,
85 },
86
87 #[snafu(display("Unexpected length {} for data element tagged {}", length, tag))]
90 UnexpectedDataValueLength {
91 tag: Tag,
92 length: Length,
93 backtrace: Backtrace,
94 },
95
96 #[snafu(display("Undefined value length for data element tagged {}", tag))]
99 UndefinedValueLength { tag: Tag, backtrace: Backtrace },
100
101 #[snafu(display("Could not write file meta group data set"))]
103 WriteSet {
104 #[snafu(backtrace)]
105 source: dicom_parser::dataset::write::Error,
106 },
107}
108
109type Result<T> = std::result::Result<T, Error>;
110
111#[derive(Debug, Clone, PartialEq)]
125pub struct FileMetaTable {
126 pub information_group_length: u32,
128 pub information_version: [u8; 2],
130 pub media_storage_sop_class_uid: String,
132 pub media_storage_sop_instance_uid: String,
134 pub transfer_syntax: String,
136 pub implementation_class_uid: String,
138
139 pub implementation_version_name: Option<String>,
141 pub source_application_entity_title: Option<String>,
143 pub sending_application_entity_title: Option<String>,
145 pub receiving_application_entity_title: Option<String>,
147 pub private_information_creator_uid: Option<String>,
149 pub private_information: Option<Vec<u8>>,
151 }
166
167fn read_str_body<'s, S, T>(source: &'s mut S, text: &T, len: u32) -> Result<String>
169where
170 S: Read + 's,
171 T: TextCodec,
172{
173 let mut v = Vec::new();
174 v.try_reserve_exact(len as usize)
175 .context(AllocationSizeSnafu)?;
176 v.resize(len as usize, 0);
177 source.read_exact(&mut v).context(ReadValueDataSnafu)?;
178
179 text.decode(&v)
180 .context(DecodeTextSnafu { name: text.name() })
181}
182
183impl FileMetaTable {
184 pub fn from_reader<R: Read>(file: R) -> Result<Self> {
190 FileMetaTable::read_from(file)
191 }
192
193 pub fn transfer_syntax(&self) -> &str {
196 self.transfer_syntax
197 .trim_end_matches(|c: char| c.is_whitespace() || c == '\0')
198 }
199
200 pub fn media_storage_sop_instance_uid(&self) -> &str {
203 self.media_storage_sop_instance_uid
204 .trim_end_matches(|c: char| c.is_whitespace() || c == '\0')
205 }
206
207 pub fn media_storage_sop_class_uid(&self) -> &str {
210 self.media_storage_sop_class_uid
211 .trim_end_matches(|c: char| c.is_whitespace() || c == '\0')
212 }
213
214 pub fn implementation_class_uid(&self) -> &str {
217 self.implementation_class_uid
218 .trim_end_matches(|c: char| c.is_whitespace() || c == '\0')
219 }
220
221 pub fn private_information_creator_uid(&self) -> Option<&str> {
224 self.private_information_creator_uid
225 .as_ref()
226 .map(|s| s.trim_end_matches(|c: char| c.is_whitespace() || c == '\0'))
227 }
228
229 pub fn set_transfer_syntax<D, R, W>(&mut self, ts: &TransferSyntax<D, R, W>) {
236 self.transfer_syntax = ts
237 .uid()
238 .trim_end_matches(|c: char| c.is_whitespace() || c == '\0')
239 .to_string();
240 self.update_information_group_length();
241 }
242
243 pub fn update_information_group_length(&mut self) {
247 self.information_group_length = self.calculate_information_group_length();
248 }
249
250 fn apply(&mut self, op: AttributeOp) -> ApplyResult {
255 let AttributeSelectorStep::Tag(tag) = op.selector.first_step() else {
256 return UnsupportedAttributeSnafu.fail();
257 };
258
259 match *tag {
260 tags::TRANSFER_SYNTAX_UID => Self::apply_required_string(op, &mut self.transfer_syntax),
261 tags::MEDIA_STORAGE_SOP_CLASS_UID => {
262 Self::apply_required_string(op, &mut self.media_storage_sop_class_uid)
263 }
264 tags::MEDIA_STORAGE_SOP_INSTANCE_UID => {
265 Self::apply_required_string(op, &mut self.media_storage_sop_instance_uid)
266 }
267 tags::IMPLEMENTATION_CLASS_UID => {
268 Self::apply_required_string(op, &mut self.implementation_class_uid)
269 }
270 tags::IMPLEMENTATION_VERSION_NAME => {
271 Self::apply_optional_string(op, &mut self.implementation_version_name)
272 }
273 tags::SOURCE_APPLICATION_ENTITY_TITLE => {
274 Self::apply_optional_string(op, &mut self.source_application_entity_title)
275 }
276 tags::SENDING_APPLICATION_ENTITY_TITLE => {
277 Self::apply_optional_string(op, &mut self.sending_application_entity_title)
278 }
279 tags::RECEIVING_APPLICATION_ENTITY_TITLE => {
280 Self::apply_optional_string(op, &mut self.receiving_application_entity_title)
281 }
282 tags::PRIVATE_INFORMATION_CREATOR_UID => {
283 Self::apply_optional_string(op, &mut self.private_information_creator_uid)
284 }
285 _ if matches!(
286 op.action,
287 AttributeAction::Remove | AttributeAction::Empty | AttributeAction::Truncate(_)
288 ) =>
289 {
290 Ok(())
293 }
294 _ => UnsupportedAttributeSnafu.fail(),
295 }?;
296
297 self.update_information_group_length();
298
299 Ok(())
300 }
301
302 fn apply_required_string(op: AttributeOp, target_attribute: &mut String) -> ApplyResult {
303 match op.action {
304 AttributeAction::Remove | AttributeAction::Empty => MandatorySnafu.fail(),
305 AttributeAction::SetVr(_) | AttributeAction::Truncate(_) => {
306 Ok(())
308 }
309 AttributeAction::Set(value) | AttributeAction::Replace(value) => {
310 if let Ok(value) = value.string() {
312 *target_attribute = value.to_string();
313 Ok(())
314 } else {
315 IncompatibleTypesSnafu {
316 kind: ValueType::Str,
317 }
318 .fail()
319 }
320 }
321 AttributeAction::SetStr(string) | AttributeAction::ReplaceStr(string) => {
322 *target_attribute = string.to_string();
323 Ok(())
324 }
325 AttributeAction::SetIfMissing(_) | AttributeAction::SetStrIfMissing(_) => {
326 Ok(())
328 }
329 AttributeAction::PushStr(_) => IllegalExtendSnafu.fail(),
330 AttributeAction::PushI32(_)
331 | AttributeAction::PushU32(_)
332 | AttributeAction::PushI16(_)
333 | AttributeAction::PushU16(_)
334 | AttributeAction::PushF32(_)
335 | AttributeAction::PushF64(_) => IncompatibleTypesSnafu {
336 kind: ValueType::Str,
337 }
338 .fail(),
339 _ => UnsupportedActionSnafu.fail(),
340 }
341 }
342
343 fn apply_optional_string(
344 op: AttributeOp,
345 target_attribute: &mut Option<String>,
346 ) -> ApplyResult {
347 match op.action {
348 AttributeAction::Remove => {
349 target_attribute.take();
350 Ok(())
351 }
352 AttributeAction::Empty => {
353 if let Some(s) = target_attribute.as_mut() {
354 s.clear();
355 }
356 Ok(())
357 }
358 AttributeAction::SetVr(_) => {
359 Ok(())
361 }
362 AttributeAction::Set(value) => {
363 if let Ok(value) = value.string() {
365 *target_attribute = Some(value.to_string());
366 Ok(())
367 } else {
368 IncompatibleTypesSnafu {
369 kind: ValueType::Str,
370 }
371 .fail()
372 }
373 }
374 AttributeAction::SetStr(value) => {
375 *target_attribute = Some(value.to_string());
376 Ok(())
377 }
378 AttributeAction::SetIfMissing(value) => {
379 if target_attribute.is_some() {
380 return Ok(());
381 }
382
383 if let Ok(value) = value.string() {
385 *target_attribute = Some(value.to_string());
386 Ok(())
387 } else {
388 IncompatibleTypesSnafu {
389 kind: ValueType::Str,
390 }
391 .fail()
392 }
393 }
394 AttributeAction::SetStrIfMissing(value) => {
395 if target_attribute.is_none() {
396 *target_attribute = Some(value.to_string());
397 }
398 Ok(())
399 }
400 AttributeAction::Replace(value) => {
401 if target_attribute.is_none() {
402 return Ok(());
403 }
404
405 if let Ok(value) = value.string() {
407 *target_attribute = Some(value.to_string());
408 Ok(())
409 } else {
410 IncompatibleTypesSnafu {
411 kind: ValueType::Str,
412 }
413 .fail()
414 }
415 }
416 AttributeAction::ReplaceStr(value) => {
417 if target_attribute.is_some() {
418 *target_attribute = Some(value.to_string());
419 }
420 Ok(())
421 }
422 AttributeAction::PushStr(_) => IllegalExtendSnafu.fail(),
423 AttributeAction::PushI32(_)
424 | AttributeAction::PushU32(_)
425 | AttributeAction::PushI16(_)
426 | AttributeAction::PushU16(_)
427 | AttributeAction::PushF32(_)
428 | AttributeAction::PushF64(_) => IncompatibleTypesSnafu {
429 kind: ValueType::Str,
430 }
431 .fail(),
432 _ => UnsupportedActionSnafu.fail(),
433 }
434 }
435
436 fn calculate_information_group_length(&self) -> u32 {
439 14 + 8
443 + dicom_len(&self.media_storage_sop_class_uid)
444 + 8
445 + dicom_len(&self.media_storage_sop_instance_uid)
446 + 8
447 + dicom_len(&self.transfer_syntax)
448 + 8
449 + dicom_len(&self.implementation_class_uid)
450 + self
451 .implementation_version_name
452 .as_ref()
453 .map(|s| 8 + dicom_len(s))
454 .unwrap_or(0)
455 + self
456 .source_application_entity_title
457 .as_ref()
458 .map(|s| 8 + dicom_len(s))
459 .unwrap_or(0)
460 + self
461 .sending_application_entity_title
462 .as_ref()
463 .map(|s| 8 + dicom_len(s))
464 .unwrap_or(0)
465 + self
466 .receiving_application_entity_title
467 .as_ref()
468 .map(|s| 8 + dicom_len(s))
469 .unwrap_or(0)
470 + self
471 .private_information_creator_uid
472 .as_ref()
473 .map(|s| 8 + dicom_len(s))
474 .unwrap_or(0)
475 + self
476 .private_information
477 .as_ref()
478 .map(|x| 12 + ((x.len() as u32 + 1) & !1))
479 .unwrap_or(0)
480 }
481
482 fn read_from<S: Read>(mut file: S) -> Result<Self> {
485 let mut buff: [u8; 4] = [0; 4];
486 {
487 file.read_exact(&mut buff).context(ReadMagicCodeSnafu)?;
489
490 ensure!(buff == DICM_MAGIC_CODE, NotDicomSnafu);
491 }
492
493 let decoder = decode::file_header_decoder();
494 let text = text::DefaultCharacterSetCodec;
495
496 let builder = FileMetaTableBuilder::new();
497
498 let group_length: u32 = {
499 let (elem, _bytes_read) = decoder
500 .decode_header(&mut file)
501 .context(DecodeElementSnafu)?;
502 if elem.tag() != Tag(0x0002, 0x0000) {
503 return UnexpectedTagSnafu { tag: elem.tag() }.fail();
504 }
505 if elem.length() != Length(4) {
506 return UnexpectedDataValueLengthSnafu {
507 tag: elem.tag(),
508 length: elem.length(),
509 }
510 .fail();
511 }
512 let mut buff: [u8; 4] = [0; 4];
513 file.read_exact(&mut buff).context(ReadValueDataSnafu)?;
514 LittleEndian::read_u32(&buff)
515 };
516
517 let mut total_bytes_read = 0;
518 let mut builder = builder.group_length(group_length);
519
520 while total_bytes_read < group_length {
522 let (elem, header_bytes_read) = decoder
523 .decode_header(&mut file)
524 .context(DecodeElementSnafu)?;
525 let elem_len = match elem.length().get() {
526 None => {
527 return UndefinedValueLengthSnafu { tag: elem.tag() }.fail();
528 }
529 Some(len) => len,
530 };
531 builder = match elem.tag() {
532 Tag(0x0002, 0x0001) => {
533 if elem.length() != Length(2) {
535 return UnexpectedDataValueLengthSnafu {
536 tag: elem.tag(),
537 length: elem.length(),
538 }
539 .fail();
540 }
541 let mut hbuf = [0u8; 2];
542 file.read_exact(&mut hbuf[..]).context(ReadValueDataSnafu)?;
543
544 builder.information_version(hbuf)
545 }
546 Tag(0x0002, 0x0002) => {
548 builder.media_storage_sop_class_uid(read_str_body(&mut file, &text, elem_len)?)
549 }
550 Tag(0x0002, 0x0003) => builder
552 .media_storage_sop_instance_uid(read_str_body(&mut file, &text, elem_len)?),
553 Tag(0x0002, 0x0010) => {
555 builder.transfer_syntax(read_str_body(&mut file, &text, elem_len)?)
556 }
557 Tag(0x0002, 0x0012) => {
559 builder.implementation_class_uid(read_str_body(&mut file, &text, elem_len)?)
560 }
561 Tag(0x0002, 0x0013) => {
562 let mut v = Vec::new();
564 v.try_reserve_exact(elem_len as usize)
565 .context(AllocationSizeSnafu)?;
566 v.resize(elem_len as usize, 0);
567 file.read_exact(&mut v).context(ReadValueDataSnafu)?;
568
569 builder.implementation_version_name(
570 text.decode(&v)
571 .context(DecodeTextSnafu { name: text.name() })?,
572 )
573 }
574 Tag(0x0002, 0x0016) => {
575 let mut v = Vec::new();
577 v.try_reserve_exact(elem_len as usize)
578 .context(AllocationSizeSnafu)?;
579 v.resize(elem_len as usize, 0);
580 file.read_exact(&mut v).context(ReadValueDataSnafu)?;
581
582 builder.source_application_entity_title(
583 text.decode(&v)
584 .context(DecodeTextSnafu { name: text.name() })?,
585 )
586 }
587 Tag(0x0002, 0x0017) => {
588 let mut v = Vec::new();
590 v.try_reserve_exact(elem_len as usize)
591 .context(AllocationSizeSnafu)?;
592 v.resize(elem_len as usize, 0);
593 file.read_exact(&mut v).context(ReadValueDataSnafu)?;
594
595 builder.sending_application_entity_title(
596 text.decode(&v)
597 .context(DecodeTextSnafu { name: text.name() })?,
598 )
599 }
600 Tag(0x0002, 0x0018) => {
601 let mut v = Vec::new();
603 v.try_reserve_exact(elem_len as usize)
604 .context(AllocationSizeSnafu)?;
605 v.resize(elem_len as usize, 0);
606 file.read_exact(&mut v).context(ReadValueDataSnafu)?;
607
608 builder.receiving_application_entity_title(
609 text.decode(&v)
610 .context(DecodeTextSnafu { name: text.name() })?,
611 )
612 }
613 Tag(0x0002, 0x0100) => {
614 let mut v = Vec::new();
616 v.try_reserve_exact(elem_len as usize)
617 .context(AllocationSizeSnafu)?;
618 v.resize(elem_len as usize, 0);
619 file.read_exact(&mut v).context(ReadValueDataSnafu)?;
620
621 builder.private_information_creator_uid(
622 text.decode(&v)
623 .context(DecodeTextSnafu { name: text.name() })?,
624 )
625 }
626 Tag(0x0002, 0x0102) => {
627 let mut v = Vec::new();
629 v.try_reserve_exact(elem_len as usize)
630 .context(AllocationSizeSnafu)?;
631 v.resize(elem_len as usize, 0);
632 file.read_exact(&mut v).context(ReadValueDataSnafu)?;
633
634 builder.private_information(v)
635 }
636 tag @ Tag(0x0002, _) => {
637 tracing::info!("Unknown tag {}", tag);
640 let bytes_read =
642 std::io::copy(&mut (&mut file).take(elem_len as u64), &mut std::io::sink())
643 .context(ReadValueDataSnafu)?;
644 if bytes_read != elem_len as u64 {
645 return UnexpectedDataValueLengthSnafu {
647 tag: elem.tag(),
648 length: elem_len,
649 }
650 .fail();
651 }
652 builder
653 }
654 tag => {
655 tracing::warn!("Unexpected off-group tag {}", tag);
658 let bytes_read =
660 std::io::copy(&mut (&mut file).take(elem_len as u64), &mut std::io::sink())
661 .context(ReadValueDataSnafu)?;
662 if bytes_read != elem_len as u64 {
663 return UnexpectedDataValueLengthSnafu {
665 tag: elem.tag(),
666 length: elem_len,
667 }
668 .fail();
669 }
670 builder
671 }
672 };
673 total_bytes_read = total_bytes_read
674 .saturating_add(header_bytes_read as u32)
675 .saturating_add(elem_len);
676 }
677
678 builder.build()
679 }
680
681 pub fn into_element_iter(self) -> impl Iterator<Item = DataElement<EmptyObject, [u8; 0]>> {
688 let mut elems = vec![
689 DataElement::new(
691 Tag(0x0002, 0x0000),
692 VR::UL,
693 Value::Primitive(self.information_group_length.into()),
694 ),
695 DataElement::new(
696 Tag(0x0002, 0x0001),
697 VR::OB,
698 Value::Primitive(dicom_value!(
699 U8,
700 [self.information_version[0], self.information_version[1]]
701 )),
702 ),
703 DataElement::new(
704 Tag(0x0002, 0x0002),
705 VR::UI,
706 Value::Primitive(self.media_storage_sop_class_uid.into()),
707 ),
708 DataElement::new(
709 Tag(0x0002, 0x0003),
710 VR::UI,
711 Value::Primitive(self.media_storage_sop_instance_uid.into()),
712 ),
713 DataElement::new(
714 Tag(0x0002, 0x0010),
715 VR::UI,
716 Value::Primitive(self.transfer_syntax.into()),
717 ),
718 DataElement::new(
719 Tag(0x0002, 0x0012),
720 VR::UI,
721 Value::Primitive(self.implementation_class_uid.into()),
722 ),
723 ];
724 if let Some(v) = self.implementation_version_name {
725 elems.push(DataElement::new(
726 Tag(0x0002, 0x0013),
727 VR::SH,
728 Value::Primitive(v.into()),
729 ));
730 }
731 if let Some(v) = self.source_application_entity_title {
732 elems.push(DataElement::new(
733 Tag(0x0002, 0x0016),
734 VR::AE,
735 Value::Primitive(v.into()),
736 ));
737 }
738 if let Some(v) = self.sending_application_entity_title {
739 elems.push(DataElement::new(
740 Tag(0x0002, 0x0017),
741 VR::AE,
742 Value::Primitive(v.into()),
743 ));
744 }
745 if let Some(v) = self.receiving_application_entity_title {
746 elems.push(DataElement::new(
747 Tag(0x0002, 0x0018),
748 VR::AE,
749 Value::Primitive(v.into()),
750 ));
751 }
752 if let Some(v) = self.private_information_creator_uid {
753 elems.push(DataElement::new(
754 Tag(0x0002, 0x0100),
755 VR::UI,
756 Value::Primitive(v.into()),
757 ));
758 }
759 if let Some(v) = self.private_information {
760 elems.push(DataElement::new(
761 Tag(0x0002, 0x0102),
762 VR::OB,
763 Value::Primitive(PrimitiveValue::U8(v.into())),
764 ));
765 }
766
767 elems.into_iter()
768 }
769
770 pub fn to_element_iter(&self) -> impl Iterator<Item = DataElement<EmptyObject, [u8; 0]>> + '_ {
775 self.clone().into_element_iter()
776 }
777
778 pub fn write<W: Write>(&self, writer: W) -> Result<()> {
779 let mut dset = DataSetWriter::new(
780 writer,
781 EncoderFor::new(ExplicitVRLittleEndianEncoder::default()),
782 );
783 dset.write_sequence(
786 self.clone()
787 .into_element_iter()
788 .flat_map(IntoTokens::into_tokens),
789 )
790 .context(WriteSetSnafu)
791 }
792}
793
794impl ApplyOp for FileMetaTable {
795 type Err = ApplyError;
796
797 fn apply(&mut self, op: AttributeOp) -> ApplyResult {
802 self.apply(op)
803 }
804}
805
806#[derive(Debug, Default, Clone)]
808pub struct FileMetaTableBuilder {
809 information_group_length: Option<u32>,
811 information_version: Option<[u8; 2]>,
813 media_storage_sop_class_uid: Option<String>,
815 media_storage_sop_instance_uid: Option<String>,
817 transfer_syntax: Option<String>,
819 implementation_class_uid: Option<String>,
821
822 implementation_version_name: Option<String>,
824 source_application_entity_title: Option<String>,
826 sending_application_entity_title: Option<String>,
828 receiving_application_entity_title: Option<String>,
830 private_information_creator_uid: Option<String>,
832 private_information: Option<Vec<u8>>,
834}
835
836#[inline]
839fn padded<T>(s: T, pad: char) -> String
840where
841 T: Into<String>,
842{
843 let mut s = s.into();
844 if s.len() % 2 == 1 {
845 s.push(pad);
846 }
847 s
848}
849
850fn ui_padded<T>(s: T) -> String
852where
853 T: Into<String>,
854{
855 padded(s, '\0')
856}
857
858fn txt_padded<T>(s: T) -> String
860where
861 T: Into<String>,
862{
863 padded(s, ' ')
864}
865
866impl FileMetaTableBuilder {
867 pub fn new() -> FileMetaTableBuilder {
869 FileMetaTableBuilder::default()
870 }
871
872 pub fn group_length(mut self, value: u32) -> FileMetaTableBuilder {
874 self.information_group_length = Some(value);
875 self
876 }
877
878 pub fn information_version(mut self, value: [u8; 2]) -> FileMetaTableBuilder {
880 self.information_version = Some(value);
881 self
882 }
883
884 pub fn media_storage_sop_class_uid<T>(mut self, value: T) -> FileMetaTableBuilder
886 where
887 T: Into<String>,
888 {
889 self.media_storage_sop_class_uid = Some(ui_padded(value));
890 self
891 }
892
893 pub fn media_storage_sop_instance_uid<T>(mut self, value: T) -> FileMetaTableBuilder
895 where
896 T: Into<String>,
897 {
898 self.media_storage_sop_instance_uid = Some(ui_padded(value));
899 self
900 }
901
902 pub fn transfer_syntax<T>(mut self, value: T) -> FileMetaTableBuilder
904 where
905 T: Into<String>,
906 {
907 self.transfer_syntax = Some(ui_padded(value));
908 self
909 }
910
911 pub fn implementation_class_uid<T>(mut self, value: T) -> FileMetaTableBuilder
913 where
914 T: Into<String>,
915 {
916 self.implementation_class_uid = Some(ui_padded(value));
917 self
918 }
919
920 pub fn implementation_version_name<T>(mut self, value: T) -> FileMetaTableBuilder
922 where
923 T: Into<String>,
924 {
925 self.implementation_version_name = Some(txt_padded(value));
926 self
927 }
928
929 pub fn source_application_entity_title<T>(mut self, value: T) -> FileMetaTableBuilder
931 where
932 T: Into<String>,
933 {
934 self.source_application_entity_title = Some(txt_padded(value));
935 self
936 }
937
938 pub fn sending_application_entity_title<T>(mut self, value: T) -> FileMetaTableBuilder
940 where
941 T: Into<String>,
942 {
943 self.sending_application_entity_title = Some(txt_padded(value));
944 self
945 }
946
947 pub fn receiving_application_entity_title<T>(mut self, value: T) -> FileMetaTableBuilder
949 where
950 T: Into<String>,
951 {
952 self.receiving_application_entity_title = Some(txt_padded(value));
953 self
954 }
955
956 pub fn private_information_creator_uid<T>(mut self, value: T) -> FileMetaTableBuilder
958 where
959 T: Into<String>,
960 {
961 self.private_information_creator_uid = Some(ui_padded(value));
962 self
963 }
964
965 pub fn private_information<T>(mut self, value: T) -> FileMetaTableBuilder
967 where
968 T: Into<Vec<u8>>,
969 {
970 self.private_information = Some(value.into());
971 self
972 }
973
974 pub fn build(self) -> Result<FileMetaTable> {
976 let information_version = self.information_version.unwrap_or(
977 [0, 1],
979 );
980 let media_storage_sop_class_uid = self.media_storage_sop_class_uid.unwrap_or_else(|| {
981 tracing::warn!("MediaStorageSOPClassUID is missing. Defaulting to empty string.");
982 String::default()
983 });
984 let media_storage_sop_instance_uid =
985 self.media_storage_sop_instance_uid.unwrap_or_else(|| {
986 tracing::warn!(
987 "MediaStorageSOPInstanceUID is missing. Defaulting to empty string."
988 );
989 String::default()
990 });
991 let transfer_syntax = self.transfer_syntax.context(MissingElementSnafu {
992 alias: "TransferSyntax",
993 })?;
994 let mut implementation_version_name = self.implementation_version_name;
995 let implementation_class_uid = self.implementation_class_uid.unwrap_or_else(|| {
996 implementation_version_name = Some(IMPLEMENTATION_VERSION_NAME.to_string());
998
999 IMPLEMENTATION_CLASS_UID.to_string()
1000 });
1001
1002 let mut table = FileMetaTable {
1003 information_group_length: 0x00,
1005 information_version,
1006 media_storage_sop_class_uid,
1007 media_storage_sop_instance_uid,
1008 transfer_syntax,
1009 implementation_class_uid,
1010 implementation_version_name,
1011 source_application_entity_title: self.source_application_entity_title,
1012 sending_application_entity_title: self.sending_application_entity_title,
1013 receiving_application_entity_title: self.receiving_application_entity_title,
1014 private_information_creator_uid: self.private_information_creator_uid,
1015 private_information: self.private_information,
1016 };
1017 table.update_information_group_length();
1018 debug_assert!(table.information_group_length > 0);
1019 Ok(table)
1020 }
1021}
1022
1023fn dicom_len<T: AsRef<str>>(x: T) -> u32 {
1024 (x.as_ref().len() as u32 + 1) & !1
1025}
1026
1027#[cfg(test)]
1028mod tests {
1029 use crate::{IMPLEMENTATION_CLASS_UID, IMPLEMENTATION_VERSION_NAME};
1030
1031 use super::{dicom_len, FileMetaTable, FileMetaTableBuilder};
1032 use dicom_core::ops::{AttributeAction, AttributeOp};
1033 use dicom_core::value::Value;
1034 use dicom_core::{dicom_value, DataElement, PrimitiveValue, Tag, VR};
1035 use dicom_dictionary_std::tags;
1036
1037 const TEST_META_1: &[u8] = &[
1038 b'D', b'I', b'C', b'M',
1040 0x02, 0x00, 0x00, 0x00, b'U', b'L', 0x04, 0x00, 0xc8, 0x00, 0x00, 0x00,
1042 0x02, 0x00, 0x01, 0x00, b'O', b'B', 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x01,
1044 0x02, 0x00, 0x02, 0x00, b'U', b'I', 0x1a, 0x00, 0x31, 0x2e, 0x32, 0x2e, 0x38, 0x34, 0x30,
1046 0x2e, 0x31, 0x30, 0x30, 0x30, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e,
1047 0x31, 0x2e, 0x31, 0x00,
1048 0x02, 0x00, 0x03, 0x00, b'U', b'I', 0x38, 0x00, 0x31, 0x2e, 0x32, 0x2e, 0x33, 0x2e, 0x34,
1050 0x2e, 0x35, 0x2e, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x2e, 0x31, 0x32, 0x33,
1051 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x2e, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
1052 0x2e, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2e, 0x31, 0x32, 0x33, 0x34,
1053 0x35, 0x36, 0x37, 0x00,
1054 0x02, 0x00, 0x10, 0x00, b'U', b'I', 0x14, 0x00, 0x31, 0x2e, 0x32, 0x2e, 0x38, 0x34, 0x30,
1056 0x2e, 0x31, 0x30, 0x30, 0x30, 0x38, 0x2e, 0x31, 0x2e, 0x32, 0x2e, 0x31, 0x00,
1057 0x02, 0x00, 0x12, 0x00, b'U', b'I', 0x14, 0x00, 0x31, 0x2e, 0x32, 0x2e, 0x33, 0x34, 0x35,
1059 0x2e, 0x36, 0x2e, 0x37, 0x38, 0x39, 0x30, 0x2e, 0x31, 0x2e, 0x32, 0x33, 0x34,
1060 0x02, 0x00, 0x13, 0x00, b'S', b'H', 0x10, 0x00, 0x52, 0x55, 0x53, 0x54, 0x59, 0x5f, 0x44,
1064 0x49, 0x43, 0x4f, 0x4d, 0x5f, 0x32, 0x36, 0x39, 0x20,
1065 0x02, 0x00, 0x16, 0x00, b'A', b'E', 0x00, 0x00,
1067 ];
1068
1069 #[test]
1070 fn read_meta_table_from_reader() {
1071 let mut source = TEST_META_1;
1072
1073 let table = FileMetaTable::from_reader(&mut source).unwrap();
1074
1075 let gt = FileMetaTable {
1076 information_group_length: 200,
1077 information_version: [0u8, 1u8],
1078 media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.1\0".to_owned(),
1079 media_storage_sop_instance_uid:
1080 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".to_owned(),
1081 transfer_syntax: "1.2.840.10008.1.2.1\0".to_owned(),
1082 implementation_class_uid: "1.2.345.6.7890.1.234".to_owned(),
1083 implementation_version_name: Some("RUSTY_DICOM_269 ".to_owned()),
1084 source_application_entity_title: Some("".to_owned()),
1085 sending_application_entity_title: None,
1086 receiving_application_entity_title: None,
1087 private_information_creator_uid: None,
1088 private_information: None,
1089 };
1090
1091 assert_eq!(table.information_group_length, 200);
1092 assert_eq!(table.information_version, [0u8, 1u8]);
1093 assert_eq!(
1094 table.media_storage_sop_class_uid,
1095 "1.2.840.10008.5.1.4.1.1.1\0"
1096 );
1097 assert_eq!(
1098 table.media_storage_sop_instance_uid,
1099 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0"
1100 );
1101 assert_eq!(table.transfer_syntax, "1.2.840.10008.1.2.1\0");
1102 assert_eq!(table.implementation_class_uid, "1.2.345.6.7890.1.234");
1103 assert_eq!(
1104 table.implementation_version_name,
1105 Some("RUSTY_DICOM_269 ".to_owned())
1106 );
1107 assert_eq!(table.source_application_entity_title, Some("".into()));
1108 assert_eq!(table.sending_application_entity_title, None);
1109 assert_eq!(table.receiving_application_entity_title, None);
1110 assert_eq!(table.private_information_creator_uid, None);
1111 assert_eq!(table.private_information, None);
1112
1113 assert_eq!(table, gt);
1114 }
1115
1116 #[test]
1117 fn create_meta_table_with_builder() {
1118 let table = FileMetaTableBuilder::new()
1119 .information_version([0, 1])
1120 .media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.1")
1121 .media_storage_sop_instance_uid(
1122 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567",
1123 )
1124 .transfer_syntax("1.2.840.10008.1.2.1")
1125 .implementation_class_uid("1.2.345.6.7890.1.234")
1126 .implementation_version_name("RUSTY_DICOM_269")
1127 .source_application_entity_title("")
1128 .build()
1129 .unwrap();
1130
1131 let gt = FileMetaTable {
1132 information_group_length: 200,
1133 information_version: [0u8, 1u8],
1134 media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.1\0".to_owned(),
1135 media_storage_sop_instance_uid:
1136 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".to_owned(),
1137 transfer_syntax: "1.2.840.10008.1.2.1\0".to_owned(),
1138 implementation_class_uid: "1.2.345.6.7890.1.234".to_owned(),
1139 implementation_version_name: Some("RUSTY_DICOM_269 ".to_owned()),
1140 source_application_entity_title: Some("".to_owned()),
1141 sending_application_entity_title: None,
1142 receiving_application_entity_title: None,
1143 private_information_creator_uid: None,
1144 private_information: None,
1145 };
1146
1147 assert_eq!(table.information_group_length, gt.information_group_length);
1148 assert_eq!(table, gt);
1149 }
1150
1151 #[test]
1153 fn create_meta_table_with_builder_minimal() {
1154 let table = FileMetaTableBuilder::new()
1155 .media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.1")
1156 .media_storage_sop_instance_uid(
1157 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567",
1158 )
1159 .transfer_syntax("1.2.840.10008.1.2")
1160 .build()
1161 .unwrap();
1162
1163 let gt = FileMetaTable {
1164 information_group_length: 154
1165 + dicom_len(IMPLEMENTATION_CLASS_UID)
1166 + dicom_len(IMPLEMENTATION_VERSION_NAME),
1167 information_version: [0u8, 1u8],
1168 media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.1\0".to_owned(),
1169 media_storage_sop_instance_uid:
1170 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".to_owned(),
1171 transfer_syntax: "1.2.840.10008.1.2\0".to_owned(),
1172 implementation_class_uid: IMPLEMENTATION_CLASS_UID.to_owned(),
1173 implementation_version_name: Some(IMPLEMENTATION_VERSION_NAME.to_owned()),
1174 source_application_entity_title: None,
1175 sending_application_entity_title: None,
1176 receiving_application_entity_title: None,
1177 private_information_creator_uid: None,
1178 private_information: None,
1179 };
1180
1181 assert_eq!(table.information_group_length, gt.information_group_length);
1182 assert_eq!(table, gt);
1183 }
1184
1185 #[test]
1187 fn change_transfer_syntax_update_table() {
1188 let mut table = FileMetaTableBuilder::new()
1189 .media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.1")
1190 .media_storage_sop_instance_uid(
1191 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567",
1192 )
1193 .transfer_syntax("1.2.840.10008.1.2.1")
1194 .build()
1195 .unwrap();
1196
1197 assert_eq!(
1198 table.information_group_length,
1199 156 + dicom_len(IMPLEMENTATION_CLASS_UID) + dicom_len(IMPLEMENTATION_VERSION_NAME)
1200 );
1201
1202 table.set_transfer_syntax(
1203 &dicom_transfer_syntax_registry::entries::IMPLICIT_VR_LITTLE_ENDIAN,
1204 );
1205 assert_eq!(
1206 table.information_group_length,
1207 154 + dicom_len(IMPLEMENTATION_CLASS_UID) + dicom_len(IMPLEMENTATION_VERSION_NAME)
1208 );
1209 }
1210
1211 #[test]
1212 fn read_meta_table_into_iter() {
1213 let table = FileMetaTable {
1214 information_group_length: 200,
1215 information_version: [0u8, 1u8],
1216 media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.1\0".to_owned(),
1217 media_storage_sop_instance_uid:
1218 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".to_owned(),
1219 transfer_syntax: "1.2.840.10008.1.2.1\0".to_owned(),
1220 implementation_class_uid: "1.2.345.6.7890.1.234".to_owned(),
1221 implementation_version_name: Some("RUSTY_DICOM_269 ".to_owned()),
1222 source_application_entity_title: Some("".to_owned()),
1223 sending_application_entity_title: None,
1224 receiving_application_entity_title: None,
1225 private_information_creator_uid: None,
1226 private_information: None,
1227 };
1228
1229 assert_eq!(table.calculate_information_group_length(), 200);
1230
1231 let gt = vec![
1232 DataElement::new(Tag(0x0002, 0x0000), VR::UL, dicom_value!(U32, 200)),
1234 DataElement::new(Tag(0x0002, 0x0001), VR::OB, dicom_value!(U8, [0, 1])),
1236 DataElement::new(
1238 Tag(0x0002, 0x0002),
1239 VR::UI,
1240 Value::Primitive("1.2.840.10008.5.1.4.1.1.1\0".into()),
1241 ),
1242 DataElement::new(
1244 Tag(0x0002, 0x0003),
1245 VR::UI,
1246 Value::Primitive(
1247 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".into(),
1248 ),
1249 ),
1250 DataElement::new(
1252 Tag(0x0002, 0x0010),
1253 VR::UI,
1254 Value::Primitive("1.2.840.10008.1.2.1\0".into()),
1255 ),
1256 DataElement::new(
1258 Tag(0x0002, 0x0012),
1259 VR::UI,
1260 Value::Primitive("1.2.345.6.7890.1.234".into()),
1261 ),
1262 DataElement::new(
1264 Tag(0x0002, 0x0013),
1265 VR::SH,
1266 Value::Primitive("RUSTY_DICOM_269 ".into()),
1267 ),
1268 DataElement::new(Tag(0x0002, 0x0016), VR::AE, Value::Primitive("".into())),
1270 ];
1271
1272 let elems: Vec<_> = table.into_element_iter().collect();
1273 assert_eq!(elems, gt);
1274 }
1275
1276 #[test]
1277 fn update_table_with_length() {
1278 let mut table = FileMetaTable {
1279 information_group_length: 55, information_version: [0u8, 1u8],
1281 media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.1\0".to_owned(),
1282 media_storage_sop_instance_uid:
1283 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".to_owned(),
1284 transfer_syntax: "1.2.840.10008.1.2.1\0".to_owned(),
1285 implementation_class_uid: "1.2.345.6.7890.1.234".to_owned(),
1286 implementation_version_name: Some("RUSTY_DICOM_269 ".to_owned()),
1287 source_application_entity_title: Some("".to_owned()),
1288 sending_application_entity_title: None,
1289 receiving_application_entity_title: None,
1290 private_information_creator_uid: None,
1291 private_information: None,
1292 };
1293
1294 table.update_information_group_length();
1295
1296 assert_eq!(table.information_group_length, 200);
1297 }
1298
1299 #[test]
1300 fn table_ops() {
1301 let mut table = FileMetaTable {
1302 information_group_length: 200,
1303 information_version: [0u8, 1u8],
1304 media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.1\0".to_owned(),
1305 media_storage_sop_instance_uid:
1306 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".to_owned(),
1307 transfer_syntax: "1.2.840.10008.1.2.1\0".to_owned(),
1308 implementation_class_uid: "1.2.345.6.7890.1.234".to_owned(),
1309 implementation_version_name: None,
1310 source_application_entity_title: None,
1311 sending_application_entity_title: None,
1312 receiving_application_entity_title: None,
1313 private_information_creator_uid: None,
1314 private_information: None,
1315 };
1316
1317 table
1319 .apply(AttributeOp::new(
1320 tags::IMPLEMENTATION_VERSION_NAME,
1321 AttributeAction::ReplaceStr("MY_DICOM_1.1".into()),
1322 ))
1323 .unwrap();
1324
1325 assert_eq!(table.implementation_version_name, None);
1326
1327 table
1329 .apply(AttributeOp::new(
1330 tags::IMPLEMENTATION_VERSION_NAME,
1331 AttributeAction::SetStr("MY_DICOM_1.1".into()),
1332 ))
1333 .unwrap();
1334
1335 assert_eq!(
1336 table.implementation_version_name.as_deref(),
1337 Some("MY_DICOM_1.1"),
1338 );
1339
1340 table
1342 .apply(AttributeOp::new(
1343 tags::SOURCE_APPLICATION_ENTITY_TITLE,
1344 AttributeAction::Set(PrimitiveValue::Str("RICOOGLE-STORAGE".into())),
1345 ))
1346 .unwrap();
1347
1348 assert_eq!(
1349 table.source_application_entity_title.as_deref(),
1350 Some("RICOOGLE-STORAGE"),
1351 );
1352
1353 table
1355 .apply(AttributeOp::new(
1356 tags::SOURCE_APPLICATION_ENTITY_TITLE,
1357 AttributeAction::SetStrIfMissing("STORE-SCU".into()),
1358 ))
1359 .unwrap();
1360
1361 assert_eq!(
1362 table.source_application_entity_title.as_deref(),
1363 Some("RICOOGLE-STORAGE"),
1364 );
1365
1366 table
1367 .apply(AttributeOp::new(
1368 tags::SENDING_APPLICATION_ENTITY_TITLE,
1369 AttributeAction::SetStrIfMissing("STORE-SCU".into()),
1370 ))
1371 .unwrap();
1372
1373 assert_eq!(
1374 table.sending_application_entity_title.as_deref(),
1375 Some("STORE-SCU"),
1376 );
1377
1378 table
1380 .apply(AttributeOp::new(
1381 tags::MEDIA_STORAGE_SOP_CLASS_UID,
1382 AttributeAction::Replace(PrimitiveValue::Str("1.2.840.10008.5.1.4.1.1.7".into())),
1383 ))
1384 .unwrap();
1385
1386 assert_eq!(
1387 table.media_storage_sop_class_uid(),
1388 "1.2.840.10008.5.1.4.1.1.7",
1389 );
1390 }
1391
1392 #[test]
1395 fn write_read_does_not_fail() {
1396 let mut table = FileMetaTable {
1397 information_group_length: 0,
1398 information_version: [0u8, 1u8],
1399 media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.7".to_owned(),
1400 media_storage_sop_instance_uid: "2.25.137731752600317795446120660167595746868".to_owned(),
1401 transfer_syntax: "1.2.840.10008.1.2.4.91".to_owned(),
1402 implementation_class_uid: "2.25.305828488182831875890203105390285383139".to_owned(),
1403 implementation_version_name: Some("MYTOOL100".to_owned()),
1404 source_application_entity_title: Some("RUSTY".to_owned()),
1405 receiving_application_entity_title: None,
1406 sending_application_entity_title: None,
1407 private_information_creator_uid: None,
1408 private_information: None,
1409 };
1410
1411 table.update_information_group_length();
1412
1413 let mut buf = vec![b'D', b'I', b'C', b'M'];
1414 table.write(&mut buf).unwrap();
1415
1416 let table2 = FileMetaTable::from_reader(&mut buf.as_slice())
1417 .expect("Should not fail to read the table from the written data");
1418
1419 assert_eq!(table.information_group_length, table2.information_group_length);
1420 }
1421}