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> {
187 FileMetaTable::read_from(file)
188 }
189
190 pub fn transfer_syntax(&self) -> &str {
193 self.transfer_syntax
194 .trim_end_matches(|c: char| c.is_whitespace() || c == '\0')
195 }
196
197 pub fn media_storage_sop_instance_uid(&self) -> &str {
200 self.media_storage_sop_instance_uid
201 .trim_end_matches(|c: char| c.is_whitespace() || c == '\0')
202 }
203
204 pub fn media_storage_sop_class_uid(&self) -> &str {
207 self.media_storage_sop_class_uid
208 .trim_end_matches(|c: char| c.is_whitespace() || c == '\0')
209 }
210
211 pub fn implementation_class_uid(&self) -> &str {
214 self.implementation_class_uid
215 .trim_end_matches(|c: char| c.is_whitespace() || c == '\0')
216 }
217
218 pub fn private_information_creator_uid(&self) -> Option<&str> {
221 self.private_information_creator_uid
222 .as_ref()
223 .map(|s| s.trim_end_matches(|c: char| c.is_whitespace() || c == '\0'))
224 }
225
226 pub fn set_transfer_syntax<D, R, W>(&mut self, ts: &TransferSyntax<D, R, W>) {
233 self.transfer_syntax = ts
234 .uid()
235 .trim_end_matches(|c: char| c.is_whitespace() || c == '\0')
236 .to_string();
237 self.update_information_group_length();
238 }
239
240 pub fn update_information_group_length(&mut self) {
244 self.information_group_length = self.calculate_information_group_length();
245 }
246
247 fn apply(&mut self, op: AttributeOp) -> ApplyResult {
252 let AttributeSelectorStep::Tag(tag) = op.selector.first_step() else {
253 return UnsupportedAttributeSnafu.fail();
254 };
255
256 match *tag {
257 tags::TRANSFER_SYNTAX_UID => Self::apply_required_string(op, &mut self.transfer_syntax),
258 tags::MEDIA_STORAGE_SOP_CLASS_UID => {
259 Self::apply_required_string(op, &mut self.media_storage_sop_class_uid)
260 }
261 tags::MEDIA_STORAGE_SOP_INSTANCE_UID => {
262 Self::apply_required_string(op, &mut self.media_storage_sop_instance_uid)
263 }
264 tags::IMPLEMENTATION_CLASS_UID => {
265 Self::apply_required_string(op, &mut self.implementation_class_uid)
266 }
267 tags::IMPLEMENTATION_VERSION_NAME => {
268 Self::apply_optional_string(op, &mut self.implementation_version_name)
269 }
270 tags::SOURCE_APPLICATION_ENTITY_TITLE => {
271 Self::apply_optional_string(op, &mut self.source_application_entity_title)
272 }
273 tags::SENDING_APPLICATION_ENTITY_TITLE => {
274 Self::apply_optional_string(op, &mut self.sending_application_entity_title)
275 }
276 tags::RECEIVING_APPLICATION_ENTITY_TITLE => {
277 Self::apply_optional_string(op, &mut self.receiving_application_entity_title)
278 }
279 tags::PRIVATE_INFORMATION_CREATOR_UID => {
280 Self::apply_optional_string(op, &mut self.private_information_creator_uid)
281 }
282 _ if matches!(
283 op.action,
284 AttributeAction::Remove | AttributeAction::Empty | AttributeAction::Truncate(_)
285 ) =>
286 {
287 Ok(())
290 }
291 _ => UnsupportedAttributeSnafu.fail(),
292 }?;
293
294 self.update_information_group_length();
295
296 Ok(())
297 }
298
299 fn apply_required_string(op: AttributeOp, target_attribute: &mut String) -> ApplyResult {
300 match op.action {
301 AttributeAction::Remove | AttributeAction::Empty => MandatorySnafu.fail(),
302 AttributeAction::SetVr(_) | AttributeAction::Truncate(_) => {
303 Ok(())
305 }
306 AttributeAction::Set(value) | AttributeAction::Replace(value) => {
307 if let Ok(value) = value.string() {
309 *target_attribute = value.to_string();
310 Ok(())
311 } else {
312 IncompatibleTypesSnafu {
313 kind: ValueType::Str,
314 }
315 .fail()
316 }
317 }
318 AttributeAction::SetStr(string) | AttributeAction::ReplaceStr(string) => {
319 *target_attribute = string.to_string();
320 Ok(())
321 }
322 AttributeAction::SetIfMissing(_) | AttributeAction::SetStrIfMissing(_) => {
323 Ok(())
325 }
326 AttributeAction::PushStr(_) => IllegalExtendSnafu.fail(),
327 AttributeAction::PushI32(_)
328 | AttributeAction::PushU32(_)
329 | AttributeAction::PushI16(_)
330 | AttributeAction::PushU16(_)
331 | AttributeAction::PushF32(_)
332 | AttributeAction::PushF64(_) => IncompatibleTypesSnafu {
333 kind: ValueType::Str,
334 }
335 .fail(),
336 _ => UnsupportedActionSnafu.fail(),
337 }
338 }
339
340 fn apply_optional_string(
341 op: AttributeOp,
342 target_attribute: &mut Option<String>,
343 ) -> ApplyResult {
344 match op.action {
345 AttributeAction::Remove => {
346 target_attribute.take();
347 Ok(())
348 }
349 AttributeAction::Empty => {
350 if let Some(s) = target_attribute.as_mut() {
351 s.clear();
352 }
353 Ok(())
354 }
355 AttributeAction::SetVr(_) => {
356 Ok(())
358 }
359 AttributeAction::Set(value) => {
360 if let Ok(value) = value.string() {
362 *target_attribute = Some(value.to_string());
363 Ok(())
364 } else {
365 IncompatibleTypesSnafu {
366 kind: ValueType::Str,
367 }
368 .fail()
369 }
370 }
371 AttributeAction::SetStr(value) => {
372 *target_attribute = Some(value.to_string());
373 Ok(())
374 }
375 AttributeAction::SetIfMissing(value) => {
376 if target_attribute.is_some() {
377 return Ok(());
378 }
379
380 if let Ok(value) = value.string() {
382 *target_attribute = Some(value.to_string());
383 Ok(())
384 } else {
385 IncompatibleTypesSnafu {
386 kind: ValueType::Str,
387 }
388 .fail()
389 }
390 }
391 AttributeAction::SetStrIfMissing(value) => {
392 if target_attribute.is_none() {
393 *target_attribute = Some(value.to_string());
394 }
395 Ok(())
396 }
397 AttributeAction::Replace(value) => {
398 if target_attribute.is_none() {
399 return Ok(());
400 }
401
402 if let Ok(value) = value.string() {
404 *target_attribute = Some(value.to_string());
405 Ok(())
406 } else {
407 IncompatibleTypesSnafu {
408 kind: ValueType::Str,
409 }
410 .fail()
411 }
412 }
413 AttributeAction::ReplaceStr(value) => {
414 if target_attribute.is_some() {
415 *target_attribute = Some(value.to_string());
416 }
417 Ok(())
418 }
419 AttributeAction::PushStr(_) => IllegalExtendSnafu.fail(),
420 AttributeAction::PushI32(_)
421 | AttributeAction::PushU32(_)
422 | AttributeAction::PushI16(_)
423 | AttributeAction::PushU16(_)
424 | AttributeAction::PushF32(_)
425 | AttributeAction::PushF64(_) => IncompatibleTypesSnafu {
426 kind: ValueType::Str,
427 }
428 .fail(),
429 _ => UnsupportedActionSnafu.fail(),
430 }
431 }
432
433 fn calculate_information_group_length(&self) -> u32 {
436 14 + 8
440 + dicom_len(&self.media_storage_sop_class_uid)
441 + 8
442 + dicom_len(&self.media_storage_sop_instance_uid)
443 + 8
444 + dicom_len(&self.transfer_syntax)
445 + 8
446 + dicom_len(&self.implementation_class_uid)
447 + self
448 .implementation_version_name
449 .as_ref()
450 .map(|s| 8 + dicom_len(s))
451 .unwrap_or(0)
452 + self
453 .source_application_entity_title
454 .as_ref()
455 .map(|s| 8 + dicom_len(s))
456 .unwrap_or(0)
457 + self
458 .sending_application_entity_title
459 .as_ref()
460 .map(|s| 8 + dicom_len(s))
461 .unwrap_or(0)
462 + self
463 .receiving_application_entity_title
464 .as_ref()
465 .map(|s| 8 + dicom_len(s))
466 .unwrap_or(0)
467 + self
468 .private_information_creator_uid
469 .as_ref()
470 .map(|s| 8 + dicom_len(s))
471 .unwrap_or(0)
472 + self
473 .private_information
474 .as_ref()
475 .map(|x| 12 + ((x.len() as u32 + 1) & !1))
476 .unwrap_or(0)
477 }
478
479 fn read_from<S: Read>(mut file: S) -> Result<Self> {
480 let mut buff: [u8; 4] = [0; 4];
481 {
482 file.read_exact(&mut buff).context(ReadMagicCodeSnafu)?;
484
485 ensure!(buff == DICM_MAGIC_CODE, NotDicomSnafu);
486 }
487
488 let decoder = decode::file_header_decoder();
489 let text = text::DefaultCharacterSetCodec;
490
491 let builder = FileMetaTableBuilder::new();
492
493 let group_length: u32 = {
494 let (elem, _bytes_read) = decoder
495 .decode_header(&mut file)
496 .context(DecodeElementSnafu)?;
497 if elem.tag() != Tag(0x0002, 0x0000) {
498 return UnexpectedTagSnafu { tag: elem.tag() }.fail();
499 }
500 if elem.length() != Length(4) {
501 return UnexpectedDataValueLengthSnafu {
502 tag: elem.tag(),
503 length: elem.length(),
504 }
505 .fail();
506 }
507 let mut buff: [u8; 4] = [0; 4];
508 file.read_exact(&mut buff).context(ReadValueDataSnafu)?;
509 LittleEndian::read_u32(&buff)
510 };
511
512 let mut total_bytes_read = 0;
513 let mut builder = builder.group_length(group_length);
514
515 while total_bytes_read < group_length {
517 let (elem, header_bytes_read) = decoder
518 .decode_header(&mut file)
519 .context(DecodeElementSnafu)?;
520 let elem_len = match elem.length().get() {
521 None => {
522 return UndefinedValueLengthSnafu { tag: elem.tag() }.fail();
523 }
524 Some(len) => len,
525 };
526 builder = match elem.tag() {
527 Tag(0x0002, 0x0001) => {
528 if elem.length() != Length(2) {
530 return UnexpectedDataValueLengthSnafu {
531 tag: elem.tag(),
532 length: elem.length(),
533 }
534 .fail();
535 }
536 let mut hbuf = [0u8; 2];
537 file.read_exact(&mut hbuf[..]).context(ReadValueDataSnafu)?;
538
539 builder.information_version(hbuf)
540 }
541 Tag(0x0002, 0x0002) => {
543 builder.media_storage_sop_class_uid(read_str_body(&mut file, &text, elem_len)?)
544 }
545 Tag(0x0002, 0x0003) => builder
547 .media_storage_sop_instance_uid(read_str_body(&mut file, &text, elem_len)?),
548 Tag(0x0002, 0x0010) => {
550 builder.transfer_syntax(read_str_body(&mut file, &text, elem_len)?)
551 }
552 Tag(0x0002, 0x0012) => {
554 builder.implementation_class_uid(read_str_body(&mut file, &text, elem_len)?)
555 }
556 Tag(0x0002, 0x0013) => {
557 let mut v = Vec::new();
559 v.try_reserve_exact(elem_len as usize)
560 .context(AllocationSizeSnafu)?;
561 v.resize(elem_len as usize, 0);
562 file.read_exact(&mut v).context(ReadValueDataSnafu)?;
563
564 builder.implementation_version_name(
565 text.decode(&v)
566 .context(DecodeTextSnafu { name: text.name() })?,
567 )
568 }
569 Tag(0x0002, 0x0016) => {
570 let mut v = Vec::new();
572 v.try_reserve_exact(elem_len as usize)
573 .context(AllocationSizeSnafu)?;
574 v.resize(elem_len as usize, 0);
575 file.read_exact(&mut v).context(ReadValueDataSnafu)?;
576
577 builder.source_application_entity_title(
578 text.decode(&v)
579 .context(DecodeTextSnafu { name: text.name() })?,
580 )
581 }
582 Tag(0x0002, 0x0017) => {
583 let mut v = Vec::new();
585 v.try_reserve_exact(elem_len as usize)
586 .context(AllocationSizeSnafu)?;
587 v.resize(elem_len as usize, 0);
588 file.read_exact(&mut v).context(ReadValueDataSnafu)?;
589
590 builder.sending_application_entity_title(
591 text.decode(&v)
592 .context(DecodeTextSnafu { name: text.name() })?,
593 )
594 }
595 Tag(0x0002, 0x0018) => {
596 let mut v = Vec::new();
598 v.try_reserve_exact(elem_len as usize)
599 .context(AllocationSizeSnafu)?;
600 v.resize(elem_len as usize, 0);
601 file.read_exact(&mut v).context(ReadValueDataSnafu)?;
602
603 builder.receiving_application_entity_title(
604 text.decode(&v)
605 .context(DecodeTextSnafu { name: text.name() })?,
606 )
607 }
608 Tag(0x0002, 0x0100) => {
609 let mut v = Vec::new();
611 v.try_reserve_exact(elem_len as usize)
612 .context(AllocationSizeSnafu)?;
613 v.resize(elem_len as usize, 0);
614 file.read_exact(&mut v).context(ReadValueDataSnafu)?;
615
616 builder.private_information_creator_uid(
617 text.decode(&v)
618 .context(DecodeTextSnafu { name: text.name() })?,
619 )
620 }
621 Tag(0x0002, 0x0102) => {
622 let mut v = Vec::new();
624 v.try_reserve_exact(elem_len as usize)
625 .context(AllocationSizeSnafu)?;
626 v.resize(elem_len as usize, 0);
627 file.read_exact(&mut v).context(ReadValueDataSnafu)?;
628
629 builder.private_information(v)
630 }
631 tag @ Tag(0x0002, _) => {
632 tracing::info!("Unknown tag {}", tag);
635 let bytes_read =
637 std::io::copy(&mut (&mut file).take(elem_len as u64), &mut std::io::sink())
638 .context(ReadValueDataSnafu)?;
639 if bytes_read != elem_len as u64 {
640 return UnexpectedDataValueLengthSnafu {
642 tag: elem.tag(),
643 length: elem_len,
644 }
645 .fail();
646 }
647 builder
648 }
649 tag => {
650 tracing::warn!("Unexpected off-group tag {}", tag);
653 let bytes_read =
655 std::io::copy(&mut (&mut file).take(elem_len as u64), &mut std::io::sink())
656 .context(ReadValueDataSnafu)?;
657 if bytes_read != elem_len as u64 {
658 return UnexpectedDataValueLengthSnafu {
660 tag: elem.tag(),
661 length: elem_len,
662 }
663 .fail();
664 }
665 builder
666 }
667 };
668 total_bytes_read = total_bytes_read
669 .saturating_add(header_bytes_read as u32)
670 .saturating_add(elem_len);
671 }
672
673 builder.build()
674 }
675
676 pub fn into_element_iter(self) -> impl Iterator<Item = DataElement<EmptyObject, [u8; 0]>> {
683 let mut elems = vec![
684 DataElement::new(
686 Tag(0x0002, 0x0000),
687 VR::UL,
688 Value::Primitive(self.information_group_length.into()),
689 ),
690 DataElement::new(
691 Tag(0x0002, 0x0001),
692 VR::OB,
693 Value::Primitive(dicom_value!(
694 U8,
695 [self.information_version[0], self.information_version[1]]
696 )),
697 ),
698 DataElement::new(
699 Tag(0x0002, 0x0002),
700 VR::UI,
701 Value::Primitive(self.media_storage_sop_class_uid.into()),
702 ),
703 DataElement::new(
704 Tag(0x0002, 0x0003),
705 VR::UI,
706 Value::Primitive(self.media_storage_sop_instance_uid.into()),
707 ),
708 DataElement::new(
709 Tag(0x0002, 0x0010),
710 VR::UI,
711 Value::Primitive(self.transfer_syntax.into()),
712 ),
713 DataElement::new(
714 Tag(0x0002, 0x0012),
715 VR::UI,
716 Value::Primitive(self.implementation_class_uid.into()),
717 ),
718 ];
719 if let Some(v) = self.implementation_version_name {
720 elems.push(DataElement::new(
721 Tag(0x0002, 0x0013),
722 VR::SH,
723 Value::Primitive(v.into()),
724 ));
725 }
726 if let Some(v) = self.source_application_entity_title {
727 elems.push(DataElement::new(
728 Tag(0x0002, 0x0016),
729 VR::AE,
730 Value::Primitive(v.into()),
731 ));
732 }
733 if let Some(v) = self.sending_application_entity_title {
734 elems.push(DataElement::new(
735 Tag(0x0002, 0x0017),
736 VR::AE,
737 Value::Primitive(v.into()),
738 ));
739 }
740 if let Some(v) = self.receiving_application_entity_title {
741 elems.push(DataElement::new(
742 Tag(0x0002, 0x0018),
743 VR::AE,
744 Value::Primitive(v.into()),
745 ));
746 }
747 if let Some(v) = self.private_information_creator_uid {
748 elems.push(DataElement::new(
749 Tag(0x0002, 0x0100),
750 VR::UI,
751 Value::Primitive(v.into()),
752 ));
753 }
754 if let Some(v) = self.private_information {
755 elems.push(DataElement::new(
756 Tag(0x0002, 0x0102),
757 VR::OB,
758 Value::Primitive(PrimitiveValue::U8(v.into())),
759 ));
760 }
761
762 elems.into_iter()
763 }
764
765 pub fn to_element_iter(&self) -> impl Iterator<Item = DataElement<EmptyObject, [u8; 0]>> + '_ {
770 self.clone().into_element_iter()
771 }
772
773 pub fn write<W: Write>(&self, writer: W) -> Result<()> {
774 let mut dset = DataSetWriter::new(
775 writer,
776 EncoderFor::new(ExplicitVRLittleEndianEncoder::default()),
777 );
778 dset.write_sequence(
781 self.clone()
782 .into_element_iter()
783 .flat_map(IntoTokens::into_tokens),
784 )
785 .context(WriteSetSnafu)
786 }
787}
788
789impl ApplyOp for FileMetaTable {
790 type Err = ApplyError;
791
792 fn apply(&mut self, op: AttributeOp) -> ApplyResult {
797 self.apply(op)
798 }
799}
800
801#[derive(Debug, Default, Clone)]
803pub struct FileMetaTableBuilder {
804 information_group_length: Option<u32>,
806 information_version: Option<[u8; 2]>,
808 media_storage_sop_class_uid: Option<String>,
810 media_storage_sop_instance_uid: Option<String>,
812 transfer_syntax: Option<String>,
814 implementation_class_uid: Option<String>,
816
817 implementation_version_name: Option<String>,
819 source_application_entity_title: Option<String>,
821 sending_application_entity_title: Option<String>,
823 receiving_application_entity_title: Option<String>,
825 private_information_creator_uid: Option<String>,
827 private_information: Option<Vec<u8>>,
829}
830
831#[inline]
834fn padded<T>(s: T, pad: char) -> String
835where
836 T: Into<String>,
837{
838 let mut s = s.into();
839 if s.len() % 2 == 1 {
840 s.push(pad);
841 }
842 s
843}
844
845fn ui_padded<T>(s: T) -> String
847where
848 T: Into<String>,
849{
850 padded(s, '\0')
851}
852
853fn txt_padded<T>(s: T) -> String
855where
856 T: Into<String>,
857{
858 padded(s, ' ')
859}
860
861impl FileMetaTableBuilder {
862 pub fn new() -> FileMetaTableBuilder {
864 FileMetaTableBuilder::default()
865 }
866
867 pub fn group_length(mut self, value: u32) -> FileMetaTableBuilder {
869 self.information_group_length = Some(value);
870 self
871 }
872
873 pub fn information_version(mut self, value: [u8; 2]) -> FileMetaTableBuilder {
875 self.information_version = Some(value);
876 self
877 }
878
879 pub fn media_storage_sop_class_uid<T>(mut self, value: T) -> FileMetaTableBuilder
881 where
882 T: Into<String>,
883 {
884 self.media_storage_sop_class_uid = Some(ui_padded(value));
885 self
886 }
887
888 pub fn media_storage_sop_instance_uid<T>(mut self, value: T) -> FileMetaTableBuilder
890 where
891 T: Into<String>,
892 {
893 self.media_storage_sop_instance_uid = Some(ui_padded(value));
894 self
895 }
896
897 pub fn transfer_syntax<T>(mut self, value: T) -> FileMetaTableBuilder
899 where
900 T: Into<String>,
901 {
902 self.transfer_syntax = Some(ui_padded(value));
903 self
904 }
905
906 pub fn implementation_class_uid<T>(mut self, value: T) -> FileMetaTableBuilder
908 where
909 T: Into<String>,
910 {
911 self.implementation_class_uid = Some(ui_padded(value));
912 self
913 }
914
915 pub fn implementation_version_name<T>(mut self, value: T) -> FileMetaTableBuilder
917 where
918 T: Into<String>,
919 {
920 self.implementation_version_name = Some(txt_padded(value));
921 self
922 }
923
924 pub fn source_application_entity_title<T>(mut self, value: T) -> FileMetaTableBuilder
926 where
927 T: Into<String>,
928 {
929 self.source_application_entity_title = Some(txt_padded(value));
930 self
931 }
932
933 pub fn sending_application_entity_title<T>(mut self, value: T) -> FileMetaTableBuilder
935 where
936 T: Into<String>,
937 {
938 self.sending_application_entity_title = Some(txt_padded(value));
939 self
940 }
941
942 pub fn receiving_application_entity_title<T>(mut self, value: T) -> FileMetaTableBuilder
944 where
945 T: Into<String>,
946 {
947 self.receiving_application_entity_title = Some(txt_padded(value));
948 self
949 }
950
951 pub fn private_information_creator_uid<T>(mut self, value: T) -> FileMetaTableBuilder
953 where
954 T: Into<String>,
955 {
956 self.private_information_creator_uid = Some(ui_padded(value));
957 self
958 }
959
960 pub fn private_information<T>(mut self, value: T) -> FileMetaTableBuilder
962 where
963 T: Into<Vec<u8>>,
964 {
965 self.private_information = Some(value.into());
966 self
967 }
968
969 pub fn build(self) -> Result<FileMetaTable> {
971 let information_version = self.information_version.unwrap_or(
972 [0, 1],
974 );
975 let media_storage_sop_class_uid = self.media_storage_sop_class_uid.unwrap_or_else(|| {
976 tracing::warn!("MediaStorageSOPClassUID is missing. Defaulting to empty string.");
977 String::default()
978 });
979 let media_storage_sop_instance_uid =
980 self.media_storage_sop_instance_uid.unwrap_or_else(|| {
981 tracing::warn!(
982 "MediaStorageSOPInstanceUID is missing. Defaulting to empty string."
983 );
984 String::default()
985 });
986 let transfer_syntax = self.transfer_syntax.context(MissingElementSnafu {
987 alias: "TransferSyntax",
988 })?;
989 let mut implementation_version_name = self.implementation_version_name;
990 let implementation_class_uid = self.implementation_class_uid.unwrap_or_else(|| {
991 implementation_version_name = Some(IMPLEMENTATION_VERSION_NAME.to_string());
993
994 IMPLEMENTATION_CLASS_UID.to_string()
995 });
996
997 let mut table = FileMetaTable {
998 information_group_length: 0x00,
1000 information_version,
1001 media_storage_sop_class_uid,
1002 media_storage_sop_instance_uid,
1003 transfer_syntax,
1004 implementation_class_uid,
1005 implementation_version_name,
1006 source_application_entity_title: self.source_application_entity_title,
1007 sending_application_entity_title: self.sending_application_entity_title,
1008 receiving_application_entity_title: self.receiving_application_entity_title,
1009 private_information_creator_uid: self.private_information_creator_uid,
1010 private_information: self.private_information,
1011 };
1012 table.update_information_group_length();
1013 debug_assert!(table.information_group_length > 0);
1014 Ok(table)
1015 }
1016}
1017
1018fn dicom_len<T: AsRef<str>>(x: T) -> u32 {
1019 (x.as_ref().len() as u32 + 1) & !1
1020}
1021
1022#[cfg(test)]
1023mod tests {
1024 use crate::{IMPLEMENTATION_CLASS_UID, IMPLEMENTATION_VERSION_NAME};
1025
1026 use super::{dicom_len, FileMetaTable, FileMetaTableBuilder};
1027 use dicom_core::ops::{AttributeAction, AttributeOp};
1028 use dicom_core::value::Value;
1029 use dicom_core::{dicom_value, DataElement, PrimitiveValue, Tag, VR};
1030 use dicom_dictionary_std::tags;
1031
1032 const TEST_META_1: &'static [u8] = &[
1033 b'D', b'I', b'C', b'M',
1035 0x02, 0x00, 0x00, 0x00, b'U', b'L', 0x04, 0x00, 0xc8, 0x00, 0x00, 0x00,
1037 0x02, 0x00, 0x01, 0x00, b'O', b'B', 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x01,
1039 0x02, 0x00, 0x02, 0x00, b'U', b'I', 0x1a, 0x00, 0x31, 0x2e, 0x32, 0x2e, 0x38, 0x34, 0x30,
1041 0x2e, 0x31, 0x30, 0x30, 0x30, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e,
1042 0x31, 0x2e, 0x31, 0x00,
1043 0x02, 0x00, 0x03, 0x00, b'U', b'I', 0x38, 0x00, 0x31, 0x2e, 0x32, 0x2e, 0x33, 0x2e, 0x34,
1045 0x2e, 0x35, 0x2e, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x2e, 0x31, 0x32, 0x33,
1046 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x2e, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
1047 0x2e, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2e, 0x31, 0x32, 0x33, 0x34,
1048 0x35, 0x36, 0x37, 0x00,
1049 0x02, 0x00, 0x10, 0x00, b'U', b'I', 0x14, 0x00, 0x31, 0x2e, 0x32, 0x2e, 0x38, 0x34, 0x30,
1051 0x2e, 0x31, 0x30, 0x30, 0x30, 0x38, 0x2e, 0x31, 0x2e, 0x32, 0x2e, 0x31, 0x00,
1052 0x02, 0x00, 0x12, 0x00, b'U', b'I', 0x14, 0x00, 0x31, 0x2e, 0x32, 0x2e, 0x33, 0x34, 0x35,
1054 0x2e, 0x36, 0x2e, 0x37, 0x38, 0x39, 0x30, 0x2e, 0x31, 0x2e, 0x32, 0x33, 0x34,
1055 0x02, 0x00, 0x13, 0x00, b'S', b'H', 0x10, 0x00, 0x52, 0x55, 0x53, 0x54, 0x59, 0x5f, 0x44,
1059 0x49, 0x43, 0x4f, 0x4d, 0x5f, 0x32, 0x36, 0x39, 0x20,
1060 0x02, 0x00, 0x16, 0x00, b'A', b'E', 0x00, 0x00,
1062 ];
1063
1064 #[test]
1065 fn read_meta_table_from_reader() {
1066 let mut source = TEST_META_1;
1067
1068 let table = FileMetaTable::from_reader(&mut source).unwrap();
1069
1070 let gt = FileMetaTable {
1071 information_group_length: 200,
1072 information_version: [0u8, 1u8],
1073 media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.1\0".to_owned(),
1074 media_storage_sop_instance_uid:
1075 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".to_owned(),
1076 transfer_syntax: "1.2.840.10008.1.2.1\0".to_owned(),
1077 implementation_class_uid: "1.2.345.6.7890.1.234".to_owned(),
1078 implementation_version_name: Some("RUSTY_DICOM_269 ".to_owned()),
1079 source_application_entity_title: Some("".to_owned()),
1080 sending_application_entity_title: None,
1081 receiving_application_entity_title: None,
1082 private_information_creator_uid: None,
1083 private_information: None,
1084 };
1085
1086 assert_eq!(table.information_group_length, 200);
1087 assert_eq!(table.information_version, [0u8, 1u8]);
1088 assert_eq!(
1089 table.media_storage_sop_class_uid,
1090 "1.2.840.10008.5.1.4.1.1.1\0"
1091 );
1092 assert_eq!(
1093 table.media_storage_sop_instance_uid,
1094 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0"
1095 );
1096 assert_eq!(table.transfer_syntax, "1.2.840.10008.1.2.1\0");
1097 assert_eq!(table.implementation_class_uid, "1.2.345.6.7890.1.234");
1098 assert_eq!(
1099 table.implementation_version_name,
1100 Some("RUSTY_DICOM_269 ".to_owned())
1101 );
1102 assert_eq!(table.source_application_entity_title, Some("".into()));
1103 assert_eq!(table.sending_application_entity_title, None);
1104 assert_eq!(table.receiving_application_entity_title, None);
1105 assert_eq!(table.private_information_creator_uid, None);
1106 assert_eq!(table.private_information, None);
1107
1108 assert_eq!(table, gt);
1109 }
1110
1111 #[test]
1112 fn create_meta_table_with_builder() {
1113 let table = FileMetaTableBuilder::new()
1114 .information_version([0, 1])
1115 .media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.1")
1116 .media_storage_sop_instance_uid(
1117 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567",
1118 )
1119 .transfer_syntax("1.2.840.10008.1.2.1")
1120 .implementation_class_uid("1.2.345.6.7890.1.234")
1121 .implementation_version_name("RUSTY_DICOM_269")
1122 .source_application_entity_title("")
1123 .build()
1124 .unwrap();
1125
1126 let gt = FileMetaTable {
1127 information_group_length: 200,
1128 information_version: [0u8, 1u8],
1129 media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.1\0".to_owned(),
1130 media_storage_sop_instance_uid:
1131 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".to_owned(),
1132 transfer_syntax: "1.2.840.10008.1.2.1\0".to_owned(),
1133 implementation_class_uid: "1.2.345.6.7890.1.234".to_owned(),
1134 implementation_version_name: Some("RUSTY_DICOM_269 ".to_owned()),
1135 source_application_entity_title: Some("".to_owned()),
1136 sending_application_entity_title: None,
1137 receiving_application_entity_title: None,
1138 private_information_creator_uid: None,
1139 private_information: None,
1140 };
1141
1142 assert_eq!(table.information_group_length, gt.information_group_length);
1143 assert_eq!(table, gt);
1144 }
1145
1146 #[test]
1148 fn create_meta_table_with_builder_minimal() {
1149 let table = FileMetaTableBuilder::new()
1150 .media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.1")
1151 .media_storage_sop_instance_uid(
1152 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567",
1153 )
1154 .transfer_syntax("1.2.840.10008.1.2")
1155 .build()
1156 .unwrap();
1157
1158 let gt = FileMetaTable {
1159 information_group_length: 154
1160 + dicom_len(IMPLEMENTATION_CLASS_UID)
1161 + dicom_len(IMPLEMENTATION_VERSION_NAME),
1162 information_version: [0u8, 1u8],
1163 media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.1\0".to_owned(),
1164 media_storage_sop_instance_uid:
1165 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".to_owned(),
1166 transfer_syntax: "1.2.840.10008.1.2\0".to_owned(),
1167 implementation_class_uid: IMPLEMENTATION_CLASS_UID.to_owned(),
1168 implementation_version_name: Some(IMPLEMENTATION_VERSION_NAME.to_owned()),
1169 source_application_entity_title: None,
1170 sending_application_entity_title: None,
1171 receiving_application_entity_title: None,
1172 private_information_creator_uid: None,
1173 private_information: None,
1174 };
1175
1176 assert_eq!(table.information_group_length, gt.information_group_length);
1177 assert_eq!(table, gt);
1178 }
1179
1180 #[test]
1182 fn change_transfer_syntax_update_table() {
1183 let mut table = FileMetaTableBuilder::new()
1184 .media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.1")
1185 .media_storage_sop_instance_uid(
1186 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567",
1187 )
1188 .transfer_syntax("1.2.840.10008.1.2.1")
1189 .build()
1190 .unwrap();
1191
1192 assert_eq!(
1193 table.information_group_length,
1194 156 + dicom_len(IMPLEMENTATION_CLASS_UID) + dicom_len(IMPLEMENTATION_VERSION_NAME)
1195 );
1196
1197 table.set_transfer_syntax(
1198 &dicom_transfer_syntax_registry::entries::IMPLICIT_VR_LITTLE_ENDIAN,
1199 );
1200 assert_eq!(
1201 table.information_group_length,
1202 154 + dicom_len(IMPLEMENTATION_CLASS_UID) + dicom_len(IMPLEMENTATION_VERSION_NAME)
1203 );
1204 }
1205
1206 #[test]
1207 fn read_meta_table_into_iter() {
1208 let table = FileMetaTable {
1209 information_group_length: 200,
1210 information_version: [0u8, 1u8],
1211 media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.1\0".to_owned(),
1212 media_storage_sop_instance_uid:
1213 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".to_owned(),
1214 transfer_syntax: "1.2.840.10008.1.2.1\0".to_owned(),
1215 implementation_class_uid: "1.2.345.6.7890.1.234".to_owned(),
1216 implementation_version_name: Some("RUSTY_DICOM_269 ".to_owned()),
1217 source_application_entity_title: Some("".to_owned()),
1218 sending_application_entity_title: None,
1219 receiving_application_entity_title: None,
1220 private_information_creator_uid: None,
1221 private_information: None,
1222 };
1223
1224 assert_eq!(table.calculate_information_group_length(), 200);
1225
1226 let gt = vec![
1227 DataElement::new(Tag(0x0002, 0x0000), VR::UL, dicom_value!(U32, 200)),
1229 DataElement::new(Tag(0x0002, 0x0001), VR::OB, dicom_value!(U8, [0, 1])),
1231 DataElement::new(
1233 Tag(0x0002, 0x0002),
1234 VR::UI,
1235 Value::Primitive("1.2.840.10008.5.1.4.1.1.1\0".into()),
1236 ),
1237 DataElement::new(
1239 Tag(0x0002, 0x0003),
1240 VR::UI,
1241 Value::Primitive(
1242 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".into(),
1243 ),
1244 ),
1245 DataElement::new(
1247 Tag(0x0002, 0x0010),
1248 VR::UI,
1249 Value::Primitive("1.2.840.10008.1.2.1\0".into()),
1250 ),
1251 DataElement::new(
1253 Tag(0x0002, 0x0012),
1254 VR::UI,
1255 Value::Primitive("1.2.345.6.7890.1.234".into()),
1256 ),
1257 DataElement::new(
1259 Tag(0x0002, 0x0013),
1260 VR::SH,
1261 Value::Primitive("RUSTY_DICOM_269 ".into()),
1262 ),
1263 DataElement::new(Tag(0x0002, 0x0016), VR::AE, Value::Primitive("".into())),
1265 ];
1266
1267 let elems: Vec<_> = table.into_element_iter().collect();
1268 assert_eq!(elems, gt);
1269 }
1270
1271 #[test]
1272 fn update_table_with_length() {
1273 let mut table = FileMetaTable {
1274 information_group_length: 55, information_version: [0u8, 1u8],
1276 media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.1\0".to_owned(),
1277 media_storage_sop_instance_uid:
1278 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".to_owned(),
1279 transfer_syntax: "1.2.840.10008.1.2.1\0".to_owned(),
1280 implementation_class_uid: "1.2.345.6.7890.1.234".to_owned(),
1281 implementation_version_name: Some("RUSTY_DICOM_269 ".to_owned()),
1282 source_application_entity_title: Some("".to_owned()),
1283 sending_application_entity_title: None,
1284 receiving_application_entity_title: None,
1285 private_information_creator_uid: None,
1286 private_information: None,
1287 };
1288
1289 table.update_information_group_length();
1290
1291 assert_eq!(table.information_group_length, 200);
1292 }
1293
1294 #[test]
1295 fn table_ops() {
1296 let mut table = FileMetaTable {
1297 information_group_length: 200,
1298 information_version: [0u8, 1u8],
1299 media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.1\0".to_owned(),
1300 media_storage_sop_instance_uid:
1301 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".to_owned(),
1302 transfer_syntax: "1.2.840.10008.1.2.1\0".to_owned(),
1303 implementation_class_uid: "1.2.345.6.7890.1.234".to_owned(),
1304 implementation_version_name: None,
1305 source_application_entity_title: None,
1306 sending_application_entity_title: None,
1307 receiving_application_entity_title: None,
1308 private_information_creator_uid: None,
1309 private_information: None,
1310 };
1311
1312 table
1314 .apply(AttributeOp::new(
1315 tags::IMPLEMENTATION_VERSION_NAME,
1316 AttributeAction::ReplaceStr("MY_DICOM_1.1".into()),
1317 ))
1318 .unwrap();
1319
1320 assert_eq!(table.implementation_version_name, None);
1321
1322 table
1324 .apply(AttributeOp::new(
1325 tags::IMPLEMENTATION_VERSION_NAME,
1326 AttributeAction::SetStr("MY_DICOM_1.1".into()),
1327 ))
1328 .unwrap();
1329
1330 assert_eq!(
1331 table.implementation_version_name.as_deref(),
1332 Some("MY_DICOM_1.1"),
1333 );
1334
1335 table
1337 .apply(AttributeOp::new(
1338 tags::SOURCE_APPLICATION_ENTITY_TITLE,
1339 AttributeAction::Set(PrimitiveValue::Str("RICOOGLE-STORAGE".into())),
1340 ))
1341 .unwrap();
1342
1343 assert_eq!(
1344 table.source_application_entity_title.as_deref(),
1345 Some("RICOOGLE-STORAGE"),
1346 );
1347
1348 table
1350 .apply(AttributeOp::new(
1351 tags::SOURCE_APPLICATION_ENTITY_TITLE,
1352 AttributeAction::SetStrIfMissing("STORE-SCU".into()),
1353 ))
1354 .unwrap();
1355
1356 assert_eq!(
1357 table.source_application_entity_title.as_deref(),
1358 Some("RICOOGLE-STORAGE"),
1359 );
1360
1361 table
1362 .apply(AttributeOp::new(
1363 tags::SENDING_APPLICATION_ENTITY_TITLE,
1364 AttributeAction::SetStrIfMissing("STORE-SCU".into()),
1365 ))
1366 .unwrap();
1367
1368 assert_eq!(
1369 table.sending_application_entity_title.as_deref(),
1370 Some("STORE-SCU"),
1371 );
1372
1373 table
1375 .apply(AttributeOp::new(
1376 tags::MEDIA_STORAGE_SOP_CLASS_UID,
1377 AttributeAction::Replace(PrimitiveValue::Str("1.2.840.10008.5.1.4.1.1.7".into())),
1378 ))
1379 .unwrap();
1380
1381 assert_eq!(
1382 table.media_storage_sop_class_uid(),
1383 "1.2.840.10008.5.1.4.1.1.7",
1384 );
1385 }
1386
1387 #[test]
1390 fn write_read_does_not_fail() {
1391 let mut table = FileMetaTable {
1392 information_group_length: 0,
1393 information_version: [0u8, 1u8],
1394 media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.7".to_owned(),
1395 media_storage_sop_instance_uid: "2.25.137731752600317795446120660167595746868".to_owned(),
1396 transfer_syntax: "1.2.840.10008.1.2.4.91".to_owned(),
1397 implementation_class_uid: "2.25.305828488182831875890203105390285383139".to_owned(),
1398 implementation_version_name: Some("MYTOOL100".to_owned()),
1399 source_application_entity_title: Some("RUSTY".to_owned()),
1400 receiving_application_entity_title: None,
1401 sending_application_entity_title: None,
1402 private_information_creator_uid: None,
1403 private_information: None,
1404 };
1405
1406 table.update_information_group_length();
1407
1408 let mut buf = vec![b'D', b'I', b'C', b'M'];
1409 table.write(&mut buf).unwrap();
1410
1411 let table2 = FileMetaTable::from_reader(&mut buf.as_slice())
1412 .expect("Should not fail to read the table from the written data");
1413
1414 assert_eq!(table.information_group_length, table2.information_group_length);
1415 }
1416}