1use byteordered::byteorder::{ByteOrder, LittleEndian};
3use dicom_core::dicom_value;
4use dicom_core::header::{DataElement, EmptyObject, HasLength, Header};
5use dicom_core::ops::{
6 ApplyOp, AttributeAction, AttributeOp, AttributeSelector, AttributeSelectorStep,
7};
8use dicom_core::value::{
9 ConvertValueError, DicomValueType, InMemFragment, PrimitiveValue, Value, ValueType,
10};
11use dicom_core::{Length, Tag, VR};
12use dicom_dictionary_std::tags;
13use dicom_encoding::TransferSyntax;
14use dicom_encoding::decode::{self, DecodeFrom};
15use dicom_encoding::encode::EncoderFor;
16use dicom_encoding::encode::explicit_le::ExplicitVRLittleEndianEncoder;
17use dicom_encoding::text::{self, TextCodec};
18use dicom_parser::dataset::{DataSetWriter, IntoTokens};
19use snafu::{Backtrace, OptionExt, ResultExt, Snafu, ensure};
20use std::borrow::Cow;
21use std::io::{Read, Write};
22
23use crate::ops::{
24 ApplyError, ApplyResult, IllegalExtendSnafu, IncompatibleTypesSnafu, MandatorySnafu,
25 UnsupportedActionSnafu, UnsupportedAttributeSnafu,
26};
27use crate::{
28 AtAccessError, AttributeError, DicomAttribute, DicomObject, IMPLEMENTATION_CLASS_UID,
29 IMPLEMENTATION_VERSION_NAME,
30};
31
32const DICM_MAGIC_CODE: [u8; 4] = [b'D', b'I', b'C', b'M'];
33
34#[derive(Debug, Snafu)]
35#[non_exhaustive]
36pub enum Error {
37 #[snafu(display("Could not start reading DICOM data"))]
40 ReadMagicCode {
41 backtrace: Backtrace,
42 source: std::io::Error,
43 },
44
45 #[snafu(display("Could not read data value"))]
48 ReadValueData {
49 backtrace: Backtrace,
50 source: std::io::Error,
51 },
52
53 #[snafu(display("Could not allocate memory"))]
56 AllocationSize {
57 backtrace: Backtrace,
58 source: std::collections::TryReserveError,
59 },
60
61 #[snafu(display("Could not decode text in {}", name))]
64 DecodeText {
65 name: std::borrow::Cow<'static, str>,
66 #[snafu(backtrace)]
67 source: dicom_encoding::text::DecodeTextError,
68 },
69
70 #[snafu(display("Invalid DICOM file (magic code check failed)"))]
72 NotDicom { backtrace: Backtrace },
73
74 #[snafu(display("Could not decode data element"))]
77 DecodeElement {
78 #[snafu(backtrace)]
79 source: dicom_encoding::decode::Error,
80 },
81
82 #[snafu(display("Unexpected data element tagged {}", tag))]
86 UnexpectedTag { tag: Tag, backtrace: Backtrace },
87
88 #[snafu(display("Missing data element `{}`", alias))]
90 MissingElement {
91 alias: &'static str,
92 backtrace: Backtrace,
93 },
94
95 #[snafu(display("Unexpected length {} for data element tagged {}", length, tag))]
98 UnexpectedDataValueLength {
99 tag: Tag,
100 length: Length,
101 backtrace: Backtrace,
102 },
103
104 #[snafu(display("Undefined value length for data element tagged {}", tag))]
107 UndefinedValueLength { tag: Tag, backtrace: Backtrace },
108
109 #[snafu(display("Could not write file meta group data set"))]
111 WriteSet {
112 #[snafu(backtrace)]
113 source: dicom_parser::dataset::write::Error,
114 },
115}
116
117type Result<T, E = Error> = std::result::Result<T, E>;
118
119#[derive(Debug, Clone)]
133pub struct FileMetaTable {
134 pub information_group_length: u32,
136 pub information_version: [u8; 2],
138 pub media_storage_sop_class_uid: String,
140 pub media_storage_sop_instance_uid: String,
142 pub transfer_syntax: String,
144 pub implementation_class_uid: String,
146
147 pub implementation_version_name: Option<String>,
149 pub source_application_entity_title: Option<String>,
151 pub sending_application_entity_title: Option<String>,
153 pub receiving_application_entity_title: Option<String>,
155 pub private_information_creator_uid: Option<String>,
157 pub private_information: Option<Vec<u8>>,
159 }
174
175impl std::cmp::PartialEq for FileMetaTable {
181 fn eq(&self, other: &Self) -> bool {
182 self.information_group_length == other.information_group_length
183 && self.information_version == other.information_version
184 && self.media_storage_sop_class_uid() == other.media_storage_sop_class_uid()
185 && self.media_storage_sop_instance_uid() == other.media_storage_sop_instance_uid()
186 && self.transfer_syntax() == other.transfer_syntax()
187 && self.implementation_class_uid() == other.implementation_class_uid()
188 && self.implementation_version_name() == other.implementation_version_name()
189 && self.source_application_entity_title() == other.source_application_entity_title()
190 && self.sending_application_entity_title() == other.sending_application_entity_title()
191 && self.receiving_application_entity_title()
192 == other.receiving_application_entity_title()
193 && self.private_information_creator_uid() == other.private_information_creator_uid()
194 && match (&self.private_information, &other.private_information) {
195 (None, None) => true,
196 (Some(private_info_1), Some(private_info_2)) => {
197 bytes_eq_without_trailing_byte(private_info_1, private_info_2)
198 }
199 (Some(_), None) | (None, Some(_)) => false,
200 }
201 }
202}
203
204fn bytes_eq_without_trailing_byte(v1: &[u8], v2: &[u8]) -> bool {
207 let v1 = if v1.len() % 2 == 0 && v1.last().copied() == Some(0) {
208 &v1[..v1.len() - 1]
209 } else {
210 v1
211 };
212 let v2 = if v2.len() % 2 == 0 && v2.last().copied() == Some(0) {
213 &v2[..v2.len() - 1]
214 } else {
215 v2
216 };
217
218 v1 == v2
219}
220
221fn read_str_body<'s, S, T>(source: &'s mut S, text: &T, len: u32) -> Result<String>
223where
224 S: Read + 's,
225 T: TextCodec,
226{
227 let mut v = Vec::new();
228 v.try_reserve_exact(len as usize)
229 .context(AllocationSizeSnafu)?;
230 v.resize(len as usize, 0);
231 source.read_exact(&mut v).context(ReadValueDataSnafu)?;
232
233 text.decode(&v)
234 .context(DecodeTextSnafu { name: text.name() })
235}
236
237impl FileMetaTable {
238 pub fn from_reader<R: Read>(file: R) -> Result<Self> {
244 FileMetaTable::read_from(file)
245 }
246
247 pub fn transfer_syntax(&self) -> &str {
250 self.transfer_syntax
251 .trim_end_matches(|c: char| c.is_whitespace() || c == '\0')
252 }
253
254 pub fn media_storage_sop_instance_uid(&self) -> &str {
257 self.media_storage_sop_instance_uid
258 .trim_end_matches(|c: char| c.is_whitespace() || c == '\0')
259 }
260
261 pub fn media_storage_sop_class_uid(&self) -> &str {
264 self.media_storage_sop_class_uid
265 .trim_end_matches(|c: char| c.is_whitespace() || c == '\0')
266 }
267
268 pub fn implementation_class_uid(&self) -> &str {
271 self.implementation_class_uid
272 .trim_end_matches(|c: char| c.is_whitespace() || c == '\0')
273 }
274
275 pub fn implementation_version_name(&self) -> Option<&str> {
278 self.implementation_version_name
279 .as_ref()
280 .map(|s| s.trim_end_matches(|c: char| c.is_whitespace() || c == '\0'))
281 }
282
283 pub fn source_application_entity_title(&self) -> Option<&str> {
286 self.source_application_entity_title
287 .as_ref()
288 .map(|s| s.trim_end_matches(|c: char| c.is_whitespace() || c == '\0'))
289 }
290
291 pub fn sending_application_entity_title(&self) -> Option<&str> {
294 self.sending_application_entity_title
295 .as_ref()
296 .map(|s| s.trim_end_matches(|c: char| c.is_whitespace() || c == '\0'))
297 }
298
299 pub fn receiving_application_entity_title(&self) -> Option<&str> {
302 self.receiving_application_entity_title
303 .as_ref()
304 .map(|s| s.trim_end_matches(|c: char| c.is_whitespace() || c == '\0'))
305 }
306
307 pub fn private_information_creator_uid(&self) -> Option<&str> {
310 self.private_information_creator_uid
311 .as_ref()
312 .map(|s| s.trim_end_matches(|c: char| c.is_whitespace() || c == '\0'))
313 }
314
315 pub fn set_transfer_syntax<D, R, W>(&mut self, ts: &TransferSyntax<D, R, W>) {
322 self.transfer_syntax = ts
323 .uid()
324 .trim_end_matches(|c: char| c.is_whitespace() || c == '\0')
325 .to_string();
326 self.update_information_group_length();
327 }
328
329 pub fn update_information_group_length(&mut self) {
333 self.information_group_length = self.calculate_information_group_length();
334 }
335
336 fn apply(&mut self, op: AttributeOp) -> ApplyResult {
341 let AttributeSelectorStep::Tag(tag) = op.selector.first_step() else {
342 return UnsupportedAttributeSnafu.fail();
343 };
344
345 match *tag {
346 tags::TRANSFER_SYNTAX_UID => Self::apply_required_string(op, &mut self.transfer_syntax),
347 tags::MEDIA_STORAGE_SOP_CLASS_UID => {
348 Self::apply_required_string(op, &mut self.media_storage_sop_class_uid)
349 }
350 tags::MEDIA_STORAGE_SOP_INSTANCE_UID => {
351 Self::apply_required_string(op, &mut self.media_storage_sop_instance_uid)
352 }
353 tags::IMPLEMENTATION_CLASS_UID => {
354 Self::apply_required_string(op, &mut self.implementation_class_uid)
355 }
356 tags::IMPLEMENTATION_VERSION_NAME => {
357 Self::apply_optional_string(op, &mut self.implementation_version_name)
358 }
359 tags::SOURCE_APPLICATION_ENTITY_TITLE => {
360 Self::apply_optional_string(op, &mut self.source_application_entity_title)
361 }
362 tags::SENDING_APPLICATION_ENTITY_TITLE => {
363 Self::apply_optional_string(op, &mut self.sending_application_entity_title)
364 }
365 tags::RECEIVING_APPLICATION_ENTITY_TITLE => {
366 Self::apply_optional_string(op, &mut self.receiving_application_entity_title)
367 }
368 tags::PRIVATE_INFORMATION_CREATOR_UID => {
369 Self::apply_optional_string(op, &mut self.private_information_creator_uid)
370 }
371 _ if matches!(
372 op.action,
373 AttributeAction::Remove | AttributeAction::Empty | AttributeAction::Truncate(_)
374 ) =>
375 {
376 Ok(())
379 }
380 _ => UnsupportedAttributeSnafu.fail(),
381 }?;
382
383 self.update_information_group_length();
384
385 Ok(())
386 }
387
388 fn apply_required_string(op: AttributeOp, target_attribute: &mut String) -> ApplyResult {
389 match op.action {
390 AttributeAction::Remove | AttributeAction::Empty => MandatorySnafu.fail(),
391 AttributeAction::SetVr(_) | AttributeAction::Truncate(_) => {
392 Ok(())
394 }
395 AttributeAction::Set(value) | AttributeAction::Replace(value) => {
396 if let Ok(value) = value.string() {
398 *target_attribute = value.to_string();
399 Ok(())
400 } else {
401 IncompatibleTypesSnafu {
402 kind: ValueType::Str,
403 }
404 .fail()
405 }
406 }
407 AttributeAction::SetStr(string) | AttributeAction::ReplaceStr(string) => {
408 *target_attribute = string.to_string();
409 Ok(())
410 }
411 AttributeAction::SetIfMissing(_) | AttributeAction::SetStrIfMissing(_) => {
412 Ok(())
414 }
415 AttributeAction::PushStr(_) => IllegalExtendSnafu.fail(),
416 AttributeAction::PushI32(_)
417 | AttributeAction::PushU32(_)
418 | AttributeAction::PushI16(_)
419 | AttributeAction::PushU16(_)
420 | AttributeAction::PushF32(_)
421 | AttributeAction::PushF64(_) => IncompatibleTypesSnafu {
422 kind: ValueType::Str,
423 }
424 .fail(),
425 _ => UnsupportedActionSnafu.fail(),
426 }
427 }
428
429 fn apply_optional_string(
430 op: AttributeOp,
431 target_attribute: &mut Option<String>,
432 ) -> ApplyResult {
433 match op.action {
434 AttributeAction::Remove => {
435 target_attribute.take();
436 Ok(())
437 }
438 AttributeAction::Empty => {
439 if let Some(s) = target_attribute.as_mut() {
440 s.clear();
441 }
442 Ok(())
443 }
444 AttributeAction::SetVr(_) => {
445 Ok(())
447 }
448 AttributeAction::Set(value) => {
449 if let Ok(value) = value.string() {
451 *target_attribute = Some(value.to_string());
452 Ok(())
453 } else {
454 IncompatibleTypesSnafu {
455 kind: ValueType::Str,
456 }
457 .fail()
458 }
459 }
460 AttributeAction::SetStr(value) => {
461 *target_attribute = Some(value.to_string());
462 Ok(())
463 }
464 AttributeAction::SetIfMissing(value) => {
465 if target_attribute.is_some() {
466 return Ok(());
467 }
468
469 if let Ok(value) = value.string() {
471 *target_attribute = Some(value.to_string());
472 Ok(())
473 } else {
474 IncompatibleTypesSnafu {
475 kind: ValueType::Str,
476 }
477 .fail()
478 }
479 }
480 AttributeAction::SetStrIfMissing(value) => {
481 if target_attribute.is_none() {
482 *target_attribute = Some(value.to_string());
483 }
484 Ok(())
485 }
486 AttributeAction::Replace(value) => {
487 if target_attribute.is_none() {
488 return Ok(());
489 }
490
491 if let Ok(value) = value.string() {
493 *target_attribute = Some(value.to_string());
494 Ok(())
495 } else {
496 IncompatibleTypesSnafu {
497 kind: ValueType::Str,
498 }
499 .fail()
500 }
501 }
502 AttributeAction::ReplaceStr(value) => {
503 if target_attribute.is_some() {
504 *target_attribute = Some(value.to_string());
505 }
506 Ok(())
507 }
508 AttributeAction::PushStr(_) => IllegalExtendSnafu.fail(),
509 AttributeAction::PushI32(_)
510 | AttributeAction::PushU32(_)
511 | AttributeAction::PushI16(_)
512 | AttributeAction::PushU16(_)
513 | AttributeAction::PushF32(_)
514 | AttributeAction::PushF64(_) => IncompatibleTypesSnafu {
515 kind: ValueType::Str,
516 }
517 .fail(),
518 _ => UnsupportedActionSnafu.fail(),
519 }
520 }
521
522 fn calculate_information_group_length(&self) -> u32 {
525 14 + 8
529 + dicom_len(&self.media_storage_sop_class_uid)
530 + 8
531 + dicom_len(&self.media_storage_sop_instance_uid)
532 + 8
533 + dicom_len(&self.transfer_syntax)
534 + 8
535 + dicom_len(&self.implementation_class_uid)
536 + self
537 .implementation_version_name
538 .as_ref()
539 .map(|s| 8 + dicom_len(s))
540 .unwrap_or(0)
541 + self
542 .source_application_entity_title
543 .as_ref()
544 .map(|s| 8 + dicom_len(s))
545 .unwrap_or(0)
546 + self
547 .sending_application_entity_title
548 .as_ref()
549 .map(|s| 8 + dicom_len(s))
550 .unwrap_or(0)
551 + self
552 .receiving_application_entity_title
553 .as_ref()
554 .map(|s| 8 + dicom_len(s))
555 .unwrap_or(0)
556 + self
557 .private_information_creator_uid
558 .as_ref()
559 .map(|s| 8 + dicom_len(s))
560 .unwrap_or(0)
561 + self
562 .private_information
563 .as_ref()
564 .map(|x| 12 + ((x.len() as u32 + 1) & !1))
565 .unwrap_or(0)
566 }
567
568 fn read_from<S: Read>(mut file: S) -> Result<Self> {
571 let mut buff: [u8; 4] = [0; 4];
572 {
573 file.read_exact(&mut buff).context(ReadMagicCodeSnafu)?;
575
576 ensure!(buff == DICM_MAGIC_CODE, NotDicomSnafu);
577 }
578
579 let decoder = decode::file_header_decoder();
580 let text = text::DefaultCharacterSetCodec;
581
582 let builder = FileMetaTableBuilder::new();
583
584 let group_length: u32 = {
585 let (elem, _bytes_read) = decoder
586 .decode_header(&mut file)
587 .context(DecodeElementSnafu)?;
588 if elem.tag() != Tag(0x0002, 0x0000) {
589 return UnexpectedTagSnafu { tag: elem.tag() }.fail();
590 }
591 if elem.length() != Length(4) {
592 return UnexpectedDataValueLengthSnafu {
593 tag: elem.tag(),
594 length: elem.length(),
595 }
596 .fail();
597 }
598 let mut buff: [u8; 4] = [0; 4];
599 file.read_exact(&mut buff).context(ReadValueDataSnafu)?;
600 LittleEndian::read_u32(&buff)
601 };
602
603 let mut total_bytes_read = 0;
604 let mut builder = builder.group_length(group_length);
605
606 while total_bytes_read < group_length {
608 let (elem, header_bytes_read) = decoder
609 .decode_header(&mut file)
610 .context(DecodeElementSnafu)?;
611 let elem_len = match elem.length().get() {
612 None => {
613 return UndefinedValueLengthSnafu { tag: elem.tag() }.fail();
614 }
615 Some(len) => len,
616 };
617 builder = match elem.tag() {
618 Tag(0x0002, 0x0001) => {
619 if elem.length() != Length(2) {
621 return UnexpectedDataValueLengthSnafu {
622 tag: elem.tag(),
623 length: elem.length(),
624 }
625 .fail();
626 }
627 let mut hbuf = [0u8; 2];
628 file.read_exact(&mut hbuf[..]).context(ReadValueDataSnafu)?;
629
630 builder.information_version(hbuf)
631 }
632 Tag(0x0002, 0x0002) => {
634 builder.media_storage_sop_class_uid(read_str_body(&mut file, &text, elem_len)?)
635 }
636 Tag(0x0002, 0x0003) => builder
638 .media_storage_sop_instance_uid(read_str_body(&mut file, &text, elem_len)?),
639 Tag(0x0002, 0x0010) => {
641 builder.transfer_syntax(read_str_body(&mut file, &text, elem_len)?)
642 }
643 Tag(0x0002, 0x0012) => {
645 builder.implementation_class_uid(read_str_body(&mut file, &text, elem_len)?)
646 }
647 Tag(0x0002, 0x0013) => {
648 let mut v = Vec::new();
650 v.try_reserve_exact(elem_len as usize)
651 .context(AllocationSizeSnafu)?;
652 v.resize(elem_len as usize, 0);
653 file.read_exact(&mut v).context(ReadValueDataSnafu)?;
654
655 builder.implementation_version_name(
656 text.decode(&v)
657 .context(DecodeTextSnafu { name: text.name() })?,
658 )
659 }
660 Tag(0x0002, 0x0016) => {
661 let mut v = Vec::new();
663 v.try_reserve_exact(elem_len as usize)
664 .context(AllocationSizeSnafu)?;
665 v.resize(elem_len as usize, 0);
666 file.read_exact(&mut v).context(ReadValueDataSnafu)?;
667
668 builder.source_application_entity_title(
669 text.decode(&v)
670 .context(DecodeTextSnafu { name: text.name() })?,
671 )
672 }
673 Tag(0x0002, 0x0017) => {
674 let mut v = Vec::new();
676 v.try_reserve_exact(elem_len as usize)
677 .context(AllocationSizeSnafu)?;
678 v.resize(elem_len as usize, 0);
679 file.read_exact(&mut v).context(ReadValueDataSnafu)?;
680
681 builder.sending_application_entity_title(
682 text.decode(&v)
683 .context(DecodeTextSnafu { name: text.name() })?,
684 )
685 }
686 Tag(0x0002, 0x0018) => {
687 let mut v = Vec::new();
689 v.try_reserve_exact(elem_len as usize)
690 .context(AllocationSizeSnafu)?;
691 v.resize(elem_len as usize, 0);
692 file.read_exact(&mut v).context(ReadValueDataSnafu)?;
693
694 builder.receiving_application_entity_title(
695 text.decode(&v)
696 .context(DecodeTextSnafu { name: text.name() })?,
697 )
698 }
699 Tag(0x0002, 0x0100) => {
700 let mut v = Vec::new();
702 v.try_reserve_exact(elem_len as usize)
703 .context(AllocationSizeSnafu)?;
704 v.resize(elem_len as usize, 0);
705 file.read_exact(&mut v).context(ReadValueDataSnafu)?;
706
707 builder.private_information_creator_uid(
708 text.decode(&v)
709 .context(DecodeTextSnafu { name: text.name() })?,
710 )
711 }
712 Tag(0x0002, 0x0102) => {
713 let mut v = Vec::new();
715 v.try_reserve_exact(elem_len as usize)
716 .context(AllocationSizeSnafu)?;
717 v.resize(elem_len as usize, 0);
718 file.read_exact(&mut v).context(ReadValueDataSnafu)?;
719
720 builder.private_information(v)
721 }
722 tag @ Tag(0x0002, _) => {
723 tracing::info!("Unknown tag {}", tag);
726 let bytes_read =
728 std::io::copy(&mut (&mut file).take(elem_len as u64), &mut std::io::sink())
729 .context(ReadValueDataSnafu)?;
730 if bytes_read != elem_len as u64 {
731 return UnexpectedDataValueLengthSnafu {
733 tag: elem.tag(),
734 length: elem_len,
735 }
736 .fail();
737 }
738 builder
739 }
740 tag => {
741 tracing::warn!("Unexpected off-group tag {}", tag);
744 let bytes_read =
746 std::io::copy(&mut (&mut file).take(elem_len as u64), &mut std::io::sink())
747 .context(ReadValueDataSnafu)?;
748 if bytes_read != elem_len as u64 {
749 return UnexpectedDataValueLengthSnafu {
751 tag: elem.tag(),
752 length: elem_len,
753 }
754 .fail();
755 }
756 builder
757 }
758 };
759 total_bytes_read = total_bytes_read
760 .saturating_add(header_bytes_read as u32)
761 .saturating_add(elem_len);
762 }
763
764 builder.build()
765 }
766
767 pub fn into_element_iter(self) -> impl Iterator<Item = DataElement<EmptyObject, [u8; 0]>> {
774 let mut elems = vec![
775 DataElement::new(
777 Tag(0x0002, 0x0000),
778 VR::UL,
779 Value::Primitive(self.information_group_length.into()),
780 ),
781 DataElement::new(
782 Tag(0x0002, 0x0001),
783 VR::OB,
784 Value::Primitive(dicom_value!(
785 U8,
786 [self.information_version[0], self.information_version[1]]
787 )),
788 ),
789 DataElement::new(
790 Tag(0x0002, 0x0002),
791 VR::UI,
792 Value::Primitive(self.media_storage_sop_class_uid.into()),
793 ),
794 DataElement::new(
795 Tag(0x0002, 0x0003),
796 VR::UI,
797 Value::Primitive(self.media_storage_sop_instance_uid.into()),
798 ),
799 DataElement::new(
800 Tag(0x0002, 0x0010),
801 VR::UI,
802 Value::Primitive(self.transfer_syntax.into()),
803 ),
804 DataElement::new(
805 Tag(0x0002, 0x0012),
806 VR::UI,
807 Value::Primitive(self.implementation_class_uid.into()),
808 ),
809 ];
810 if let Some(v) = self.implementation_version_name {
811 elems.push(DataElement::new(
812 Tag(0x0002, 0x0013),
813 VR::SH,
814 Value::Primitive(v.into()),
815 ));
816 }
817 if let Some(v) = self.source_application_entity_title {
818 elems.push(DataElement::new(
819 Tag(0x0002, 0x0016),
820 VR::AE,
821 Value::Primitive(v.into()),
822 ));
823 }
824 if let Some(v) = self.sending_application_entity_title {
825 elems.push(DataElement::new(
826 Tag(0x0002, 0x0017),
827 VR::AE,
828 Value::Primitive(v.into()),
829 ));
830 }
831 if let Some(v) = self.receiving_application_entity_title {
832 elems.push(DataElement::new(
833 Tag(0x0002, 0x0018),
834 VR::AE,
835 Value::Primitive(v.into()),
836 ));
837 }
838 if let Some(v) = self.private_information_creator_uid {
839 elems.push(DataElement::new(
840 Tag(0x0002, 0x0100),
841 VR::UI,
842 Value::Primitive(v.into()),
843 ));
844 }
845 if let Some(v) = self.private_information {
846 elems.push(DataElement::new(
847 Tag(0x0002, 0x0102),
848 VR::OB,
849 Value::Primitive(PrimitiveValue::U8(v.into())),
850 ));
851 }
852
853 elems.into_iter()
854 }
855
856 pub fn to_element_iter(&self) -> impl Iterator<Item = DataElement<EmptyObject, [u8; 0]>> + '_ {
861 self.clone().into_element_iter()
862 }
863
864 pub fn write<W: Write>(&self, writer: W) -> Result<()> {
865 let mut dset = DataSetWriter::new(
866 writer,
867 EncoderFor::new(ExplicitVRLittleEndianEncoder::default()),
868 );
869 dset.write_sequence(
872 self.clone()
873 .into_element_iter()
874 .flat_map(IntoTokens::into_tokens),
875 )
876 .context(WriteSetSnafu)?;
877
878 dset.flush().context(WriteSetSnafu)
879 }
880}
881
882#[derive(Debug)]
884pub struct FileMetaAttribute<'a> {
885 meta: &'a FileMetaTable,
886 tag_e: u16,
887}
888
889impl HasLength for FileMetaAttribute<'_> {
890 fn length(&self) -> Length {
891 match Tag(0x0002, self.tag_e) {
892 tags::FILE_META_INFORMATION_GROUP_LENGTH => Length(4),
893 tags::MEDIA_STORAGE_SOP_CLASS_UID => {
894 Length(self.meta.media_storage_sop_class_uid.len() as u32)
895 }
896 tags::MEDIA_STORAGE_SOP_INSTANCE_UID => {
897 Length(self.meta.media_storage_sop_instance_uid.len() as u32)
898 }
899 tags::IMPLEMENTATION_CLASS_UID => {
900 Length(self.meta.implementation_class_uid.len() as u32)
901 }
902 tags::IMPLEMENTATION_VERSION_NAME => Length(
903 self.meta
904 .implementation_version_name
905 .as_ref()
906 .map(|s| s.len() as u32)
907 .unwrap_or(0),
908 ),
909 tags::SOURCE_APPLICATION_ENTITY_TITLE => Length(
910 self.meta
911 .source_application_entity_title
912 .as_ref()
913 .map(|s| s.len() as u32)
914 .unwrap_or(0),
915 ),
916 tags::SENDING_APPLICATION_ENTITY_TITLE => Length(
917 self.meta
918 .sending_application_entity_title
919 .as_ref()
920 .map(|s| s.len() as u32)
921 .unwrap_or(0),
922 ),
923 tags::TRANSFER_SYNTAX_UID => Length(self.meta.transfer_syntax.len() as u32),
924 tags::PRIVATE_INFORMATION_CREATOR_UID => Length(
925 self.meta
926 .private_information_creator_uid
927 .as_ref()
928 .map(|s| s.len() as u32)
929 .unwrap_or(0),
930 ),
931 _ => unreachable!(),
932 }
933 }
934}
935
936impl DicomValueType for FileMetaAttribute<'_> {
937 fn value_type(&self) -> ValueType {
938 match Tag(0x0002, self.tag_e) {
939 tags::MEDIA_STORAGE_SOP_CLASS_UID
940 | tags::MEDIA_STORAGE_SOP_INSTANCE_UID
941 | tags::TRANSFER_SYNTAX_UID
942 | tags::IMPLEMENTATION_CLASS_UID
943 | tags::IMPLEMENTATION_VERSION_NAME
944 | tags::SOURCE_APPLICATION_ENTITY_TITLE
945 | tags::SENDING_APPLICATION_ENTITY_TITLE
946 | tags::RECEIVING_APPLICATION_ENTITY_TITLE
947 | tags::PRIVATE_INFORMATION_CREATOR_UID => ValueType::Str,
948 tags::FILE_META_INFORMATION_GROUP_LENGTH => ValueType::U32,
949 tags::FILE_META_INFORMATION_VERSION => ValueType::U8,
950 tags::PRIVATE_INFORMATION => ValueType::U8,
951 _ => unreachable!(),
952 }
953 }
954
955 fn cardinality(&self) -> usize {
956 match Tag(0x0002, self.tag_e) {
957 tags::MEDIA_STORAGE_SOP_CLASS_UID
958 | tags::MEDIA_STORAGE_SOP_INSTANCE_UID
959 | tags::SOURCE_APPLICATION_ENTITY_TITLE
960 | tags::SENDING_APPLICATION_ENTITY_TITLE
961 | tags::RECEIVING_APPLICATION_ENTITY_TITLE
962 | tags::TRANSFER_SYNTAX_UID
963 | tags::IMPLEMENTATION_CLASS_UID
964 | tags::IMPLEMENTATION_VERSION_NAME
965 | tags::PRIVATE_INFORMATION_CREATOR_UID => 1,
966 tags::FILE_META_INFORMATION_GROUP_LENGTH => 1,
967 tags::PRIVATE_INFORMATION => 1,
968 tags::FILE_META_INFORMATION_VERSION => 2,
969 _ => 1,
970 }
971 }
972}
973
974impl DicomAttribute for FileMetaAttribute<'_> {
975 type Item<'b>
976 = EmptyObject
977 where
978 Self: 'b;
979 type PixelData<'b>
980 = InMemFragment
981 where
982 Self: 'b;
983
984 fn to_primitive_value(&self) -> Result<PrimitiveValue, AttributeError> {
985 Ok(match Tag(0x0002, self.tag_e) {
986 tags::FILE_META_INFORMATION_GROUP_LENGTH => {
987 PrimitiveValue::from(self.meta.information_group_length)
988 }
989 tags::FILE_META_INFORMATION_VERSION => {
990 PrimitiveValue::from(self.meta.information_version)
991 }
992 tags::MEDIA_STORAGE_SOP_CLASS_UID => {
993 PrimitiveValue::from(self.meta.media_storage_sop_class_uid.clone())
994 }
995 tags::MEDIA_STORAGE_SOP_INSTANCE_UID => {
996 PrimitiveValue::from(self.meta.media_storage_sop_instance_uid.clone())
997 }
998 tags::SOURCE_APPLICATION_ENTITY_TITLE => {
999 PrimitiveValue::from(self.meta.source_application_entity_title.clone().unwrap())
1000 }
1001 tags::SENDING_APPLICATION_ENTITY_TITLE => {
1002 PrimitiveValue::from(self.meta.sending_application_entity_title.clone().unwrap())
1003 }
1004 tags::RECEIVING_APPLICATION_ENTITY_TITLE => PrimitiveValue::from(
1005 self.meta
1006 .receiving_application_entity_title
1007 .clone()
1008 .unwrap(),
1009 ),
1010 tags::TRANSFER_SYNTAX_UID => PrimitiveValue::from(self.meta.transfer_syntax.clone()),
1011 tags::IMPLEMENTATION_CLASS_UID => {
1012 PrimitiveValue::from(self.meta.implementation_class_uid.clone())
1013 }
1014 tags::IMPLEMENTATION_VERSION_NAME => {
1015 PrimitiveValue::from(self.meta.implementation_version_name.clone().unwrap())
1016 }
1017 tags::PRIVATE_INFORMATION_CREATOR_UID => {
1018 PrimitiveValue::from(self.meta.private_information_creator_uid.clone().unwrap())
1019 }
1020 tags::PRIVATE_INFORMATION => {
1021 PrimitiveValue::from(self.meta.private_information.clone().unwrap())
1022 }
1023 _ => unreachable!(),
1024 })
1025 }
1026
1027 fn to_str(&self) -> std::result::Result<std::borrow::Cow<'_, str>, AttributeError> {
1028 match Tag(0x0002, self.tag_e) {
1029 tags::FILE_META_INFORMATION_GROUP_LENGTH => {
1030 Ok(self.meta.information_group_length.to_string().into())
1031 }
1032 tags::FILE_META_INFORMATION_VERSION => Ok(format!(
1033 "{:02X}{:02X}",
1034 self.meta.information_version[0], self.meta.information_version[1]
1035 )
1036 .into()),
1037 tags::MEDIA_STORAGE_SOP_CLASS_UID => {
1038 Ok(Cow::Borrowed(self.meta.media_storage_sop_class_uid()))
1039 }
1040 tags::MEDIA_STORAGE_SOP_INSTANCE_UID => {
1041 Ok(Cow::Borrowed(self.meta.media_storage_sop_instance_uid()))
1042 }
1043 tags::TRANSFER_SYNTAX_UID => Ok(Cow::Borrowed(self.meta.transfer_syntax())),
1044 tags::IMPLEMENTATION_CLASS_UID => {
1045 Ok(Cow::Borrowed(self.meta.implementation_class_uid()))
1046 }
1047 tags::IMPLEMENTATION_VERSION_NAME => Ok(self
1048 .meta
1049 .implementation_version_name
1050 .as_deref()
1051 .map(Cow::Borrowed)
1052 .unwrap_or_default()),
1053 tags::SOURCE_APPLICATION_ENTITY_TITLE => Ok(self
1054 .meta
1055 .source_application_entity_title
1056 .as_deref()
1057 .map(Cow::Borrowed)
1058 .unwrap_or_default()),
1059 tags::SENDING_APPLICATION_ENTITY_TITLE => Ok(self
1060 .meta
1061 .sending_application_entity_title
1062 .as_deref()
1063 .map(Cow::Borrowed)
1064 .unwrap_or_default()),
1065 tags::RECEIVING_APPLICATION_ENTITY_TITLE => Ok(self
1066 .meta
1067 .receiving_application_entity_title
1068 .as_deref()
1069 .map(Cow::Borrowed)
1070 .unwrap_or_default()),
1071 tags::PRIVATE_INFORMATION_CREATOR_UID => Ok(self
1072 .meta
1073 .private_information_creator_uid
1074 .as_deref()
1075 .map(|v| {
1076 Cow::Borrowed(v.trim_end_matches(|c: char| c.is_whitespace() || c == '\0'))
1077 })
1078 .unwrap_or_default()),
1079 tags::PRIVATE_INFORMATION => Err(AttributeError::ConvertValue {
1080 source: ConvertValueError {
1081 cause: None,
1082 original: ValueType::U8,
1083 requested: "str",
1084 },
1085 }),
1086 _ => unreachable!(),
1087 }
1088 }
1089
1090 fn item(&self, _index: u32) -> Result<Self::Item<'_>, AttributeError> {
1091 Err(AttributeError::NotDataSet)
1092 }
1093
1094 fn num_items(&self) -> Option<u32> {
1095 None
1096 }
1097
1098 fn fragment(&self, _index: u32) -> Result<Self::PixelData<'_>, AttributeError> {
1099 Err(AttributeError::NotPixelData)
1100 }
1101
1102 fn num_fragments(&self) -> Option<u32> {
1103 None
1104 }
1105}
1106
1107impl DicomObject for FileMetaTable {
1108 type Attribute<'a>
1109 = FileMetaAttribute<'a>
1110 where
1111 Self: 'a;
1112
1113 type LeafAttribute<'a>
1114 = FileMetaAttribute<'a>
1115 where
1116 Self: 'a;
1117
1118 fn attr_opt(
1119 &self,
1120 tag: Tag,
1121 ) -> std::result::Result<Option<Self::Attribute<'_>>, crate::AccessError> {
1122 if match tag {
1126 tags::FILE_META_INFORMATION_GROUP_LENGTH
1128 | tags::FILE_META_INFORMATION_VERSION
1129 | tags::MEDIA_STORAGE_SOP_CLASS_UID
1130 | tags::MEDIA_STORAGE_SOP_INSTANCE_UID
1131 | tags::TRANSFER_SYNTAX_UID
1132 | tags::IMPLEMENTATION_CLASS_UID
1133 | tags::IMPLEMENTATION_VERSION_NAME => true,
1134 tags::SOURCE_APPLICATION_ENTITY_TITLE
1136 if self.source_application_entity_title.is_some() =>
1137 {
1138 true
1139 }
1140 tags::SENDING_APPLICATION_ENTITY_TITLE
1141 if self.sending_application_entity_title.is_some() =>
1142 {
1143 true
1144 }
1145 tags::RECEIVING_APPLICATION_ENTITY_TITLE
1146 if self.receiving_application_entity_title.is_some() =>
1147 {
1148 true
1149 }
1150 tags::PRIVATE_INFORMATION_CREATOR_UID
1151 if self.private_information_creator_uid.is_some() =>
1152 {
1153 true
1154 }
1155 tags::PRIVATE_INFORMATION if self.private_information.is_some() => true,
1156 _ => false,
1157 } {
1158 Ok(Some(FileMetaAttribute {
1159 meta: self,
1160 tag_e: tag.element(),
1161 }))
1162 } else {
1163 Ok(None)
1164 }
1165 }
1166
1167 fn attr_by_name_opt<'a>(
1168 &'a self,
1169 name: &str,
1170 ) -> std::result::Result<Option<Self::Attribute<'a>>, crate::AccessByNameError> {
1171 let tag = match name {
1172 "FileMetaInformationGroupLength" => tags::FILE_META_INFORMATION_GROUP_LENGTH,
1173 "FileMetaInformationVersion" => tags::FILE_META_INFORMATION_VERSION,
1174 "MediaStorageSOPClassUID" => tags::MEDIA_STORAGE_SOP_CLASS_UID,
1175 "MediaStorageSOPInstanceUID" => tags::MEDIA_STORAGE_SOP_INSTANCE_UID,
1176 "TransferSyntaxUID" => tags::TRANSFER_SYNTAX_UID,
1177 "ImplementationClassUID" => tags::IMPLEMENTATION_CLASS_UID,
1178 "ImplementationVersionName" => tags::IMPLEMENTATION_VERSION_NAME,
1179 "SourceApplicationEntityTitle" => tags::SOURCE_APPLICATION_ENTITY_TITLE,
1180 "SendingApplicationEntityTitle" => tags::SENDING_APPLICATION_ENTITY_TITLE,
1181 "ReceivingApplicationEntityTitle" => tags::RECEIVING_APPLICATION_ENTITY_TITLE,
1182 "PrivateInformationCreatorUID" => tags::PRIVATE_INFORMATION_CREATOR_UID,
1183 "PrivateInformation" => tags::PRIVATE_INFORMATION,
1184 _ => return Ok(None),
1185 };
1186 self.attr_opt(tag)
1187 .map_err(|_| crate::NoSuchAttributeNameSnafu { name }.build())
1188 }
1189
1190 fn at(
1191 &self,
1192 selector: impl Into<AttributeSelector>,
1193 ) -> Result<Self::LeafAttribute<'_>, crate::AtAccessError> {
1194 let selector: AttributeSelector = selector.into();
1195 match selector.split_first() {
1196 (AttributeSelectorStep::Tag(tag), None) => self
1197 .attr(tag)
1198 .map_err(|_| AtAccessError::MissingLeafElement { selector }),
1199 (_, Some(_)) => crate::NotASequenceSnafu {
1200 selector,
1201 step_index: 0_u32,
1202 }
1203 .fail(),
1204 (_, None) => unreachable!("broken invariant: nested step at end of selector"),
1205 }
1206 }
1207}
1208
1209impl ApplyOp for FileMetaTable {
1210 type Err = ApplyError;
1211
1212 fn apply(&mut self, op: AttributeOp) -> ApplyResult {
1217 self.apply(op)
1218 }
1219}
1220
1221#[derive(Debug, Default, Clone)]
1223pub struct FileMetaTableBuilder {
1224 information_group_length: Option<u32>,
1226 information_version: Option<[u8; 2]>,
1228 media_storage_sop_class_uid: Option<String>,
1230 media_storage_sop_instance_uid: Option<String>,
1232 transfer_syntax: Option<String>,
1234 implementation_class_uid: Option<String>,
1236
1237 implementation_version_name: Option<String>,
1239 source_application_entity_title: Option<String>,
1241 sending_application_entity_title: Option<String>,
1243 receiving_application_entity_title: Option<String>,
1245 private_information_creator_uid: Option<String>,
1247 private_information: Option<Vec<u8>>,
1249}
1250
1251#[inline]
1254fn padded<T>(s: T, pad: char) -> String
1255where
1256 T: Into<String>,
1257{
1258 let mut s = s.into();
1259 if s.len() % 2 == 1 {
1260 s.push(pad);
1261 }
1262 s
1263}
1264
1265fn ui_padded<T>(s: T) -> String
1267where
1268 T: Into<String>,
1269{
1270 padded(s, '\0')
1271}
1272
1273fn txt_padded<T>(s: T) -> String
1275where
1276 T: Into<String>,
1277{
1278 padded(s, ' ')
1279}
1280
1281impl FileMetaTableBuilder {
1282 pub fn new() -> FileMetaTableBuilder {
1284 FileMetaTableBuilder::default()
1285 }
1286
1287 pub fn group_length(mut self, value: u32) -> FileMetaTableBuilder {
1289 self.information_group_length = Some(value);
1290 self
1291 }
1292
1293 pub fn information_version(mut self, value: [u8; 2]) -> FileMetaTableBuilder {
1295 self.information_version = Some(value);
1296 self
1297 }
1298
1299 pub fn media_storage_sop_class_uid<T>(mut self, value: T) -> FileMetaTableBuilder
1301 where
1302 T: Into<String>,
1303 {
1304 self.media_storage_sop_class_uid = Some(ui_padded(value));
1305 self
1306 }
1307
1308 pub fn media_storage_sop_instance_uid<T>(mut self, value: T) -> FileMetaTableBuilder
1310 where
1311 T: Into<String>,
1312 {
1313 self.media_storage_sop_instance_uid = Some(ui_padded(value));
1314 self
1315 }
1316
1317 pub fn transfer_syntax<T>(mut self, value: T) -> FileMetaTableBuilder
1319 where
1320 T: Into<String>,
1321 {
1322 self.transfer_syntax = Some(ui_padded(value));
1323 self
1324 }
1325
1326 pub fn implementation_class_uid<T>(mut self, value: T) -> FileMetaTableBuilder
1328 where
1329 T: Into<String>,
1330 {
1331 self.implementation_class_uid = Some(ui_padded(value));
1332 self
1333 }
1334
1335 pub fn implementation_version_name<T>(mut self, value: T) -> FileMetaTableBuilder
1337 where
1338 T: Into<String>,
1339 {
1340 self.implementation_version_name = Some(txt_padded(value));
1341 self
1342 }
1343
1344 pub fn source_application_entity_title<T>(mut self, value: T) -> FileMetaTableBuilder
1346 where
1347 T: Into<String>,
1348 {
1349 self.source_application_entity_title = Some(txt_padded(value));
1350 self
1351 }
1352
1353 pub fn sending_application_entity_title<T>(mut self, value: T) -> FileMetaTableBuilder
1355 where
1356 T: Into<String>,
1357 {
1358 self.sending_application_entity_title = Some(txt_padded(value));
1359 self
1360 }
1361
1362 pub fn receiving_application_entity_title<T>(mut self, value: T) -> FileMetaTableBuilder
1364 where
1365 T: Into<String>,
1366 {
1367 self.receiving_application_entity_title = Some(txt_padded(value));
1368 self
1369 }
1370
1371 pub fn private_information_creator_uid<T>(mut self, value: T) -> FileMetaTableBuilder
1373 where
1374 T: Into<String>,
1375 {
1376 self.private_information_creator_uid = Some(ui_padded(value));
1377 self
1378 }
1379
1380 pub fn private_information<T>(mut self, value: T) -> FileMetaTableBuilder
1382 where
1383 T: Into<Vec<u8>>,
1384 {
1385 self.private_information = Some(value.into());
1386 self
1387 }
1388
1389 pub fn build(self) -> Result<FileMetaTable> {
1391 let information_version = self.information_version.unwrap_or(
1392 [0, 1],
1394 );
1395 let media_storage_sop_class_uid = self.media_storage_sop_class_uid.unwrap_or_else(|| {
1396 tracing::warn!("MediaStorageSOPClassUID is missing. Defaulting to empty string.");
1397 String::default()
1398 });
1399 let media_storage_sop_instance_uid =
1400 self.media_storage_sop_instance_uid.unwrap_or_else(|| {
1401 tracing::warn!(
1402 "MediaStorageSOPInstanceUID is missing. Defaulting to empty string."
1403 );
1404 String::default()
1405 });
1406 let transfer_syntax = self.transfer_syntax.context(MissingElementSnafu {
1407 alias: "TransferSyntax",
1408 })?;
1409 let mut implementation_version_name = self.implementation_version_name;
1410 let implementation_class_uid = self.implementation_class_uid.unwrap_or_else(|| {
1411 implementation_version_name = Some(IMPLEMENTATION_VERSION_NAME.to_string());
1413
1414 IMPLEMENTATION_CLASS_UID.to_string()
1415 });
1416
1417 let mut table = FileMetaTable {
1418 information_group_length: 0x00,
1420 information_version,
1421 media_storage_sop_class_uid,
1422 media_storage_sop_instance_uid,
1423 transfer_syntax,
1424 implementation_class_uid,
1425 implementation_version_name,
1426 source_application_entity_title: self.source_application_entity_title,
1427 sending_application_entity_title: self.sending_application_entity_title,
1428 receiving_application_entity_title: self.receiving_application_entity_title,
1429 private_information_creator_uid: self.private_information_creator_uid,
1430 private_information: self.private_information,
1431 };
1432 table.update_information_group_length();
1433 debug_assert!(table.information_group_length > 0);
1434 Ok(table)
1435 }
1436}
1437
1438fn dicom_len<T: AsRef<str>>(x: T) -> u32 {
1439 (x.as_ref().len() as u32 + 1) & !1
1440}
1441
1442#[cfg(test)]
1443mod tests {
1444 use crate::{IMPLEMENTATION_CLASS_UID, IMPLEMENTATION_VERSION_NAME};
1445
1446 use super::{FileMetaTable, FileMetaTableBuilder, dicom_len};
1447 use dicom_core::ops::{AttributeAction, AttributeOp};
1448 use dicom_core::value::Value;
1449 use dicom_core::{DataElement, PrimitiveValue, Tag, VR, dicom_value};
1450 use dicom_dictionary_std::tags;
1451
1452 const TEST_META_1: &[u8] = &[
1453 b'D', b'I', b'C', b'M',
1455 0x02, 0x00, 0x00, 0x00, b'U', b'L', 0x04, 0x00, 0xc8, 0x00, 0x00, 0x00,
1457 0x02, 0x00, 0x01, 0x00, b'O', b'B', 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x01,
1459 0x02, 0x00, 0x02, 0x00, b'U', b'I', 0x1a, 0x00, 0x31, 0x2e, 0x32, 0x2e, 0x38, 0x34, 0x30,
1461 0x2e, 0x31, 0x30, 0x30, 0x30, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e,
1462 0x31, 0x2e, 0x31, 0x00,
1463 0x02, 0x00, 0x03, 0x00, b'U', b'I', 0x38, 0x00, 0x31, 0x2e, 0x32, 0x2e, 0x33, 0x2e, 0x34,
1465 0x2e, 0x35, 0x2e, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x2e, 0x31, 0x32, 0x33,
1466 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x2e, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
1467 0x2e, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2e, 0x31, 0x32, 0x33, 0x34,
1468 0x35, 0x36, 0x37, 0x00,
1469 0x02, 0x00, 0x10, 0x00, b'U', b'I', 0x14, 0x00, 0x31, 0x2e, 0x32, 0x2e, 0x38, 0x34, 0x30,
1471 0x2e, 0x31, 0x30, 0x30, 0x30, 0x38, 0x2e, 0x31, 0x2e, 0x32, 0x2e, 0x31, 0x00,
1472 0x02, 0x00, 0x12, 0x00, b'U', b'I', 0x14, 0x00, 0x31, 0x2e, 0x32, 0x2e, 0x33, 0x34, 0x35,
1474 0x2e, 0x36, 0x2e, 0x37, 0x38, 0x39, 0x30, 0x2e, 0x31, 0x2e, 0x32, 0x33, 0x34,
1475 0x02, 0x00, 0x13, 0x00, b'S', b'H', 0x10, 0x00, 0x52, 0x55, 0x53, 0x54, 0x59, 0x5f, 0x44,
1479 0x49, 0x43, 0x4f, 0x4d, 0x5f, 0x32, 0x36, 0x39, 0x20,
1480 0x02, 0x00, 0x16, 0x00, b'A', b'E', 0x00, 0x00,
1482 ];
1483
1484 #[test]
1485 fn read_meta_table_from_reader() {
1486 let mut source = TEST_META_1;
1487
1488 let table = FileMetaTable::from_reader(&mut source).unwrap();
1489
1490 let gt = FileMetaTable {
1491 information_group_length: 200,
1492 information_version: [0u8, 1u8],
1493 media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.1\0".to_owned(),
1494 media_storage_sop_instance_uid:
1495 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".to_owned(),
1496 transfer_syntax: "1.2.840.10008.1.2.1\0".to_owned(),
1497 implementation_class_uid: "1.2.345.6.7890.1.234".to_owned(),
1498 implementation_version_name: Some("RUSTY_DICOM_269 ".to_owned()),
1499 source_application_entity_title: Some("".to_owned()),
1500 sending_application_entity_title: None,
1501 receiving_application_entity_title: None,
1502 private_information_creator_uid: None,
1503 private_information: None,
1504 };
1505
1506 assert_eq!(table.information_group_length, 200);
1507 assert_eq!(table.information_version, [0u8, 1u8]);
1508 assert_eq!(
1509 table.media_storage_sop_class_uid,
1510 "1.2.840.10008.5.1.4.1.1.1\0"
1511 );
1512 assert_eq!(
1513 table.media_storage_sop_instance_uid,
1514 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0"
1515 );
1516 assert_eq!(table.transfer_syntax, "1.2.840.10008.1.2.1\0");
1517 assert_eq!(table.implementation_class_uid, "1.2.345.6.7890.1.234");
1518 assert_eq!(
1519 table.implementation_version_name,
1520 Some("RUSTY_DICOM_269 ".to_owned())
1521 );
1522 assert_eq!(table.source_application_entity_title, Some("".into()));
1523 assert_eq!(table.sending_application_entity_title, None);
1524 assert_eq!(table.receiving_application_entity_title, None);
1525 assert_eq!(table.private_information_creator_uid, None);
1526 assert_eq!(table.private_information, None);
1527
1528 assert_eq!(table, gt);
1529 }
1530
1531 #[test]
1532 fn create_meta_table_with_builder() {
1533 let table = FileMetaTableBuilder::new()
1534 .information_version([0, 1])
1535 .media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.1")
1536 .media_storage_sop_instance_uid(
1537 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567",
1538 )
1539 .transfer_syntax("1.2.840.10008.1.2.1")
1540 .implementation_class_uid("1.2.345.6.7890.1.234")
1541 .implementation_version_name("RUSTY_DICOM_269")
1542 .source_application_entity_title("")
1543 .build()
1544 .unwrap();
1545
1546 let gt = FileMetaTable {
1547 information_group_length: 200,
1548 information_version: [0u8, 1u8],
1549 media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.1\0".to_owned(),
1550 media_storage_sop_instance_uid:
1551 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".to_owned(),
1552 transfer_syntax: "1.2.840.10008.1.2.1\0".to_owned(),
1553 implementation_class_uid: "1.2.345.6.7890.1.234".to_owned(),
1554 implementation_version_name: Some("RUSTY_DICOM_269 ".to_owned()),
1555 source_application_entity_title: Some("".to_owned()),
1556 sending_application_entity_title: None,
1557 receiving_application_entity_title: None,
1558 private_information_creator_uid: None,
1559 private_information: None,
1560 };
1561
1562 assert_eq!(table.information_group_length, gt.information_group_length);
1563 assert_eq!(table, gt);
1564 }
1565
1566 #[test]
1568 fn create_meta_table_with_builder_minimal() {
1569 let table = FileMetaTableBuilder::new()
1570 .media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.1")
1571 .media_storage_sop_instance_uid(
1572 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567",
1573 )
1574 .transfer_syntax("1.2.840.10008.1.2")
1575 .build()
1576 .unwrap();
1577
1578 let gt = FileMetaTable {
1579 information_group_length: 154
1580 + dicom_len(IMPLEMENTATION_CLASS_UID)
1581 + dicom_len(IMPLEMENTATION_VERSION_NAME),
1582 information_version: [0u8, 1u8],
1583 media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.1\0".to_owned(),
1584 media_storage_sop_instance_uid:
1585 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".to_owned(),
1586 transfer_syntax: "1.2.840.10008.1.2\0".to_owned(),
1587 implementation_class_uid: IMPLEMENTATION_CLASS_UID.to_owned(),
1588 implementation_version_name: Some(IMPLEMENTATION_VERSION_NAME.to_owned()),
1589 source_application_entity_title: None,
1590 sending_application_entity_title: None,
1591 receiving_application_entity_title: None,
1592 private_information_creator_uid: None,
1593 private_information: None,
1594 };
1595
1596 assert_eq!(table.information_group_length, gt.information_group_length);
1597 assert_eq!(table, gt);
1598 }
1599
1600 #[test]
1602 fn change_transfer_syntax_update_table() {
1603 let mut table = FileMetaTableBuilder::new()
1604 .media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.1")
1605 .media_storage_sop_instance_uid(
1606 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567",
1607 )
1608 .transfer_syntax("1.2.840.10008.1.2.1")
1609 .build()
1610 .unwrap();
1611
1612 assert_eq!(
1613 table.information_group_length,
1614 156 + dicom_len(IMPLEMENTATION_CLASS_UID) + dicom_len(IMPLEMENTATION_VERSION_NAME)
1615 );
1616
1617 table.set_transfer_syntax(
1618 &dicom_transfer_syntax_registry::entries::IMPLICIT_VR_LITTLE_ENDIAN,
1619 );
1620 assert_eq!(
1621 table.information_group_length,
1622 154 + dicom_len(IMPLEMENTATION_CLASS_UID) + dicom_len(IMPLEMENTATION_VERSION_NAME)
1623 );
1624 }
1625
1626 #[test]
1627 fn read_meta_table_into_iter() {
1628 let table = FileMetaTable {
1629 information_group_length: 200,
1630 information_version: [0u8, 1u8],
1631 media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.1\0".to_owned(),
1632 media_storage_sop_instance_uid:
1633 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".to_owned(),
1634 transfer_syntax: "1.2.840.10008.1.2.1\0".to_owned(),
1635 implementation_class_uid: "1.2.345.6.7890.1.234".to_owned(),
1636 implementation_version_name: Some("RUSTY_DICOM_269 ".to_owned()),
1637 source_application_entity_title: Some("".to_owned()),
1638 sending_application_entity_title: None,
1639 receiving_application_entity_title: None,
1640 private_information_creator_uid: None,
1641 private_information: None,
1642 };
1643
1644 assert_eq!(table.calculate_information_group_length(), 200);
1645
1646 let gt = vec![
1647 DataElement::new(Tag(0x0002, 0x0000), VR::UL, dicom_value!(U32, 200)),
1649 DataElement::new(Tag(0x0002, 0x0001), VR::OB, dicom_value!(U8, [0, 1])),
1651 DataElement::new(
1653 Tag(0x0002, 0x0002),
1654 VR::UI,
1655 Value::Primitive("1.2.840.10008.5.1.4.1.1.1\0".into()),
1656 ),
1657 DataElement::new(
1659 Tag(0x0002, 0x0003),
1660 VR::UI,
1661 Value::Primitive(
1662 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".into(),
1663 ),
1664 ),
1665 DataElement::new(
1667 Tag(0x0002, 0x0010),
1668 VR::UI,
1669 Value::Primitive("1.2.840.10008.1.2.1\0".into()),
1670 ),
1671 DataElement::new(
1673 Tag(0x0002, 0x0012),
1674 VR::UI,
1675 Value::Primitive("1.2.345.6.7890.1.234".into()),
1676 ),
1677 DataElement::new(
1679 Tag(0x0002, 0x0013),
1680 VR::SH,
1681 Value::Primitive("RUSTY_DICOM_269 ".into()),
1682 ),
1683 DataElement::new(Tag(0x0002, 0x0016), VR::AE, Value::Primitive("".into())),
1685 ];
1686
1687 let elems: Vec<_> = table.into_element_iter().collect();
1688 assert_eq!(elems, gt);
1689 }
1690
1691 #[test]
1693 fn meta_table_eq() {
1694 let mut table = FileMetaTable {
1695 information_group_length: 200,
1696 information_version: [0u8, 1u8],
1697 media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.1\0".to_owned(),
1698 media_storage_sop_instance_uid:
1699 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".to_owned(),
1700 transfer_syntax: "1.2.840.10008.1.2.1\0".to_owned(),
1701 implementation_class_uid: "1.2.345.6.7890.1.234".to_owned(),
1702 implementation_version_name: Some("RUSTY_DICOM_001".to_owned()),
1703 source_application_entity_title: Some("".to_owned()),
1704 sending_application_entity_title: None,
1705 receiving_application_entity_title: None,
1706 private_information_creator_uid: None,
1707 private_information: None,
1708 };
1709
1710 assert_eq!(&table, &table);
1712
1713 let mut table2 = table.clone();
1714 table2.implementation_version_name = Some("RUSTY_DICOM_002".to_owned());
1715
1716 assert_ne!(&table, &table2);
1718
1719 table2.implementation_version_name = Some("RUSTY_DICOM_001 ".to_owned());
1721 assert_eq!(&table, &table2);
1722
1723 table2.media_storage_sop_class_uid = "1.2.840.10008.5.1.4.1.1.1".to_owned();
1725 assert_eq!(&table, &table2);
1726
1727 table.private_information_creator_uid = Some("xxx".to_owned());
1729 table2.private_information_creator_uid = Some("xxx\0".to_owned());
1730 assert_eq!(&table, &table2);
1731
1732 table.private_information = Some(vec![1, 2, 5]);
1734 table2.private_information = Some(vec![1, 2, 5, 0]);
1735 assert_eq!(&table, &table2);
1736 }
1737
1738 #[test]
1739 fn update_table_with_length() {
1740 let mut table = FileMetaTable {
1741 information_group_length: 55, information_version: [0u8, 1u8],
1743 media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.1\0".to_owned(),
1744 media_storage_sop_instance_uid:
1745 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".to_owned(),
1746 transfer_syntax: "1.2.840.10008.1.2.1\0".to_owned(),
1747 implementation_class_uid: "1.2.345.6.7890.1.234".to_owned(),
1748 implementation_version_name: Some("RUSTY_DICOM_269 ".to_owned()),
1749 source_application_entity_title: Some("".to_owned()),
1750 sending_application_entity_title: None,
1751 receiving_application_entity_title: None,
1752 private_information_creator_uid: None,
1753 private_information: None,
1754 };
1755
1756 table.update_information_group_length();
1757
1758 assert_eq!(table.information_group_length, 200);
1759 }
1760
1761 #[test]
1762 fn table_ops() {
1763 let mut table = FileMetaTable {
1764 information_group_length: 200,
1765 information_version: [0u8, 1u8],
1766 media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.1\0".to_owned(),
1767 media_storage_sop_instance_uid:
1768 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".to_owned(),
1769 transfer_syntax: "1.2.840.10008.1.2.1\0".to_owned(),
1770 implementation_class_uid: "1.2.345.6.7890.1.234".to_owned(),
1771 implementation_version_name: None,
1772 source_application_entity_title: None,
1773 sending_application_entity_title: None,
1774 receiving_application_entity_title: None,
1775 private_information_creator_uid: None,
1776 private_information: None,
1777 };
1778
1779 table
1781 .apply(AttributeOp::new(
1782 tags::IMPLEMENTATION_VERSION_NAME,
1783 AttributeAction::ReplaceStr("MY_DICOM_1.1".into()),
1784 ))
1785 .unwrap();
1786
1787 assert_eq!(table.implementation_version_name, None);
1788
1789 table
1791 .apply(AttributeOp::new(
1792 tags::IMPLEMENTATION_VERSION_NAME,
1793 AttributeAction::SetStr("MY_DICOM_1.1".into()),
1794 ))
1795 .unwrap();
1796
1797 assert_eq!(
1798 table.implementation_version_name.as_deref(),
1799 Some("MY_DICOM_1.1"),
1800 );
1801
1802 table
1804 .apply(AttributeOp::new(
1805 tags::SOURCE_APPLICATION_ENTITY_TITLE,
1806 AttributeAction::Set(PrimitiveValue::Str("RICOOGLE-STORAGE".into())),
1807 ))
1808 .unwrap();
1809
1810 assert_eq!(
1811 table.source_application_entity_title.as_deref(),
1812 Some("RICOOGLE-STORAGE"),
1813 );
1814
1815 table
1817 .apply(AttributeOp::new(
1818 tags::SOURCE_APPLICATION_ENTITY_TITLE,
1819 AttributeAction::SetStrIfMissing("STORE-SCU".into()),
1820 ))
1821 .unwrap();
1822
1823 assert_eq!(
1824 table.source_application_entity_title.as_deref(),
1825 Some("RICOOGLE-STORAGE"),
1826 );
1827
1828 table
1829 .apply(AttributeOp::new(
1830 tags::SENDING_APPLICATION_ENTITY_TITLE,
1831 AttributeAction::SetStrIfMissing("STORE-SCU".into()),
1832 ))
1833 .unwrap();
1834
1835 assert_eq!(
1836 table.sending_application_entity_title.as_deref(),
1837 Some("STORE-SCU"),
1838 );
1839
1840 table
1842 .apply(AttributeOp::new(
1843 tags::MEDIA_STORAGE_SOP_CLASS_UID,
1844 AttributeAction::Replace(PrimitiveValue::Str("1.2.840.10008.5.1.4.1.1.7".into())),
1845 ))
1846 .unwrap();
1847
1848 assert_eq!(
1849 table.media_storage_sop_class_uid(),
1850 "1.2.840.10008.5.1.4.1.1.7",
1851 );
1852 }
1853
1854 #[test]
1857 fn write_read_does_not_fail() {
1858 let mut table = FileMetaTable {
1859 information_group_length: 0,
1860 information_version: [0u8, 1u8],
1861 media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.7".to_owned(),
1862 media_storage_sop_instance_uid: "2.25.137731752600317795446120660167595746868"
1863 .to_owned(),
1864 transfer_syntax: "1.2.840.10008.1.2.4.91".to_owned(),
1865 implementation_class_uid: "2.25.305828488182831875890203105390285383139".to_owned(),
1866 implementation_version_name: Some("MYTOOL100".to_owned()),
1867 source_application_entity_title: Some("RUSTY".to_owned()),
1868 receiving_application_entity_title: None,
1869 sending_application_entity_title: None,
1870 private_information_creator_uid: None,
1871 private_information: None,
1872 };
1873
1874 table.update_information_group_length();
1875
1876 let mut buf = vec![b'D', b'I', b'C', b'M'];
1877 table.write(&mut buf).unwrap();
1878
1879 let table2 = FileMetaTable::from_reader(&mut buf.as_slice())
1880 .expect("Should not fail to read the table from the written data");
1881
1882 assert_eq!(
1883 table.information_group_length,
1884 table2.information_group_length
1885 );
1886 }
1887
1888 #[test]
1890 fn dicom_object_api() {
1891 use crate::{DicomAttribute as _, DicomObject as _};
1892 use dicom_dictionary_std::uids;
1893
1894 let meta = FileMetaTableBuilder::new()
1895 .transfer_syntax(uids::RLE_LOSSLESS)
1896 .media_storage_sop_class_uid(uids::ENHANCED_MR_IMAGE_STORAGE)
1897 .media_storage_sop_instance_uid("2.25.94766187067244888884745908966163363746")
1898 .implementation_version_name("RUSTY_DICOM_269")
1899 .build()
1900 .unwrap();
1901
1902 assert_eq!(
1903 meta.attr(tags::TRANSFER_SYNTAX_UID)
1904 .unwrap()
1905 .to_str()
1906 .unwrap(),
1907 uids::RLE_LOSSLESS
1908 );
1909
1910 let sop_class_uid = meta.attr_opt(tags::MEDIA_STORAGE_SOP_CLASS_UID).unwrap();
1911 let sop_class_uid = sop_class_uid.as_ref().map(|v| v.to_str().unwrap());
1912 assert_eq!(
1913 sop_class_uid.as_deref(),
1914 Some(uids::ENHANCED_MR_IMAGE_STORAGE)
1915 );
1916
1917 assert_eq!(
1918 meta.attr_by_name("MediaStorageSOPInstanceUID")
1919 .unwrap()
1920 .to_str()
1921 .unwrap(),
1922 "2.25.94766187067244888884745908966163363746"
1923 );
1924
1925 assert!(meta.attr_opt(tags::PRIVATE_INFORMATION).unwrap().is_none());
1926 }
1927}