dicom_object/
meta.rs

1//! Module containing data structures and readers of DICOM file meta information tables.
2use 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::decode::{self, DecodeFrom};
14use dicom_encoding::encode::explicit_le::ExplicitVRLittleEndianEncoder;
15use dicom_encoding::encode::EncoderFor;
16use dicom_encoding::text::{self, TextCodec};
17use dicom_encoding::TransferSyntax;
18use dicom_parser::dataset::{DataSetWriter, IntoTokens};
19use snafu::{ensure, Backtrace, OptionExt, ResultExt, Snafu};
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    /// The file meta group parser could not read
38    /// the magic code `DICM` from its source.
39    #[snafu(display("Could not start reading DICOM data"))]
40    ReadMagicCode {
41        backtrace: Backtrace,
42        source: std::io::Error,
43    },
44
45    /// The file meta group parser could not fetch
46    /// the value of a data element from its source.
47    #[snafu(display("Could not read data value"))]
48    ReadValueData {
49        backtrace: Backtrace,
50        source: std::io::Error,
51    },
52
53    /// The parser could not allocate memory for the
54    /// given length of a data element.
55    #[snafu(display("Could not allocate memory"))]
56    AllocationSize {
57        backtrace: Backtrace,
58        source: std::collections::TryReserveError,
59    },
60
61    /// The file meta group parser could not decode
62    /// the text in one of its data elements.
63    #[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    /// Invalid DICOM data, detected by checking the `DICM` code.
71    #[snafu(display("Invalid DICOM file (magic code check failed)"))]
72    NotDicom { backtrace: Backtrace },
73
74    /// An issue occurred while decoding the next data element
75    /// in the file meta data set.
76    #[snafu(display("Could not decode data element"))]
77    DecodeElement {
78        #[snafu(backtrace)]
79        source: dicom_encoding::decode::Error,
80    },
81
82    /// A data element with an unexpected tag was retrieved:
83    /// the parser was expecting another tag first,
84    /// or at least one that is part of the the file meta group.
85    #[snafu(display("Unexpected data element tagged {}", tag))]
86    UnexpectedTag { tag: Tag, backtrace: Backtrace },
87
88    /// A required file meta data element is missing.
89    #[snafu(display("Missing data element `{}`", alias))]
90    MissingElement {
91        alias: &'static str,
92        backtrace: Backtrace,
93    },
94
95    /// The value length of a data elements in the file meta group
96    /// was unexpected.
97    #[snafu(display("Unexpected length {} for data element tagged {}", length, tag))]
98    UnexpectedDataValueLength {
99        tag: Tag,
100        length: Length,
101        backtrace: Backtrace,
102    },
103
104    /// The value length of a data element is undefined,
105    /// but knowing the length is required in its context.
106    #[snafu(display("Undefined value length for data element tagged {}", tag))]
107    UndefinedValueLength { tag: Tag, backtrace: Backtrace },
108
109    /// The file meta group data set could not be written.
110    #[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/// DICOM File Meta Information Table.
120///
121/// This data type contains the relevant parts of the file meta information table,
122/// as specified in [part 6, chapter 7][1] of the standard.
123///
124/// Creating a new file meta table from scratch
125/// is more easily done using a [`FileMetaTableBuilder`].
126/// When modifying the struct's public fields,
127/// it is possible to update the information group length
128/// through method [`update_information_group_length`][2].
129///
130/// [1]: http://dicom.nema.org/medical/dicom/current/output/chtml/part06/chapter_7.html
131/// [2]: FileMetaTable::update_information_group_length
132#[derive(Debug, Clone, PartialEq)]
133pub struct FileMetaTable {
134    /// File Meta Information Group Length
135    pub information_group_length: u32,
136    /// File Meta Information Version
137    pub information_version: [u8; 2],
138    /// Media Storage SOP Class UID
139    pub media_storage_sop_class_uid: String,
140    /// Media Storage SOP Instance UID
141    pub media_storage_sop_instance_uid: String,
142    /// Transfer Syntax UID
143    pub transfer_syntax: String,
144    /// Implementation Class UID
145    pub implementation_class_uid: String,
146
147    /// Implementation Version Name
148    pub implementation_version_name: Option<String>,
149    /// Source Application Entity Title
150    pub source_application_entity_title: Option<String>,
151    /// Sending Application Entity Title
152    pub sending_application_entity_title: Option<String>,
153    /// Receiving Application Entity Title
154    pub receiving_application_entity_title: Option<String>,
155    /// Private Information Creator UID
156    pub private_information_creator_uid: Option<String>,
157    /// Private Information
158    pub private_information: Option<Vec<u8>>,
159    /*
160    Missing attributes:
161
162    (0002,0026) Source Presentation Address Source​Presentation​Address UR 1
163    (0002,0027) Sending Presentation Address Sending​Presentation​Address UR 1
164    (0002,0028) Receiving Presentation Address Receiving​Presentation​Address UR 1
165    (0002,0031) RTV Meta Information Version RTV​Meta​Information​Version OB 1
166    (0002,0032) RTV Communication SOP Class UID RTV​Communication​SOP​Class​UID UI 1
167    (0002,0033) RTV Communication SOP Instance UID RTV​Communication​SOP​Instance​UID UI 1
168    (0002,0035) RTV Source Identifier RTV​Source​Identifier OB 1
169    (0002,0036) RTV Flow Identifier RTV​Flow​Identifier OB 1
170    (0002,0037) RTV Flow RTP Sampling Rate RTV​Flow​RTP​Sampling​Rate UL 1
171    (0002,0038) RTV Flow Actual Frame Duration RTV​Flow​Actual​Frame​Duration FD 1
172    */
173}
174
175/// Utility function for reading the body of the DICOM element as a UID.
176fn read_str_body<'s, S, T>(source: &'s mut S, text: &T, len: u32) -> Result<String>
177where
178    S: Read + 's,
179    T: TextCodec,
180{
181    let mut v = Vec::new();
182    v.try_reserve_exact(len as usize)
183        .context(AllocationSizeSnafu)?;
184    v.resize(len as usize, 0);
185    source.read_exact(&mut v).context(ReadValueDataSnafu)?;
186
187    text.decode(&v)
188        .context(DecodeTextSnafu { name: text.name() })
189}
190
191impl FileMetaTable {
192    /// Construct a file meta group table
193    /// by parsing a DICOM data set from a reader.
194    ///
195    /// This method fails if the first four bytes
196    /// are not the DICOM magic code `DICM`.
197    pub fn from_reader<R: Read>(file: R) -> Result<Self> {
198        FileMetaTable::read_from(file)
199    }
200
201    /// Getter for the transfer syntax UID,
202    /// with trailing characters already excluded.
203    pub fn transfer_syntax(&self) -> &str {
204        self.transfer_syntax
205            .trim_end_matches(|c: char| c.is_whitespace() || c == '\0')
206    }
207
208    /// Getter for the media storage SOP instance UID,
209    /// with trailing characters already excluded.
210    pub fn media_storage_sop_instance_uid(&self) -> &str {
211        self.media_storage_sop_instance_uid
212            .trim_end_matches(|c: char| c.is_whitespace() || c == '\0')
213    }
214
215    /// Getter for the media storage SOP class UID,
216    /// with trailing characters already excluded.
217    pub fn media_storage_sop_class_uid(&self) -> &str {
218        self.media_storage_sop_class_uid
219            .trim_end_matches(|c: char| c.is_whitespace() || c == '\0')
220    }
221
222    /// Getter for the implementation class UID,
223    /// with trailing characters already excluded.
224    pub fn implementation_class_uid(&self) -> &str {
225        self.implementation_class_uid
226            .trim_end_matches(|c: char| c.is_whitespace() || c == '\0')
227    }
228
229    /// Getter for the private information creator UID,
230    /// with trailing characters already excluded.
231    pub fn private_information_creator_uid(&self) -> Option<&str> {
232        self.private_information_creator_uid
233            .as_ref()
234            .map(|s| s.trim_end_matches(|c: char| c.is_whitespace() || c == '\0'))
235    }
236
237    /// Set the file meta table's transfer syntax
238    /// according to the given transfer syntax descriptor.
239    ///
240    /// This replaces the table's transfer syntax UID
241    /// to the given transfer syntax, without padding to even length.
242    /// The information group length field is automatically recalculated.
243    pub fn set_transfer_syntax<D, R, W>(&mut self, ts: &TransferSyntax<D, R, W>) {
244        self.transfer_syntax = ts
245            .uid()
246            .trim_end_matches(|c: char| c.is_whitespace() || c == '\0')
247            .to_string();
248        self.update_information_group_length();
249    }
250
251    /// Calculate the expected file meta group length
252    /// according to the file meta attributes currently set,
253    /// and assign it to the field `information_group_length`.
254    pub fn update_information_group_length(&mut self) {
255        self.information_group_length = self.calculate_information_group_length();
256    }
257
258    /// Apply the given attribute operation on this file meta information table.
259    ///
260    /// See the [`dicom_core::ops`] module
261    /// for more information.
262    fn apply(&mut self, op: AttributeOp) -> ApplyResult {
263        let AttributeSelectorStep::Tag(tag) = op.selector.first_step() else {
264            return UnsupportedAttributeSnafu.fail();
265        };
266
267        match *tag {
268            tags::TRANSFER_SYNTAX_UID => Self::apply_required_string(op, &mut self.transfer_syntax),
269            tags::MEDIA_STORAGE_SOP_CLASS_UID => {
270                Self::apply_required_string(op, &mut self.media_storage_sop_class_uid)
271            }
272            tags::MEDIA_STORAGE_SOP_INSTANCE_UID => {
273                Self::apply_required_string(op, &mut self.media_storage_sop_instance_uid)
274            }
275            tags::IMPLEMENTATION_CLASS_UID => {
276                Self::apply_required_string(op, &mut self.implementation_class_uid)
277            }
278            tags::IMPLEMENTATION_VERSION_NAME => {
279                Self::apply_optional_string(op, &mut self.implementation_version_name)
280            }
281            tags::SOURCE_APPLICATION_ENTITY_TITLE => {
282                Self::apply_optional_string(op, &mut self.source_application_entity_title)
283            }
284            tags::SENDING_APPLICATION_ENTITY_TITLE => {
285                Self::apply_optional_string(op, &mut self.sending_application_entity_title)
286            }
287            tags::RECEIVING_APPLICATION_ENTITY_TITLE => {
288                Self::apply_optional_string(op, &mut self.receiving_application_entity_title)
289            }
290            tags::PRIVATE_INFORMATION_CREATOR_UID => {
291                Self::apply_optional_string(op, &mut self.private_information_creator_uid)
292            }
293            _ if matches!(
294                op.action,
295                AttributeAction::Remove | AttributeAction::Empty | AttributeAction::Truncate(_)
296            ) =>
297            {
298                // any other attribute is not supported
299                // (ignore Remove, Empty, Truncate)
300                Ok(())
301            }
302            _ => UnsupportedAttributeSnafu.fail(),
303        }?;
304
305        self.update_information_group_length();
306
307        Ok(())
308    }
309
310    fn apply_required_string(op: AttributeOp, target_attribute: &mut String) -> ApplyResult {
311        match op.action {
312            AttributeAction::Remove | AttributeAction::Empty => MandatorySnafu.fail(),
313            AttributeAction::SetVr(_) | AttributeAction::Truncate(_) => {
314                // ignore
315                Ok(())
316            }
317            AttributeAction::Set(value) | AttributeAction::Replace(value) => {
318                // require value to be textual
319                if let Ok(value) = value.string() {
320                    *target_attribute = value.to_string();
321                    Ok(())
322                } else {
323                    IncompatibleTypesSnafu {
324                        kind: ValueType::Str,
325                    }
326                    .fail()
327                }
328            }
329            AttributeAction::SetStr(string) | AttributeAction::ReplaceStr(string) => {
330                *target_attribute = string.to_string();
331                Ok(())
332            }
333            AttributeAction::SetIfMissing(_) | AttributeAction::SetStrIfMissing(_) => {
334                // no-op
335                Ok(())
336            }
337            AttributeAction::PushStr(_) => IllegalExtendSnafu.fail(),
338            AttributeAction::PushI32(_)
339            | AttributeAction::PushU32(_)
340            | AttributeAction::PushI16(_)
341            | AttributeAction::PushU16(_)
342            | AttributeAction::PushF32(_)
343            | AttributeAction::PushF64(_) => IncompatibleTypesSnafu {
344                kind: ValueType::Str,
345            }
346            .fail(),
347            _ => UnsupportedActionSnafu.fail(),
348        }
349    }
350
351    fn apply_optional_string(
352        op: AttributeOp,
353        target_attribute: &mut Option<String>,
354    ) -> ApplyResult {
355        match op.action {
356            AttributeAction::Remove => {
357                target_attribute.take();
358                Ok(())
359            }
360            AttributeAction::Empty => {
361                if let Some(s) = target_attribute.as_mut() {
362                    s.clear();
363                }
364                Ok(())
365            }
366            AttributeAction::SetVr(_) => {
367                // ignore
368                Ok(())
369            }
370            AttributeAction::Set(value) => {
371                // require value to be textual
372                if let Ok(value) = value.string() {
373                    *target_attribute = Some(value.to_string());
374                    Ok(())
375                } else {
376                    IncompatibleTypesSnafu {
377                        kind: ValueType::Str,
378                    }
379                    .fail()
380                }
381            }
382            AttributeAction::SetStr(value) => {
383                *target_attribute = Some(value.to_string());
384                Ok(())
385            }
386            AttributeAction::SetIfMissing(value) => {
387                if target_attribute.is_some() {
388                    return Ok(());
389                }
390
391                // require value to be textual
392                if let Ok(value) = value.string() {
393                    *target_attribute = Some(value.to_string());
394                    Ok(())
395                } else {
396                    IncompatibleTypesSnafu {
397                        kind: ValueType::Str,
398                    }
399                    .fail()
400                }
401            }
402            AttributeAction::SetStrIfMissing(value) => {
403                if target_attribute.is_none() {
404                    *target_attribute = Some(value.to_string());
405                }
406                Ok(())
407            }
408            AttributeAction::Replace(value) => {
409                if target_attribute.is_none() {
410                    return Ok(());
411                }
412
413                // require value to be textual
414                if let Ok(value) = value.string() {
415                    *target_attribute = Some(value.to_string());
416                    Ok(())
417                } else {
418                    IncompatibleTypesSnafu {
419                        kind: ValueType::Str,
420                    }
421                    .fail()
422                }
423            }
424            AttributeAction::ReplaceStr(value) => {
425                if target_attribute.is_some() {
426                    *target_attribute = Some(value.to_string());
427                }
428                Ok(())
429            }
430            AttributeAction::PushStr(_) => IllegalExtendSnafu.fail(),
431            AttributeAction::PushI32(_)
432            | AttributeAction::PushU32(_)
433            | AttributeAction::PushI16(_)
434            | AttributeAction::PushU16(_)
435            | AttributeAction::PushF32(_)
436            | AttributeAction::PushF64(_) => IncompatibleTypesSnafu {
437                kind: ValueType::Str,
438            }
439            .fail(),
440            _ => UnsupportedActionSnafu.fail(),
441        }
442    }
443
444    /// Calculate the expected file meta group length,
445    /// ignoring `information_group_length`.
446    fn calculate_information_group_length(&self) -> u32 {
447        // determine the expected meta group size based on the given fields.
448        // attribute FileMetaInformationGroupLength is not included
449        // in the calculations intentionally
450        14 + 8
451            + dicom_len(&self.media_storage_sop_class_uid)
452            + 8
453            + dicom_len(&self.media_storage_sop_instance_uid)
454            + 8
455            + dicom_len(&self.transfer_syntax)
456            + 8
457            + dicom_len(&self.implementation_class_uid)
458            + self
459                .implementation_version_name
460                .as_ref()
461                .map(|s| 8 + dicom_len(s))
462                .unwrap_or(0)
463            + self
464                .source_application_entity_title
465                .as_ref()
466                .map(|s| 8 + dicom_len(s))
467                .unwrap_or(0)
468            + self
469                .sending_application_entity_title
470                .as_ref()
471                .map(|s| 8 + dicom_len(s))
472                .unwrap_or(0)
473            + self
474                .receiving_application_entity_title
475                .as_ref()
476                .map(|s| 8 + dicom_len(s))
477                .unwrap_or(0)
478            + self
479                .private_information_creator_uid
480                .as_ref()
481                .map(|s| 8 + dicom_len(s))
482                .unwrap_or(0)
483            + self
484                .private_information
485                .as_ref()
486                .map(|x| 12 + ((x.len() as u32 + 1) & !1))
487                .unwrap_or(0)
488    }
489
490    /// Read the DICOM magic code (`b"DICM"`)
491    /// and the whole file meta group from the given reader.
492    fn read_from<S: Read>(mut file: S) -> Result<Self> {
493        let mut buff: [u8; 4] = [0; 4];
494        {
495            // check magic code
496            file.read_exact(&mut buff).context(ReadMagicCodeSnafu)?;
497
498            ensure!(buff == DICM_MAGIC_CODE, NotDicomSnafu);
499        }
500
501        let decoder = decode::file_header_decoder();
502        let text = text::DefaultCharacterSetCodec;
503
504        let builder = FileMetaTableBuilder::new();
505
506        let group_length: u32 = {
507            let (elem, _bytes_read) = decoder
508                .decode_header(&mut file)
509                .context(DecodeElementSnafu)?;
510            if elem.tag() != Tag(0x0002, 0x0000) {
511                return UnexpectedTagSnafu { tag: elem.tag() }.fail();
512            }
513            if elem.length() != Length(4) {
514                return UnexpectedDataValueLengthSnafu {
515                    tag: elem.tag(),
516                    length: elem.length(),
517                }
518                .fail();
519            }
520            let mut buff: [u8; 4] = [0; 4];
521            file.read_exact(&mut buff).context(ReadValueDataSnafu)?;
522            LittleEndian::read_u32(&buff)
523        };
524
525        let mut total_bytes_read = 0;
526        let mut builder = builder.group_length(group_length);
527
528        // Fetch optional data elements
529        while total_bytes_read < group_length {
530            let (elem, header_bytes_read) = decoder
531                .decode_header(&mut file)
532                .context(DecodeElementSnafu)?;
533            let elem_len = match elem.length().get() {
534                None => {
535                    return UndefinedValueLengthSnafu { tag: elem.tag() }.fail();
536                }
537                Some(len) => len,
538            };
539            builder = match elem.tag() {
540                Tag(0x0002, 0x0001) => {
541                    // Implementation Version
542                    if elem.length() != Length(2) {
543                        return UnexpectedDataValueLengthSnafu {
544                            tag: elem.tag(),
545                            length: elem.length(),
546                        }
547                        .fail();
548                    }
549                    let mut hbuf = [0u8; 2];
550                    file.read_exact(&mut hbuf[..]).context(ReadValueDataSnafu)?;
551
552                    builder.information_version(hbuf)
553                }
554                // Media Storage SOP Class UID
555                Tag(0x0002, 0x0002) => {
556                    builder.media_storage_sop_class_uid(read_str_body(&mut file, &text, elem_len)?)
557                }
558                // Media Storage SOP Instance UID
559                Tag(0x0002, 0x0003) => builder
560                    .media_storage_sop_instance_uid(read_str_body(&mut file, &text, elem_len)?),
561                // Transfer Syntax
562                Tag(0x0002, 0x0010) => {
563                    builder.transfer_syntax(read_str_body(&mut file, &text, elem_len)?)
564                }
565                // Implementation Class UID
566                Tag(0x0002, 0x0012) => {
567                    builder.implementation_class_uid(read_str_body(&mut file, &text, elem_len)?)
568                }
569                Tag(0x0002, 0x0013) => {
570                    // Implementation Version Name
571                    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.implementation_version_name(
578                        text.decode(&v)
579                            .context(DecodeTextSnafu { name: text.name() })?,
580                    )
581                }
582                Tag(0x0002, 0x0016) => {
583                    // Source Application Entity Title
584                    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.source_application_entity_title(
591                        text.decode(&v)
592                            .context(DecodeTextSnafu { name: text.name() })?,
593                    )
594                }
595                Tag(0x0002, 0x0017) => {
596                    // Sending Application Entity Title
597                    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.sending_application_entity_title(
604                        text.decode(&v)
605                            .context(DecodeTextSnafu { name: text.name() })?,
606                    )
607                }
608                Tag(0x0002, 0x0018) => {
609                    // Receiving Application Entity Title
610                    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.receiving_application_entity_title(
617                        text.decode(&v)
618                            .context(DecodeTextSnafu { name: text.name() })?,
619                    )
620                }
621                Tag(0x0002, 0x0100) => {
622                    // Private Information Creator UID
623                    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_creator_uid(
630                        text.decode(&v)
631                            .context(DecodeTextSnafu { name: text.name() })?,
632                    )
633                }
634                Tag(0x0002, 0x0102) => {
635                    // Private Information
636                    let mut v = Vec::new();
637                    v.try_reserve_exact(elem_len as usize)
638                        .context(AllocationSizeSnafu)?;
639                    v.resize(elem_len as usize, 0);
640                    file.read_exact(&mut v).context(ReadValueDataSnafu)?;
641
642                    builder.private_information(v)
643                }
644                tag @ Tag(0x0002, _) => {
645                    // unknown tag, do nothing
646                    // could be an unsupported or non-standard attribute
647                    tracing::info!("Unknown tag {}", tag);
648                    // consume value without saving it
649                    let bytes_read =
650                        std::io::copy(&mut (&mut file).take(elem_len as u64), &mut std::io::sink())
651                            .context(ReadValueDataSnafu)?;
652                    if bytes_read != elem_len as u64 {
653                        // reported element length longer than actual stream
654                        return UnexpectedDataValueLengthSnafu {
655                            tag: elem.tag(),
656                            length: elem_len,
657                        }
658                        .fail();
659                    }
660                    builder
661                }
662                tag => {
663                    // unexpected tag from another group! do nothing for now,
664                    // but this could pose an issue up ahead (see #50)
665                    tracing::warn!("Unexpected off-group tag {}", tag);
666                    // consume value without saving it
667                    let bytes_read =
668                        std::io::copy(&mut (&mut file).take(elem_len as u64), &mut std::io::sink())
669                            .context(ReadValueDataSnafu)?;
670                    if bytes_read != elem_len as u64 {
671                        // reported element length longer than actual stream
672                        return UnexpectedDataValueLengthSnafu {
673                            tag: elem.tag(),
674                            length: elem_len,
675                        }
676                        .fail();
677                    }
678                    builder
679                }
680            };
681            total_bytes_read = total_bytes_read
682                .saturating_add(header_bytes_read as u32)
683                .saturating_add(elem_len);
684        }
685
686        builder.build()
687    }
688
689    /// Create an iterator over the defined data elements
690    /// of the file meta group,
691    /// consuming the file meta table.
692    ///
693    /// See [`to_element_iter`](FileMetaTable::to_element_iter)
694    /// for a version which copies the element from the table.
695    pub fn into_element_iter(self) -> impl Iterator<Item = DataElement<EmptyObject, [u8; 0]>> {
696        let mut elems = vec![
697            // file information group length
698            DataElement::new(
699                Tag(0x0002, 0x0000),
700                VR::UL,
701                Value::Primitive(self.information_group_length.into()),
702            ),
703            DataElement::new(
704                Tag(0x0002, 0x0001),
705                VR::OB,
706                Value::Primitive(dicom_value!(
707                    U8,
708                    [self.information_version[0], self.information_version[1]]
709                )),
710            ),
711            DataElement::new(
712                Tag(0x0002, 0x0002),
713                VR::UI,
714                Value::Primitive(self.media_storage_sop_class_uid.into()),
715            ),
716            DataElement::new(
717                Tag(0x0002, 0x0003),
718                VR::UI,
719                Value::Primitive(self.media_storage_sop_instance_uid.into()),
720            ),
721            DataElement::new(
722                Tag(0x0002, 0x0010),
723                VR::UI,
724                Value::Primitive(self.transfer_syntax.into()),
725            ),
726            DataElement::new(
727                Tag(0x0002, 0x0012),
728                VR::UI,
729                Value::Primitive(self.implementation_class_uid.into()),
730            ),
731        ];
732        if let Some(v) = self.implementation_version_name {
733            elems.push(DataElement::new(
734                Tag(0x0002, 0x0013),
735                VR::SH,
736                Value::Primitive(v.into()),
737            ));
738        }
739        if let Some(v) = self.source_application_entity_title {
740            elems.push(DataElement::new(
741                Tag(0x0002, 0x0016),
742                VR::AE,
743                Value::Primitive(v.into()),
744            ));
745        }
746        if let Some(v) = self.sending_application_entity_title {
747            elems.push(DataElement::new(
748                Tag(0x0002, 0x0017),
749                VR::AE,
750                Value::Primitive(v.into()),
751            ));
752        }
753        if let Some(v) = self.receiving_application_entity_title {
754            elems.push(DataElement::new(
755                Tag(0x0002, 0x0018),
756                VR::AE,
757                Value::Primitive(v.into()),
758            ));
759        }
760        if let Some(v) = self.private_information_creator_uid {
761            elems.push(DataElement::new(
762                Tag(0x0002, 0x0100),
763                VR::UI,
764                Value::Primitive(v.into()),
765            ));
766        }
767        if let Some(v) = self.private_information {
768            elems.push(DataElement::new(
769                Tag(0x0002, 0x0102),
770                VR::OB,
771                Value::Primitive(PrimitiveValue::U8(v.into())),
772            ));
773        }
774
775        elems.into_iter()
776    }
777
778    /// Create an iterator of data elements copied from the file meta group.
779    ///
780    /// See [`into_element_iter`](FileMetaTable::into_element_iter)
781    /// for a version which consumes the table.
782    pub fn to_element_iter(&self) -> impl Iterator<Item = DataElement<EmptyObject, [u8; 0]>> + '_ {
783        self.clone().into_element_iter()
784    }
785
786    pub fn write<W: Write>(&self, writer: W) -> Result<()> {
787        let mut dset = DataSetWriter::new(
788            writer,
789            EncoderFor::new(ExplicitVRLittleEndianEncoder::default()),
790        );
791        //There are no sequences in the `FileMetaTable`, so the value of `invalidate_sq_len` is
792        //not important
793        dset.write_sequence(
794            self.clone()
795                .into_element_iter()
796                .flat_map(IntoTokens::into_tokens),
797        )
798        .context(WriteSetSnafu)?;
799
800        dset.flush().context(WriteSetSnafu)
801    }
802}
803
804/// An attribute selector for a file meta information table.
805#[derive(Debug)]
806pub struct FileMetaAttribute<'a> {
807    meta: &'a FileMetaTable,
808    tag_e: u16,
809}
810
811impl HasLength for FileMetaAttribute<'_> {
812    fn length(&self) -> Length {
813        match Tag(0x0002, self.tag_e) {
814            tags::FILE_META_INFORMATION_GROUP_LENGTH => Length(4),
815            tags::MEDIA_STORAGE_SOP_CLASS_UID => {
816                Length(self.meta.media_storage_sop_class_uid.len() as u32)
817            }
818            tags::MEDIA_STORAGE_SOP_INSTANCE_UID => {
819                Length(self.meta.media_storage_sop_instance_uid.len() as u32)
820            }
821            tags::IMPLEMENTATION_CLASS_UID => {
822                Length(self.meta.implementation_class_uid.len() as u32)
823            }
824            tags::IMPLEMENTATION_VERSION_NAME => Length(
825                self.meta
826                    .implementation_version_name
827                    .as_ref()
828                    .map(|s| s.len() as u32)
829                    .unwrap_or(0),
830            ),
831            tags::SOURCE_APPLICATION_ENTITY_TITLE => Length(
832                self.meta
833                    .source_application_entity_title
834                    .as_ref()
835                    .map(|s| s.len() as u32)
836                    .unwrap_or(0),
837            ),
838            tags::SENDING_APPLICATION_ENTITY_TITLE => Length(
839                self.meta
840                    .sending_application_entity_title
841                    .as_ref()
842                    .map(|s| s.len() as u32)
843                    .unwrap_or(0),
844            ),
845            tags::TRANSFER_SYNTAX_UID => Length(self.meta.transfer_syntax.len() as u32),
846            tags::PRIVATE_INFORMATION_CREATOR_UID => Length(
847                self.meta
848                    .private_information_creator_uid
849                    .as_ref()
850                    .map(|s| s.len() as u32)
851                    .unwrap_or(0),
852            ),
853            _ => unreachable!(),
854        }
855    }
856}
857
858impl DicomValueType for FileMetaAttribute<'_> {
859    fn value_type(&self) -> ValueType {
860        match Tag(0x0002, self.tag_e) {
861            tags::MEDIA_STORAGE_SOP_CLASS_UID
862            | tags::MEDIA_STORAGE_SOP_INSTANCE_UID
863            | tags::TRANSFER_SYNTAX_UID
864            | tags::IMPLEMENTATION_CLASS_UID
865            | tags::IMPLEMENTATION_VERSION_NAME
866            | tags::SOURCE_APPLICATION_ENTITY_TITLE
867            | tags::SENDING_APPLICATION_ENTITY_TITLE
868            | tags::RECEIVING_APPLICATION_ENTITY_TITLE
869            | tags::PRIVATE_INFORMATION_CREATOR_UID => ValueType::Str,
870            tags::FILE_META_INFORMATION_GROUP_LENGTH => ValueType::U32,
871            tags::FILE_META_INFORMATION_VERSION => ValueType::U8,
872            tags::PRIVATE_INFORMATION => ValueType::U8,
873            _ => unreachable!(),
874        }
875    }
876
877    fn cardinality(&self) -> usize {
878        match Tag(0x0002, self.tag_e) {
879            tags::MEDIA_STORAGE_SOP_CLASS_UID
880            | tags::MEDIA_STORAGE_SOP_INSTANCE_UID
881            | tags::SOURCE_APPLICATION_ENTITY_TITLE
882            | tags::SENDING_APPLICATION_ENTITY_TITLE
883            | tags::RECEIVING_APPLICATION_ENTITY_TITLE
884            | tags::TRANSFER_SYNTAX_UID
885            | tags::IMPLEMENTATION_CLASS_UID
886            | tags::IMPLEMENTATION_VERSION_NAME
887            | tags::PRIVATE_INFORMATION_CREATOR_UID => 1,
888            tags::FILE_META_INFORMATION_GROUP_LENGTH => 1,
889            tags::PRIVATE_INFORMATION => 1,
890            tags::FILE_META_INFORMATION_VERSION => 2,
891            _ => 1,
892        }
893    }
894}
895
896impl DicomAttribute for FileMetaAttribute<'_> {
897    type Item<'b>
898        = EmptyObject
899    where
900        Self: 'b;
901    type PixelData<'b>
902        = InMemFragment
903    where
904        Self: 'b;
905
906    fn to_primitive_value(&self) -> Result<PrimitiveValue, AttributeError> {
907        Ok(match Tag(0x0002, self.tag_e) {
908            tags::FILE_META_INFORMATION_GROUP_LENGTH => {
909                PrimitiveValue::from(self.meta.information_group_length)
910            }
911            tags::FILE_META_INFORMATION_VERSION => {
912                PrimitiveValue::from(self.meta.information_version)
913            }
914            tags::MEDIA_STORAGE_SOP_CLASS_UID => {
915                PrimitiveValue::from(self.meta.media_storage_sop_class_uid.clone())
916            }
917            tags::MEDIA_STORAGE_SOP_INSTANCE_UID => {
918                PrimitiveValue::from(self.meta.media_storage_sop_instance_uid.clone())
919            }
920            tags::SOURCE_APPLICATION_ENTITY_TITLE => {
921                PrimitiveValue::from(self.meta.source_application_entity_title.clone().unwrap())
922            }
923            tags::SENDING_APPLICATION_ENTITY_TITLE => {
924                PrimitiveValue::from(self.meta.sending_application_entity_title.clone().unwrap())
925            }
926            tags::RECEIVING_APPLICATION_ENTITY_TITLE => PrimitiveValue::from(
927                self.meta
928                    .receiving_application_entity_title
929                    .clone()
930                    .unwrap(),
931            ),
932            tags::TRANSFER_SYNTAX_UID => PrimitiveValue::from(self.meta.transfer_syntax.clone()),
933            tags::IMPLEMENTATION_CLASS_UID => {
934                PrimitiveValue::from(self.meta.implementation_class_uid.clone())
935            }
936            tags::IMPLEMENTATION_VERSION_NAME => {
937                PrimitiveValue::from(self.meta.implementation_version_name.clone().unwrap())
938            }
939            tags::PRIVATE_INFORMATION_CREATOR_UID => {
940                PrimitiveValue::from(self.meta.private_information_creator_uid.clone().unwrap())
941            }
942            tags::PRIVATE_INFORMATION => {
943                PrimitiveValue::from(self.meta.private_information.clone().unwrap())
944            }
945            _ => unreachable!(),
946        })
947    }
948
949    fn to_str(&self) -> std::result::Result<std::borrow::Cow<'_, str>, AttributeError> {
950        match Tag(0x0002, self.tag_e) {
951            tags::FILE_META_INFORMATION_GROUP_LENGTH => {
952                Ok(self.meta.information_group_length.to_string().into())
953            }
954            tags::FILE_META_INFORMATION_VERSION => Ok(format!(
955                "{:02X}{:02X}",
956                self.meta.information_version[0], self.meta.information_version[1]
957            )
958            .into()),
959            tags::MEDIA_STORAGE_SOP_CLASS_UID => {
960                Ok(Cow::Borrowed(self.meta.media_storage_sop_class_uid()))
961            }
962            tags::MEDIA_STORAGE_SOP_INSTANCE_UID => {
963                Ok(Cow::Borrowed(self.meta.media_storage_sop_instance_uid()))
964            }
965            tags::TRANSFER_SYNTAX_UID => Ok(Cow::Borrowed(self.meta.transfer_syntax())),
966            tags::IMPLEMENTATION_CLASS_UID => {
967                Ok(Cow::Borrowed(self.meta.implementation_class_uid()))
968            }
969            tags::IMPLEMENTATION_VERSION_NAME => Ok(self
970                .meta
971                .implementation_version_name
972                .as_deref()
973                .map(Cow::Borrowed)
974                .unwrap_or_default()),
975            tags::SOURCE_APPLICATION_ENTITY_TITLE => Ok(self
976                .meta
977                .source_application_entity_title
978                .as_deref()
979                .map(Cow::Borrowed)
980                .unwrap_or_default()),
981            tags::SENDING_APPLICATION_ENTITY_TITLE => Ok(self
982                .meta
983                .sending_application_entity_title
984                .as_deref()
985                .map(Cow::Borrowed)
986                .unwrap_or_default()),
987            tags::RECEIVING_APPLICATION_ENTITY_TITLE => Ok(self
988                .meta
989                .receiving_application_entity_title
990                .as_deref()
991                .map(Cow::Borrowed)
992                .unwrap_or_default()),
993            tags::PRIVATE_INFORMATION_CREATOR_UID => Ok(self
994                .meta
995                .private_information_creator_uid
996                .as_deref()
997                .map(|v| {
998                    Cow::Borrowed(v.trim_end_matches(|c: char| c.is_whitespace() || c == '\0'))
999                })
1000                .unwrap_or_default()),
1001            tags::PRIVATE_INFORMATION => Err(AttributeError::ConvertValue {
1002                source: ConvertValueError {
1003                    cause: None,
1004                    original: ValueType::U8,
1005                    requested: "str",
1006                },
1007            }),
1008            _ => unreachable!(),
1009        }
1010    }
1011
1012    fn item(&self, _index: u32) -> Result<Self::Item<'_>, AttributeError> {
1013        Err(AttributeError::NotDataSet)
1014    }
1015
1016    fn num_items(&self) -> Option<u32> {
1017        None
1018    }
1019
1020    fn fragment(&self, _index: u32) -> Result<Self::PixelData<'_>, AttributeError> {
1021        Err(AttributeError::NotPixelData)
1022    }
1023
1024    fn num_fragments(&self) -> Option<u32> {
1025        None
1026    }
1027}
1028
1029impl DicomObject for FileMetaTable {
1030    type Attribute<'a>
1031        = FileMetaAttribute<'a>
1032    where
1033        Self: 'a;
1034
1035    type LeafAttribute<'a>
1036        = FileMetaAttribute<'a>
1037    where
1038        Self: 'a;
1039
1040    fn attr_opt(
1041        &self,
1042        tag: Tag,
1043    ) -> std::result::Result<Option<Self::Attribute<'_>>, crate::AccessError> {
1044        // check that the attribute value is in the table,
1045        // then return a suitable `FileMetaAttribute`
1046
1047        if match tag {
1048            // mandatory attributes
1049            tags::FILE_META_INFORMATION_GROUP_LENGTH
1050            | tags::FILE_META_INFORMATION_VERSION
1051            | tags::MEDIA_STORAGE_SOP_CLASS_UID
1052            | tags::MEDIA_STORAGE_SOP_INSTANCE_UID
1053            | tags::TRANSFER_SYNTAX_UID
1054            | tags::IMPLEMENTATION_CLASS_UID
1055            | tags::IMPLEMENTATION_VERSION_NAME => true,
1056            // optional attributes
1057            tags::SOURCE_APPLICATION_ENTITY_TITLE
1058                if self.source_application_entity_title.is_some() =>
1059            {
1060                true
1061            }
1062            tags::SENDING_APPLICATION_ENTITY_TITLE
1063                if self.sending_application_entity_title.is_some() =>
1064            {
1065                true
1066            }
1067            tags::RECEIVING_APPLICATION_ENTITY_TITLE
1068                if self.receiving_application_entity_title.is_some() =>
1069            {
1070                true
1071            }
1072            tags::PRIVATE_INFORMATION_CREATOR_UID
1073                if self.private_information_creator_uid.is_some() =>
1074            {
1075                true
1076            }
1077            tags::PRIVATE_INFORMATION if self.private_information.is_some() => true,
1078            _ => false,
1079        } {
1080            Ok(Some(FileMetaAttribute {
1081                meta: self,
1082                tag_e: tag.element(),
1083            }))
1084        } else {
1085            Ok(None)
1086        }
1087    }
1088
1089    fn attr_by_name_opt<'a>(
1090        &'a self,
1091        name: &str,
1092    ) -> std::result::Result<Option<Self::Attribute<'a>>, crate::AccessByNameError> {
1093        let tag = match name {
1094            "FileMetaInformationGroupLength" => tags::FILE_META_INFORMATION_GROUP_LENGTH,
1095            "FileMetaInformationVersion" => tags::FILE_META_INFORMATION_VERSION,
1096            "MediaStorageSOPClassUID" => tags::MEDIA_STORAGE_SOP_CLASS_UID,
1097            "MediaStorageSOPInstanceUID" => tags::MEDIA_STORAGE_SOP_INSTANCE_UID,
1098            "TransferSyntaxUID" => tags::TRANSFER_SYNTAX_UID,
1099            "ImplementationClassUID" => tags::IMPLEMENTATION_CLASS_UID,
1100            "ImplementationVersionName" => tags::IMPLEMENTATION_VERSION_NAME,
1101            "SourceApplicationEntityTitle" => tags::SOURCE_APPLICATION_ENTITY_TITLE,
1102            "SendingApplicationEntityTitle" => tags::SENDING_APPLICATION_ENTITY_TITLE,
1103            "ReceivingApplicationEntityTitle" => tags::RECEIVING_APPLICATION_ENTITY_TITLE,
1104            "PrivateInformationCreatorUID" => tags::PRIVATE_INFORMATION_CREATOR_UID,
1105            "PrivateInformation" => tags::PRIVATE_INFORMATION,
1106            _ => return Ok(None),
1107        };
1108        self.attr_opt(tag)
1109            .map_err(|_| crate::NoSuchAttributeNameSnafu { name }.build())
1110    }
1111
1112    fn at(
1113        &self,
1114        selector: impl Into<AttributeSelector>,
1115    ) -> Result<Self::LeafAttribute<'_>, crate::AtAccessError> {
1116        let selector: AttributeSelector = selector.into();
1117        match selector.split_first() {
1118            (AttributeSelectorStep::Tag(tag), None) => self
1119                .attr(tag)
1120                .map_err(|_| AtAccessError::MissingLeafElement { selector }),
1121            (_, Some(_)) => crate::NotASequenceSnafu {
1122                selector,
1123                step_index: 0_u32,
1124            }
1125            .fail(),
1126            (_, None) => unreachable!("broken invariant: nested step at end of selector"),
1127        }
1128    }
1129}
1130
1131impl ApplyOp for FileMetaTable {
1132    type Err = ApplyError;
1133
1134    /// Apply the given attribute operation on this file meta information table.
1135    ///
1136    /// See the [`dicom_core::ops`] module
1137    /// for more information.
1138    fn apply(&mut self, op: AttributeOp) -> ApplyResult {
1139        self.apply(op)
1140    }
1141}
1142
1143/// A builder for DICOM meta information tables.
1144#[derive(Debug, Default, Clone)]
1145pub struct FileMetaTableBuilder {
1146    /// File Meta Information Group Length (UL)
1147    information_group_length: Option<u32>,
1148    /// File Meta Information Version (OB)
1149    information_version: Option<[u8; 2]>,
1150    /// Media Storage SOP Class UID (UI)
1151    media_storage_sop_class_uid: Option<String>,
1152    /// Media Storage SOP Instance UID (UI)
1153    media_storage_sop_instance_uid: Option<String>,
1154    /// Transfer Syntax UID (UI)
1155    transfer_syntax: Option<String>,
1156    /// Implementation Class UID (UI)
1157    implementation_class_uid: Option<String>,
1158
1159    /// Implementation Version Name (SH)
1160    implementation_version_name: Option<String>,
1161    /// Source Application Entity Title (AE)
1162    source_application_entity_title: Option<String>,
1163    /// Sending Application Entity Title (AE)
1164    sending_application_entity_title: Option<String>,
1165    /// Receiving Application Entity Title (AE)
1166    receiving_application_entity_title: Option<String>,
1167    /// Private Information Creator UID (UI)
1168    private_information_creator_uid: Option<String>,
1169    /// Private Information (OB)
1170    private_information: Option<Vec<u8>>,
1171}
1172
1173/// Ensure that the string is even lengthed, by adding a trailing character
1174/// if not.
1175#[inline]
1176fn padded<T>(s: T, pad: char) -> String
1177where
1178    T: Into<String>,
1179{
1180    let mut s = s.into();
1181    if s.len() % 2 == 1 {
1182        s.push(pad);
1183    }
1184    s
1185}
1186
1187/// Ensure that the string is even lengthed with trailing '\0's.
1188fn ui_padded<T>(s: T) -> String
1189where
1190    T: Into<String>,
1191{
1192    padded(s, '\0')
1193}
1194
1195/// Ensure that the string is even lengthed with trailing spaces.
1196fn txt_padded<T>(s: T) -> String
1197where
1198    T: Into<String>,
1199{
1200    padded(s, ' ')
1201}
1202
1203impl FileMetaTableBuilder {
1204    /// Create a new, empty builder.
1205    pub fn new() -> FileMetaTableBuilder {
1206        FileMetaTableBuilder::default()
1207    }
1208
1209    /// Define the meta information group length.
1210    pub fn group_length(mut self, value: u32) -> FileMetaTableBuilder {
1211        self.information_group_length = Some(value);
1212        self
1213    }
1214
1215    /// Define the meta information version.
1216    pub fn information_version(mut self, value: [u8; 2]) -> FileMetaTableBuilder {
1217        self.information_version = Some(value);
1218        self
1219    }
1220
1221    /// Define the media storage SOP class UID.
1222    pub fn media_storage_sop_class_uid<T>(mut self, value: T) -> FileMetaTableBuilder
1223    where
1224        T: Into<String>,
1225    {
1226        self.media_storage_sop_class_uid = Some(ui_padded(value));
1227        self
1228    }
1229
1230    /// Define the media storage SOP instance UID.
1231    pub fn media_storage_sop_instance_uid<T>(mut self, value: T) -> FileMetaTableBuilder
1232    where
1233        T: Into<String>,
1234    {
1235        self.media_storage_sop_instance_uid = Some(ui_padded(value));
1236        self
1237    }
1238
1239    /// Define the transfer syntax UID.
1240    pub fn transfer_syntax<T>(mut self, value: T) -> FileMetaTableBuilder
1241    where
1242        T: Into<String>,
1243    {
1244        self.transfer_syntax = Some(ui_padded(value));
1245        self
1246    }
1247
1248    /// Define the implementation class UID.
1249    pub fn implementation_class_uid<T>(mut self, value: T) -> FileMetaTableBuilder
1250    where
1251        T: Into<String>,
1252    {
1253        self.implementation_class_uid = Some(ui_padded(value));
1254        self
1255    }
1256
1257    /// Define the implementation version name.
1258    pub fn implementation_version_name<T>(mut self, value: T) -> FileMetaTableBuilder
1259    where
1260        T: Into<String>,
1261    {
1262        self.implementation_version_name = Some(txt_padded(value));
1263        self
1264    }
1265
1266    /// Define the source application entity title.
1267    pub fn source_application_entity_title<T>(mut self, value: T) -> FileMetaTableBuilder
1268    where
1269        T: Into<String>,
1270    {
1271        self.source_application_entity_title = Some(txt_padded(value));
1272        self
1273    }
1274
1275    /// Define the sending application entity title.
1276    pub fn sending_application_entity_title<T>(mut self, value: T) -> FileMetaTableBuilder
1277    where
1278        T: Into<String>,
1279    {
1280        self.sending_application_entity_title = Some(txt_padded(value));
1281        self
1282    }
1283
1284    /// Define the receiving application entity title.
1285    pub fn receiving_application_entity_title<T>(mut self, value: T) -> FileMetaTableBuilder
1286    where
1287        T: Into<String>,
1288    {
1289        self.receiving_application_entity_title = Some(txt_padded(value));
1290        self
1291    }
1292
1293    /// Define the private information creator UID.
1294    pub fn private_information_creator_uid<T>(mut self, value: T) -> FileMetaTableBuilder
1295    where
1296        T: Into<String>,
1297    {
1298        self.private_information_creator_uid = Some(ui_padded(value));
1299        self
1300    }
1301
1302    /// Define the private information as a vector of bytes.
1303    pub fn private_information<T>(mut self, value: T) -> FileMetaTableBuilder
1304    where
1305        T: Into<Vec<u8>>,
1306    {
1307        self.private_information = Some(value.into());
1308        self
1309    }
1310
1311    /// Build the table.
1312    pub fn build(self) -> Result<FileMetaTable> {
1313        let information_version = self.information_version.unwrap_or(
1314            // Missing information version, will assume (00H, 01H). See #28
1315            [0, 1],
1316        );
1317        let media_storage_sop_class_uid = self.media_storage_sop_class_uid.unwrap_or_else(|| {
1318            tracing::warn!("MediaStorageSOPClassUID is missing. Defaulting to empty string.");
1319            String::default()
1320        });
1321        let media_storage_sop_instance_uid =
1322            self.media_storage_sop_instance_uid.unwrap_or_else(|| {
1323                tracing::warn!(
1324                    "MediaStorageSOPInstanceUID is missing. Defaulting to empty string."
1325                );
1326                String::default()
1327            });
1328        let transfer_syntax = self.transfer_syntax.context(MissingElementSnafu {
1329            alias: "TransferSyntax",
1330        })?;
1331        let mut implementation_version_name = self.implementation_version_name;
1332        let implementation_class_uid = self.implementation_class_uid.unwrap_or_else(|| {
1333            // override implementation version name
1334            implementation_version_name = Some(IMPLEMENTATION_VERSION_NAME.to_string());
1335
1336            IMPLEMENTATION_CLASS_UID.to_string()
1337        });
1338
1339        let mut table = FileMetaTable {
1340            // placeholder value which will be replaced on update
1341            information_group_length: 0x00,
1342            information_version,
1343            media_storage_sop_class_uid,
1344            media_storage_sop_instance_uid,
1345            transfer_syntax,
1346            implementation_class_uid,
1347            implementation_version_name,
1348            source_application_entity_title: self.source_application_entity_title,
1349            sending_application_entity_title: self.sending_application_entity_title,
1350            receiving_application_entity_title: self.receiving_application_entity_title,
1351            private_information_creator_uid: self.private_information_creator_uid,
1352            private_information: self.private_information,
1353        };
1354        table.update_information_group_length();
1355        debug_assert!(table.information_group_length > 0);
1356        Ok(table)
1357    }
1358}
1359
1360fn dicom_len<T: AsRef<str>>(x: T) -> u32 {
1361    (x.as_ref().len() as u32 + 1) & !1
1362}
1363
1364#[cfg(test)]
1365mod tests {
1366    use crate::{IMPLEMENTATION_CLASS_UID, IMPLEMENTATION_VERSION_NAME};
1367
1368    use super::{dicom_len, FileMetaTable, FileMetaTableBuilder};
1369    use dicom_core::ops::{AttributeAction, AttributeOp};
1370    use dicom_core::value::Value;
1371    use dicom_core::{dicom_value, DataElement, PrimitiveValue, Tag, VR};
1372    use dicom_dictionary_std::tags;
1373
1374    const TEST_META_1: &[u8] = &[
1375        // magic code
1376        b'D', b'I', b'C', b'M',
1377        // File Meta Information Group Length: (0000,0002) ; UL ; 4 ; 200
1378        0x02, 0x00, 0x00, 0x00, b'U', b'L', 0x04, 0x00, 0xc8, 0x00, 0x00, 0x00,
1379        // File Meta Information Version: (0002, 0001) ; OB ; 2 ; [0x00, 0x01]
1380        0x02, 0x00, 0x01, 0x00, b'O', b'B', 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x01,
1381        // Media Storage SOP Class UID (0002, 0002) ; UI ; 26 ; "1.2.840.10008.5.1.4.1.1.1\0" (ComputedRadiographyImageStorage)
1382        0x02, 0x00, 0x02, 0x00, b'U', b'I', 0x1a, 0x00, 0x31, 0x2e, 0x32, 0x2e, 0x38, 0x34, 0x30,
1383        0x2e, 0x31, 0x30, 0x30, 0x30, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e,
1384        0x31, 0x2e, 0x31, 0x00,
1385        // Media Storage SOP Instance UID (0002, 0003) ; UI ; 56 ; "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0"
1386        0x02, 0x00, 0x03, 0x00, b'U', b'I', 0x38, 0x00, 0x31, 0x2e, 0x32, 0x2e, 0x33, 0x2e, 0x34,
1387        0x2e, 0x35, 0x2e, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x2e, 0x31, 0x32, 0x33,
1388        0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x2e, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
1389        0x2e, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2e, 0x31, 0x32, 0x33, 0x34,
1390        0x35, 0x36, 0x37, 0x00,
1391        // Transfer Syntax UID (0002, 0010) ; UI ; 20 ; "1.2.840.10008.1.2.1\0" (LittleEndianExplicit)
1392        0x02, 0x00, 0x10, 0x00, b'U', b'I', 0x14, 0x00, 0x31, 0x2e, 0x32, 0x2e, 0x38, 0x34, 0x30,
1393        0x2e, 0x31, 0x30, 0x30, 0x30, 0x38, 0x2e, 0x31, 0x2e, 0x32, 0x2e, 0x31, 0x00,
1394        // Implementation Class UID (0002, 0012) ; UI ; 20 ; "1.2.345.6.7890.1.234"
1395        0x02, 0x00, 0x12, 0x00, b'U', b'I', 0x14, 0x00, 0x31, 0x2e, 0x32, 0x2e, 0x33, 0x34, 0x35,
1396        0x2e, 0x36, 0x2e, 0x37, 0x38, 0x39, 0x30, 0x2e, 0x31, 0x2e, 0x32, 0x33, 0x34,
1397        // optional elements:
1398
1399        // Implementation Version Name (0002,0013) ; SH ; "RUSTY_DICOM_269"
1400        0x02, 0x00, 0x13, 0x00, b'S', b'H', 0x10, 0x00, 0x52, 0x55, 0x53, 0x54, 0x59, 0x5f, 0x44,
1401        0x49, 0x43, 0x4f, 0x4d, 0x5f, 0x32, 0x36, 0x39, 0x20,
1402        // Source Application Entity Title (0002, 0016) ; AE ; 0 (no data)
1403        0x02, 0x00, 0x16, 0x00, b'A', b'E', 0x00, 0x00,
1404    ];
1405
1406    #[test]
1407    fn read_meta_table_from_reader() {
1408        let mut source = TEST_META_1;
1409
1410        let table = FileMetaTable::from_reader(&mut source).unwrap();
1411
1412        let gt = FileMetaTable {
1413            information_group_length: 200,
1414            information_version: [0u8, 1u8],
1415            media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.1\0".to_owned(),
1416            media_storage_sop_instance_uid:
1417                "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".to_owned(),
1418            transfer_syntax: "1.2.840.10008.1.2.1\0".to_owned(),
1419            implementation_class_uid: "1.2.345.6.7890.1.234".to_owned(),
1420            implementation_version_name: Some("RUSTY_DICOM_269 ".to_owned()),
1421            source_application_entity_title: Some("".to_owned()),
1422            sending_application_entity_title: None,
1423            receiving_application_entity_title: None,
1424            private_information_creator_uid: None,
1425            private_information: None,
1426        };
1427
1428        assert_eq!(table.information_group_length, 200);
1429        assert_eq!(table.information_version, [0u8, 1u8]);
1430        assert_eq!(
1431            table.media_storage_sop_class_uid,
1432            "1.2.840.10008.5.1.4.1.1.1\0"
1433        );
1434        assert_eq!(
1435            table.media_storage_sop_instance_uid,
1436            "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0"
1437        );
1438        assert_eq!(table.transfer_syntax, "1.2.840.10008.1.2.1\0");
1439        assert_eq!(table.implementation_class_uid, "1.2.345.6.7890.1.234");
1440        assert_eq!(
1441            table.implementation_version_name,
1442            Some("RUSTY_DICOM_269 ".to_owned())
1443        );
1444        assert_eq!(table.source_application_entity_title, Some("".into()));
1445        assert_eq!(table.sending_application_entity_title, None);
1446        assert_eq!(table.receiving_application_entity_title, None);
1447        assert_eq!(table.private_information_creator_uid, None);
1448        assert_eq!(table.private_information, None);
1449
1450        assert_eq!(table, gt);
1451    }
1452
1453    #[test]
1454    fn create_meta_table_with_builder() {
1455        let table = FileMetaTableBuilder::new()
1456            .information_version([0, 1])
1457            .media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.1")
1458            .media_storage_sop_instance_uid(
1459                "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567",
1460            )
1461            .transfer_syntax("1.2.840.10008.1.2.1")
1462            .implementation_class_uid("1.2.345.6.7890.1.234")
1463            .implementation_version_name("RUSTY_DICOM_269")
1464            .source_application_entity_title("")
1465            .build()
1466            .unwrap();
1467
1468        let gt = FileMetaTable {
1469            information_group_length: 200,
1470            information_version: [0u8, 1u8],
1471            media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.1\0".to_owned(),
1472            media_storage_sop_instance_uid:
1473                "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".to_owned(),
1474            transfer_syntax: "1.2.840.10008.1.2.1\0".to_owned(),
1475            implementation_class_uid: "1.2.345.6.7890.1.234".to_owned(),
1476            implementation_version_name: Some("RUSTY_DICOM_269 ".to_owned()),
1477            source_application_entity_title: Some("".to_owned()),
1478            sending_application_entity_title: None,
1479            receiving_application_entity_title: None,
1480            private_information_creator_uid: None,
1481            private_information: None,
1482        };
1483
1484        assert_eq!(table.information_group_length, gt.information_group_length);
1485        assert_eq!(table, gt);
1486    }
1487
1488    /// Build a file meta table with the minimum set of parameters.
1489    #[test]
1490    fn create_meta_table_with_builder_minimal() {
1491        let table = FileMetaTableBuilder::new()
1492            .media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.1")
1493            .media_storage_sop_instance_uid(
1494                "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567",
1495            )
1496            .transfer_syntax("1.2.840.10008.1.2")
1497            .build()
1498            .unwrap();
1499
1500        let gt = FileMetaTable {
1501            information_group_length: 154
1502                + dicom_len(IMPLEMENTATION_CLASS_UID)
1503                + dicom_len(IMPLEMENTATION_VERSION_NAME),
1504            information_version: [0u8, 1u8],
1505            media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.1\0".to_owned(),
1506            media_storage_sop_instance_uid:
1507                "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".to_owned(),
1508            transfer_syntax: "1.2.840.10008.1.2\0".to_owned(),
1509            implementation_class_uid: IMPLEMENTATION_CLASS_UID.to_owned(),
1510            implementation_version_name: Some(IMPLEMENTATION_VERSION_NAME.to_owned()),
1511            source_application_entity_title: None,
1512            sending_application_entity_title: None,
1513            receiving_application_entity_title: None,
1514            private_information_creator_uid: None,
1515            private_information: None,
1516        };
1517
1518        assert_eq!(table.information_group_length, gt.information_group_length);
1519        assert_eq!(table, gt);
1520    }
1521
1522    /// Changing the transfer syntax updates the file meta group length.
1523    #[test]
1524    fn change_transfer_syntax_update_table() {
1525        let mut table = FileMetaTableBuilder::new()
1526            .media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.1")
1527            .media_storage_sop_instance_uid(
1528                "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567",
1529            )
1530            .transfer_syntax("1.2.840.10008.1.2.1")
1531            .build()
1532            .unwrap();
1533
1534        assert_eq!(
1535            table.information_group_length,
1536            156 + dicom_len(IMPLEMENTATION_CLASS_UID) + dicom_len(IMPLEMENTATION_VERSION_NAME)
1537        );
1538
1539        table.set_transfer_syntax(
1540            &dicom_transfer_syntax_registry::entries::IMPLICIT_VR_LITTLE_ENDIAN,
1541        );
1542        assert_eq!(
1543            table.information_group_length,
1544            154 + dicom_len(IMPLEMENTATION_CLASS_UID) + dicom_len(IMPLEMENTATION_VERSION_NAME)
1545        );
1546    }
1547
1548    #[test]
1549    fn read_meta_table_into_iter() {
1550        let table = FileMetaTable {
1551            information_group_length: 200,
1552            information_version: [0u8, 1u8],
1553            media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.1\0".to_owned(),
1554            media_storage_sop_instance_uid:
1555                "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".to_owned(),
1556            transfer_syntax: "1.2.840.10008.1.2.1\0".to_owned(),
1557            implementation_class_uid: "1.2.345.6.7890.1.234".to_owned(),
1558            implementation_version_name: Some("RUSTY_DICOM_269 ".to_owned()),
1559            source_application_entity_title: Some("".to_owned()),
1560            sending_application_entity_title: None,
1561            receiving_application_entity_title: None,
1562            private_information_creator_uid: None,
1563            private_information: None,
1564        };
1565
1566        assert_eq!(table.calculate_information_group_length(), 200);
1567
1568        let gt = vec![
1569            // Information Group Length
1570            DataElement::new(Tag(0x0002, 0x0000), VR::UL, dicom_value!(U32, 200)),
1571            // Information Version
1572            DataElement::new(Tag(0x0002, 0x0001), VR::OB, dicom_value!(U8, [0, 1])),
1573            // Media Storage SOP Class UID
1574            DataElement::new(
1575                Tag(0x0002, 0x0002),
1576                VR::UI,
1577                Value::Primitive("1.2.840.10008.5.1.4.1.1.1\0".into()),
1578            ),
1579            // Media Storage SOP Instance UID
1580            DataElement::new(
1581                Tag(0x0002, 0x0003),
1582                VR::UI,
1583                Value::Primitive(
1584                    "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".into(),
1585                ),
1586            ),
1587            // Transfer Syntax
1588            DataElement::new(
1589                Tag(0x0002, 0x0010),
1590                VR::UI,
1591                Value::Primitive("1.2.840.10008.1.2.1\0".into()),
1592            ),
1593            // Implementation Class UID
1594            DataElement::new(
1595                Tag(0x0002, 0x0012),
1596                VR::UI,
1597                Value::Primitive("1.2.345.6.7890.1.234".into()),
1598            ),
1599            // Implementation Version Name
1600            DataElement::new(
1601                Tag(0x0002, 0x0013),
1602                VR::SH,
1603                Value::Primitive("RUSTY_DICOM_269 ".into()),
1604            ),
1605            // Source Application Entity Title
1606            DataElement::new(Tag(0x0002, 0x0016), VR::AE, Value::Primitive("".into())),
1607        ];
1608
1609        let elems: Vec<_> = table.into_element_iter().collect();
1610        assert_eq!(elems, gt);
1611    }
1612
1613    #[test]
1614    fn update_table_with_length() {
1615        let mut table = FileMetaTable {
1616            information_group_length: 55, // dummy value
1617            information_version: [0u8, 1u8],
1618            media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.1\0".to_owned(),
1619            media_storage_sop_instance_uid:
1620                "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".to_owned(),
1621            transfer_syntax: "1.2.840.10008.1.2.1\0".to_owned(),
1622            implementation_class_uid: "1.2.345.6.7890.1.234".to_owned(),
1623            implementation_version_name: Some("RUSTY_DICOM_269 ".to_owned()),
1624            source_application_entity_title: Some("".to_owned()),
1625            sending_application_entity_title: None,
1626            receiving_application_entity_title: None,
1627            private_information_creator_uid: None,
1628            private_information: None,
1629        };
1630
1631        table.update_information_group_length();
1632
1633        assert_eq!(table.information_group_length, 200);
1634    }
1635
1636    #[test]
1637    fn table_ops() {
1638        let mut table = FileMetaTable {
1639            information_group_length: 200,
1640            information_version: [0u8, 1u8],
1641            media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.1\0".to_owned(),
1642            media_storage_sop_instance_uid:
1643                "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".to_owned(),
1644            transfer_syntax: "1.2.840.10008.1.2.1\0".to_owned(),
1645            implementation_class_uid: "1.2.345.6.7890.1.234".to_owned(),
1646            implementation_version_name: None,
1647            source_application_entity_title: None,
1648            sending_application_entity_title: None,
1649            receiving_application_entity_title: None,
1650            private_information_creator_uid: None,
1651            private_information: None,
1652        };
1653
1654        // replace does not set missing attributes
1655        table
1656            .apply(AttributeOp::new(
1657                tags::IMPLEMENTATION_VERSION_NAME,
1658                AttributeAction::ReplaceStr("MY_DICOM_1.1".into()),
1659            ))
1660            .unwrap();
1661
1662        assert_eq!(table.implementation_version_name, None);
1663
1664        // but SetStr does
1665        table
1666            .apply(AttributeOp::new(
1667                tags::IMPLEMENTATION_VERSION_NAME,
1668                AttributeAction::SetStr("MY_DICOM_1.1".into()),
1669            ))
1670            .unwrap();
1671
1672        assert_eq!(
1673            table.implementation_version_name.as_deref(),
1674            Some("MY_DICOM_1.1"),
1675        );
1676
1677        // Set (primitive) also works
1678        table
1679            .apply(AttributeOp::new(
1680                tags::SOURCE_APPLICATION_ENTITY_TITLE,
1681                AttributeAction::Set(PrimitiveValue::Str("RICOOGLE-STORAGE".into())),
1682            ))
1683            .unwrap();
1684
1685        assert_eq!(
1686            table.source_application_entity_title.as_deref(),
1687            Some("RICOOGLE-STORAGE"),
1688        );
1689
1690        // set if missing works only if value isn't set yet
1691        table
1692            .apply(AttributeOp::new(
1693                tags::SOURCE_APPLICATION_ENTITY_TITLE,
1694                AttributeAction::SetStrIfMissing("STORE-SCU".into()),
1695            ))
1696            .unwrap();
1697
1698        assert_eq!(
1699            table.source_application_entity_title.as_deref(),
1700            Some("RICOOGLE-STORAGE"),
1701        );
1702
1703        table
1704            .apply(AttributeOp::new(
1705                tags::SENDING_APPLICATION_ENTITY_TITLE,
1706                AttributeAction::SetStrIfMissing("STORE-SCU".into()),
1707            ))
1708            .unwrap();
1709
1710        assert_eq!(
1711            table.sending_application_entity_title.as_deref(),
1712            Some("STORE-SCU"),
1713        );
1714
1715        // replacing mandatory field
1716        table
1717            .apply(AttributeOp::new(
1718                tags::MEDIA_STORAGE_SOP_CLASS_UID,
1719                AttributeAction::Replace(PrimitiveValue::Str("1.2.840.10008.5.1.4.1.1.7".into())),
1720            ))
1721            .unwrap();
1722
1723        assert_eq!(
1724            table.media_storage_sop_class_uid(),
1725            "1.2.840.10008.5.1.4.1.1.7",
1726        );
1727    }
1728
1729    /// writing file meta information and reading it back
1730    /// should not fail and the the group length should be the same
1731    #[test]
1732    fn write_read_does_not_fail() {
1733        let mut table = FileMetaTable {
1734            information_group_length: 0,
1735            information_version: [0u8, 1u8],
1736            media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.7".to_owned(),
1737            media_storage_sop_instance_uid: "2.25.137731752600317795446120660167595746868"
1738                .to_owned(),
1739            transfer_syntax: "1.2.840.10008.1.2.4.91".to_owned(),
1740            implementation_class_uid: "2.25.305828488182831875890203105390285383139".to_owned(),
1741            implementation_version_name: Some("MYTOOL100".to_owned()),
1742            source_application_entity_title: Some("RUSTY".to_owned()),
1743            receiving_application_entity_title: None,
1744            sending_application_entity_title: None,
1745            private_information_creator_uid: None,
1746            private_information: None,
1747        };
1748
1749        table.update_information_group_length();
1750
1751        let mut buf = vec![b'D', b'I', b'C', b'M'];
1752        table.write(&mut buf).unwrap();
1753
1754        let table2 = FileMetaTable::from_reader(&mut buf.as_slice())
1755            .expect("Should not fail to read the table from the written data");
1756
1757        assert_eq!(
1758            table.information_group_length,
1759            table2.information_group_length
1760        );
1761    }
1762
1763    /// Can access file meta properties via the DicomObject trait
1764    #[test]
1765    fn dicom_object_api() {
1766        use crate::{DicomAttribute as _, DicomObject as _};
1767        use dicom_dictionary_std::uids;
1768
1769        let meta = FileMetaTableBuilder::new()
1770            .transfer_syntax(uids::RLE_LOSSLESS)
1771            .media_storage_sop_class_uid(uids::ENHANCED_MR_IMAGE_STORAGE)
1772            .media_storage_sop_instance_uid("2.25.94766187067244888884745908966163363746")
1773            .implementation_version_name("RUSTY_DICOM_269")
1774            .build()
1775            .unwrap();
1776
1777        assert_eq!(
1778            meta.attr(tags::TRANSFER_SYNTAX_UID)
1779                .unwrap()
1780                .to_str()
1781                .unwrap(),
1782            uids::RLE_LOSSLESS
1783        );
1784
1785        let sop_class_uid = meta.attr_opt(tags::MEDIA_STORAGE_SOP_CLASS_UID).unwrap();
1786        let sop_class_uid = sop_class_uid.as_ref().map(|v| v.to_str().unwrap());
1787        assert_eq!(
1788            sop_class_uid.as_deref(),
1789            Some(uids::ENHANCED_MR_IMAGE_STORAGE)
1790        );
1791
1792        assert_eq!(
1793            meta.attr_by_name("MediaStorageSOPInstanceUID")
1794                .unwrap()
1795                .to_str()
1796                .unwrap(),
1797            "2.25.94766187067244888884745908966163363746"
1798        );
1799
1800        assert!(meta.attr_opt(tags::PRIVATE_INFORMATION).unwrap().is_none());
1801    }
1802}