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::{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    /// The file meta group parser could not read
30    /// the magic code `DICM` from its source.
31    #[snafu(display("Could not start reading DICOM data"))]
32    ReadMagicCode {
33        backtrace: Backtrace,
34        source: std::io::Error,
35    },
36
37    /// The file meta group parser could not fetch
38    /// the value of a data element from its source.
39    #[snafu(display("Could not read data value"))]
40    ReadValueData {
41        backtrace: Backtrace,
42        source: std::io::Error,
43    },
44
45    /// The parser could not allocate memory for the
46    /// given length of a data element.
47    #[snafu(display("Could not allocate memory"))]
48    AllocationSize {
49        backtrace: Backtrace,
50        source: std::collections::TryReserveError,
51    },
52
53    /// The file meta group parser could not decode
54    /// the text in one of its data elements.
55    #[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    /// Invalid DICOM data, detected by checking the `DICM` code.
63    #[snafu(display("Invalid DICOM file (magic code check failed)"))]
64    NotDicom { backtrace: Backtrace },
65
66    /// An issue occurred while decoding the next data element
67    /// in the file meta data set.
68    #[snafu(display("Could not decode data element"))]
69    DecodeElement {
70        #[snafu(backtrace)]
71        source: dicom_encoding::decode::Error,
72    },
73
74    /// A data element with an unexpected tag was retrieved:
75    /// the parser was expecting another tag first,
76    /// or at least one that is part of the the file meta group.
77    #[snafu(display("Unexpected data element tagged {}", tag))]
78    UnexpectedTag { tag: Tag, backtrace: Backtrace },
79
80    /// A required file meta data element is missing.
81    #[snafu(display("Missing data element `{}`", alias))]
82    MissingElement {
83        alias: &'static str,
84        backtrace: Backtrace,
85    },
86
87    /// The value length of a data elements in the file meta group
88    /// was unexpected.
89    #[snafu(display("Unexpected length {} for data element tagged {}", length, tag))]
90    UnexpectedDataValueLength {
91        tag: Tag,
92        length: Length,
93        backtrace: Backtrace,
94    },
95
96    /// The value length of a data element is undefined,
97    /// but knowing the length is required in its context.
98    #[snafu(display("Undefined value length for data element tagged {}", tag))]
99    UndefinedValueLength { tag: Tag, backtrace: Backtrace },
100
101    /// The file meta group data set could not be written.
102    #[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/// DICOM File Meta Information Table.
112///
113/// This data type contains the relevant parts of the file meta information table,
114/// as specified in [part 6, chapter 7][1] of the standard.
115///
116/// Creating a new file meta table from scratch
117/// is more easily done using a [`FileMetaTableBuilder`].
118/// When modifying the struct's public fields,
119/// it is possible to update the information group length
120/// through method [`update_information_group_length`][2].
121///
122/// [1]: http://dicom.nema.org/medical/dicom/current/output/chtml/part06/chapter_7.html
123/// [2]: FileMetaTable::update_information_group_length
124#[derive(Debug, Clone, PartialEq)]
125pub struct FileMetaTable {
126    /// File Meta Information Group Length
127    pub information_group_length: u32,
128    /// File Meta Information Version
129    pub information_version: [u8; 2],
130    /// Media Storage SOP Class UID
131    pub media_storage_sop_class_uid: String,
132    /// Media Storage SOP Instance UID
133    pub media_storage_sop_instance_uid: String,
134    /// Transfer Syntax UID
135    pub transfer_syntax: String,
136    /// Implementation Class UID
137    pub implementation_class_uid: String,
138
139    /// Implementation Version Name
140    pub implementation_version_name: Option<String>,
141    /// Source Application Entity Title
142    pub source_application_entity_title: Option<String>,
143    /// Sending Application Entity Title
144    pub sending_application_entity_title: Option<String>,
145    /// Receiving Application Entity Title
146    pub receiving_application_entity_title: Option<String>,
147    /// Private Information Creator UID
148    pub private_information_creator_uid: Option<String>,
149    /// Private Information
150    pub private_information: Option<Vec<u8>>,
151    /*
152    Missing attributes:
153
154    (0002,0026) Source Presentation Address Source​Presentation​Address UR 1
155    (0002,0027) Sending Presentation Address Sending​Presentation​Address UR 1
156    (0002,0028) Receiving Presentation Address Receiving​Presentation​Address UR 1
157    (0002,0031) RTV Meta Information Version RTV​Meta​Information​Version OB 1
158    (0002,0032) RTV Communication SOP Class UID RTV​Communication​SOP​Class​UID UI 1
159    (0002,0033) RTV Communication SOP Instance UID RTV​Communication​SOP​Instance​UID UI 1
160    (0002,0035) RTV Source Identifier RTV​Source​Identifier OB 1
161    (0002,0036) RTV Flow Identifier RTV​Flow​Identifier OB 1
162    (0002,0037) RTV Flow RTP Sampling Rate RTV​Flow​RTP​Sampling​Rate UL 1
163    (0002,0038) RTV Flow Actual Frame Duration RTV​Flow​Actual​Frame​Duration FD 1
164    */
165}
166
167/// Utility function for reading the body of the DICOM element as a UID.
168fn 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    /// Construct a file meta group table
185    /// by parsing a DICOM data set from a reader.
186    /// 
187    /// This method fails if the first four bytes
188    /// are not the DICOM magic code `DICM`.
189    pub fn from_reader<R: Read>(file: R) -> Result<Self> {
190        FileMetaTable::read_from(file)
191    }
192
193    /// Getter for the transfer syntax UID,
194    /// with trailing characters already excluded.
195    pub fn transfer_syntax(&self) -> &str {
196        self.transfer_syntax
197            .trim_end_matches(|c: char| c.is_whitespace() || c == '\0')
198    }
199
200    /// Getter for the media storage SOP instance UID,
201    /// with trailing characters already excluded.
202    pub fn media_storage_sop_instance_uid(&self) -> &str {
203        self.media_storage_sop_instance_uid
204            .trim_end_matches(|c: char| c.is_whitespace() || c == '\0')
205    }
206
207    /// Getter for the media storage SOP class UID,
208    /// with trailing characters already excluded.
209    pub fn media_storage_sop_class_uid(&self) -> &str {
210        self.media_storage_sop_class_uid
211            .trim_end_matches(|c: char| c.is_whitespace() || c == '\0')
212    }
213
214    /// Getter for the implementation class UID,
215    /// with trailing characters already excluded.
216    pub fn implementation_class_uid(&self) -> &str {
217        self.implementation_class_uid
218            .trim_end_matches(|c: char| c.is_whitespace() || c == '\0')
219    }
220
221    /// Getter for the private information creator UID,
222    /// with trailing characters already excluded.
223    pub fn private_information_creator_uid(&self) -> Option<&str> {
224        self.private_information_creator_uid
225            .as_ref()
226            .map(|s| s.trim_end_matches(|c: char| c.is_whitespace() || c == '\0'))
227    }
228
229    /// Set the file meta table's transfer syntax
230    /// according to the given transfer syntax descriptor.
231    ///
232    /// This replaces the table's transfer syntax UID
233    /// to the given transfer syntax, without padding to even length.
234    /// The information group length field is automatically recalculated.
235    pub fn set_transfer_syntax<D, R, W>(&mut self, ts: &TransferSyntax<D, R, W>) {
236        self.transfer_syntax = ts
237            .uid()
238            .trim_end_matches(|c: char| c.is_whitespace() || c == '\0')
239            .to_string();
240        self.update_information_group_length();
241    }
242
243    /// Calculate the expected file meta group length
244    /// according to the file meta attributes currently set,
245    /// and assign it to the field `information_group_length`.
246    pub fn update_information_group_length(&mut self) {
247        self.information_group_length = self.calculate_information_group_length();
248    }
249
250    /// Apply the given attribute operation on this file meta information table.
251    ///
252    /// See the [`dicom_core::ops`] module
253    /// for more information.
254    fn apply(&mut self, op: AttributeOp) -> ApplyResult {
255        let AttributeSelectorStep::Tag(tag) = op.selector.first_step() else {
256            return UnsupportedAttributeSnafu.fail();
257        };
258
259        match *tag {
260            tags::TRANSFER_SYNTAX_UID => Self::apply_required_string(op, &mut self.transfer_syntax),
261            tags::MEDIA_STORAGE_SOP_CLASS_UID => {
262                Self::apply_required_string(op, &mut self.media_storage_sop_class_uid)
263            }
264            tags::MEDIA_STORAGE_SOP_INSTANCE_UID => {
265                Self::apply_required_string(op, &mut self.media_storage_sop_instance_uid)
266            }
267            tags::IMPLEMENTATION_CLASS_UID => {
268                Self::apply_required_string(op, &mut self.implementation_class_uid)
269            }
270            tags::IMPLEMENTATION_VERSION_NAME => {
271                Self::apply_optional_string(op, &mut self.implementation_version_name)
272            }
273            tags::SOURCE_APPLICATION_ENTITY_TITLE => {
274                Self::apply_optional_string(op, &mut self.source_application_entity_title)
275            }
276            tags::SENDING_APPLICATION_ENTITY_TITLE => {
277                Self::apply_optional_string(op, &mut self.sending_application_entity_title)
278            }
279            tags::RECEIVING_APPLICATION_ENTITY_TITLE => {
280                Self::apply_optional_string(op, &mut self.receiving_application_entity_title)
281            }
282            tags::PRIVATE_INFORMATION_CREATOR_UID => {
283                Self::apply_optional_string(op, &mut self.private_information_creator_uid)
284            }
285            _ if matches!(
286                op.action,
287                AttributeAction::Remove | AttributeAction::Empty | AttributeAction::Truncate(_)
288            ) =>
289            {
290                // any other attribute is not supported
291                // (ignore Remove, Empty, Truncate)
292                Ok(())
293            }
294            _ => UnsupportedAttributeSnafu.fail(),
295        }?;
296
297        self.update_information_group_length();
298
299        Ok(())
300    }
301
302    fn apply_required_string(op: AttributeOp, target_attribute: &mut String) -> ApplyResult {
303        match op.action {
304            AttributeAction::Remove | AttributeAction::Empty => MandatorySnafu.fail(),
305            AttributeAction::SetVr(_) | AttributeAction::Truncate(_) => {
306                // ignore
307                Ok(())
308            }
309            AttributeAction::Set(value) | AttributeAction::Replace(value) => {
310                // require value to be textual
311                if let Ok(value) = value.string() {
312                    *target_attribute = value.to_string();
313                    Ok(())
314                } else {
315                    IncompatibleTypesSnafu {
316                        kind: ValueType::Str,
317                    }
318                    .fail()
319                }
320            }
321            AttributeAction::SetStr(string) | AttributeAction::ReplaceStr(string) => {
322                *target_attribute = string.to_string();
323                Ok(())
324            }
325            AttributeAction::SetIfMissing(_) | AttributeAction::SetStrIfMissing(_) => {
326                // no-op
327                Ok(())
328            }
329            AttributeAction::PushStr(_) => IllegalExtendSnafu.fail(),
330            AttributeAction::PushI32(_)
331            | AttributeAction::PushU32(_)
332            | AttributeAction::PushI16(_)
333            | AttributeAction::PushU16(_)
334            | AttributeAction::PushF32(_)
335            | AttributeAction::PushF64(_) => IncompatibleTypesSnafu {
336                kind: ValueType::Str,
337            }
338            .fail(),
339            _ => UnsupportedActionSnafu.fail(),
340        }
341    }
342
343    fn apply_optional_string(
344        op: AttributeOp,
345        target_attribute: &mut Option<String>,
346    ) -> ApplyResult {
347        match op.action {
348            AttributeAction::Remove => {
349                target_attribute.take();
350                Ok(())
351            }
352            AttributeAction::Empty => {
353                if let Some(s) = target_attribute.as_mut() {
354                    s.clear();
355                }
356                Ok(())
357            }
358            AttributeAction::SetVr(_) => {
359                // ignore
360                Ok(())
361            }
362            AttributeAction::Set(value) => {
363                // require value to be textual
364                if let Ok(value) = value.string() {
365                    *target_attribute = Some(value.to_string());
366                    Ok(())
367                } else {
368                    IncompatibleTypesSnafu {
369                        kind: ValueType::Str,
370                    }
371                    .fail()
372                }
373            }
374            AttributeAction::SetStr(value) => {
375                *target_attribute = Some(value.to_string());
376                Ok(())
377            }
378            AttributeAction::SetIfMissing(value) => {
379                if target_attribute.is_some() {
380                    return Ok(());
381                }
382
383                // require value to be textual
384                if let Ok(value) = value.string() {
385                    *target_attribute = Some(value.to_string());
386                    Ok(())
387                } else {
388                    IncompatibleTypesSnafu {
389                        kind: ValueType::Str,
390                    }
391                    .fail()
392                }
393            }
394            AttributeAction::SetStrIfMissing(value) => {
395                if target_attribute.is_none() {
396                    *target_attribute = Some(value.to_string());
397                }
398                Ok(())
399            }
400            AttributeAction::Replace(value) => {
401                if target_attribute.is_none() {
402                    return Ok(());
403                }
404
405                // require value to be textual
406                if let Ok(value) = value.string() {
407                    *target_attribute = Some(value.to_string());
408                    Ok(())
409                } else {
410                    IncompatibleTypesSnafu {
411                        kind: ValueType::Str,
412                    }
413                    .fail()
414                }
415            }
416            AttributeAction::ReplaceStr(value) => {
417                if target_attribute.is_some() {
418                    *target_attribute = Some(value.to_string());
419                }
420                Ok(())
421            }
422            AttributeAction::PushStr(_) => IllegalExtendSnafu.fail(),
423            AttributeAction::PushI32(_)
424            | AttributeAction::PushU32(_)
425            | AttributeAction::PushI16(_)
426            | AttributeAction::PushU16(_)
427            | AttributeAction::PushF32(_)
428            | AttributeAction::PushF64(_) => IncompatibleTypesSnafu {
429                kind: ValueType::Str,
430            }
431            .fail(),
432            _ => UnsupportedActionSnafu.fail(),
433        }
434    }
435
436    /// Calculate the expected file meta group length,
437    /// ignoring `information_group_length`.
438    fn calculate_information_group_length(&self) -> u32 {
439        // determine the expected meta group size based on the given fields.
440        // attribute FileMetaInformationGroupLength is not included
441        // in the calculations intentionally
442        14 + 8
443            + dicom_len(&self.media_storage_sop_class_uid)
444            + 8
445            + dicom_len(&self.media_storage_sop_instance_uid)
446            + 8
447            + dicom_len(&self.transfer_syntax)
448            + 8
449            + dicom_len(&self.implementation_class_uid)
450            + self
451                .implementation_version_name
452                .as_ref()
453                .map(|s| 8 + dicom_len(s))
454                .unwrap_or(0)
455            + self
456                .source_application_entity_title
457                .as_ref()
458                .map(|s| 8 + dicom_len(s))
459                .unwrap_or(0)
460            + self
461                .sending_application_entity_title
462                .as_ref()
463                .map(|s| 8 + dicom_len(s))
464                .unwrap_or(0)
465            + self
466                .receiving_application_entity_title
467                .as_ref()
468                .map(|s| 8 + dicom_len(s))
469                .unwrap_or(0)
470            + self
471                .private_information_creator_uid
472                .as_ref()
473                .map(|s| 8 + dicom_len(s))
474                .unwrap_or(0)
475            + self
476                .private_information
477                .as_ref()
478                .map(|x| 12 + ((x.len() as u32 + 1) & !1))
479                .unwrap_or(0)
480    }
481
482    /// Read the DICOM magic code (`b"DICM"`)
483    /// and the whole file meta group from the given reader.
484    fn read_from<S: Read>(mut file: S) -> Result<Self> {
485        let mut buff: [u8; 4] = [0; 4];
486        {
487            // check magic code
488            file.read_exact(&mut buff).context(ReadMagicCodeSnafu)?;
489
490            ensure!(buff == DICM_MAGIC_CODE, NotDicomSnafu);
491        }
492
493        let decoder = decode::file_header_decoder();
494        let text = text::DefaultCharacterSetCodec;
495
496        let builder = FileMetaTableBuilder::new();
497
498        let group_length: u32 = {
499            let (elem, _bytes_read) = decoder
500                .decode_header(&mut file)
501                .context(DecodeElementSnafu)?;
502            if elem.tag() != Tag(0x0002, 0x0000) {
503                return UnexpectedTagSnafu { tag: elem.tag() }.fail();
504            }
505            if elem.length() != Length(4) {
506                return UnexpectedDataValueLengthSnafu {
507                    tag: elem.tag(),
508                    length: elem.length(),
509                }
510                .fail();
511            }
512            let mut buff: [u8; 4] = [0; 4];
513            file.read_exact(&mut buff).context(ReadValueDataSnafu)?;
514            LittleEndian::read_u32(&buff)
515        };
516
517        let mut total_bytes_read = 0;
518        let mut builder = builder.group_length(group_length);
519
520        // Fetch optional data elements
521        while total_bytes_read < group_length {
522            let (elem, header_bytes_read) = decoder
523                .decode_header(&mut file)
524                .context(DecodeElementSnafu)?;
525            let elem_len = match elem.length().get() {
526                None => {
527                    return UndefinedValueLengthSnafu { tag: elem.tag() }.fail();
528                }
529                Some(len) => len,
530            };
531            builder = match elem.tag() {
532                Tag(0x0002, 0x0001) => {
533                    // Implementation Version
534                    if elem.length() != Length(2) {
535                        return UnexpectedDataValueLengthSnafu {
536                            tag: elem.tag(),
537                            length: elem.length(),
538                        }
539                        .fail();
540                    }
541                    let mut hbuf = [0u8; 2];
542                    file.read_exact(&mut hbuf[..]).context(ReadValueDataSnafu)?;
543
544                    builder.information_version(hbuf)
545                }
546                // Media Storage SOP Class UID
547                Tag(0x0002, 0x0002) => {
548                    builder.media_storage_sop_class_uid(read_str_body(&mut file, &text, elem_len)?)
549                }
550                // Media Storage SOP Instance UID
551                Tag(0x0002, 0x0003) => builder
552                    .media_storage_sop_instance_uid(read_str_body(&mut file, &text, elem_len)?),
553                // Transfer Syntax
554                Tag(0x0002, 0x0010) => {
555                    builder.transfer_syntax(read_str_body(&mut file, &text, elem_len)?)
556                }
557                // Implementation Class UID
558                Tag(0x0002, 0x0012) => {
559                    builder.implementation_class_uid(read_str_body(&mut file, &text, elem_len)?)
560                }
561                Tag(0x0002, 0x0013) => {
562                    // Implementation Version Name
563                    let mut v = Vec::new();
564                    v.try_reserve_exact(elem_len as usize)
565                        .context(AllocationSizeSnafu)?;
566                    v.resize(elem_len as usize, 0);
567                    file.read_exact(&mut v).context(ReadValueDataSnafu)?;
568
569                    builder.implementation_version_name(
570                        text.decode(&v)
571                            .context(DecodeTextSnafu { name: text.name() })?,
572                    )
573                }
574                Tag(0x0002, 0x0016) => {
575                    // Source Application Entity Title
576                    let mut v = Vec::new();
577                    v.try_reserve_exact(elem_len as usize)
578                        .context(AllocationSizeSnafu)?;
579                    v.resize(elem_len as usize, 0);
580                    file.read_exact(&mut v).context(ReadValueDataSnafu)?;
581
582                    builder.source_application_entity_title(
583                        text.decode(&v)
584                            .context(DecodeTextSnafu { name: text.name() })?,
585                    )
586                }
587                Tag(0x0002, 0x0017) => {
588                    // Sending Application Entity Title
589                    let mut v = Vec::new();
590                    v.try_reserve_exact(elem_len as usize)
591                        .context(AllocationSizeSnafu)?;
592                    v.resize(elem_len as usize, 0);
593                    file.read_exact(&mut v).context(ReadValueDataSnafu)?;
594
595                    builder.sending_application_entity_title(
596                        text.decode(&v)
597                            .context(DecodeTextSnafu { name: text.name() })?,
598                    )
599                }
600                Tag(0x0002, 0x0018) => {
601                    // Receiving Application Entity Title
602                    let mut v = Vec::new();
603                    v.try_reserve_exact(elem_len as usize)
604                        .context(AllocationSizeSnafu)?;
605                    v.resize(elem_len as usize, 0);
606                    file.read_exact(&mut v).context(ReadValueDataSnafu)?;
607
608                    builder.receiving_application_entity_title(
609                        text.decode(&v)
610                            .context(DecodeTextSnafu { name: text.name() })?,
611                    )
612                }
613                Tag(0x0002, 0x0100) => {
614                    // Private Information Creator UID
615                    let mut v = Vec::new();
616                    v.try_reserve_exact(elem_len as usize)
617                        .context(AllocationSizeSnafu)?;
618                    v.resize(elem_len as usize, 0);
619                    file.read_exact(&mut v).context(ReadValueDataSnafu)?;
620
621                    builder.private_information_creator_uid(
622                        text.decode(&v)
623                            .context(DecodeTextSnafu { name: text.name() })?,
624                    )
625                }
626                Tag(0x0002, 0x0102) => {
627                    // Private Information
628                    let mut v = Vec::new();
629                    v.try_reserve_exact(elem_len as usize)
630                        .context(AllocationSizeSnafu)?;
631                    v.resize(elem_len as usize, 0);
632                    file.read_exact(&mut v).context(ReadValueDataSnafu)?;
633
634                    builder.private_information(v)
635                }
636                tag @ Tag(0x0002, _) => {
637                    // unknown tag, do nothing
638                    // could be an unsupported or non-standard attribute
639                    tracing::info!("Unknown tag {}", tag);
640                    // consume value without saving it
641                    let bytes_read =
642                        std::io::copy(&mut (&mut file).take(elem_len as u64), &mut std::io::sink())
643                            .context(ReadValueDataSnafu)?;
644                    if bytes_read != elem_len as u64 {
645                        // reported element length longer than actual stream
646                        return UnexpectedDataValueLengthSnafu {
647                            tag: elem.tag(),
648                            length: elem_len,
649                        }
650                        .fail();
651                    }
652                    builder
653                }
654                tag => {
655                    // unexpected tag from another group! do nothing for now,
656                    // but this could pose an issue up ahead (see #50)
657                    tracing::warn!("Unexpected off-group tag {}", tag);
658                    // consume value without saving it
659                    let bytes_read =
660                        std::io::copy(&mut (&mut file).take(elem_len as u64), &mut std::io::sink())
661                            .context(ReadValueDataSnafu)?;
662                    if bytes_read != elem_len as u64 {
663                        // reported element length longer than actual stream
664                        return UnexpectedDataValueLengthSnafu {
665                            tag: elem.tag(),
666                            length: elem_len,
667                        }
668                        .fail();
669                    }
670                    builder
671                }
672            };
673            total_bytes_read = total_bytes_read
674                .saturating_add(header_bytes_read as u32)
675                .saturating_add(elem_len);
676        }
677
678        builder.build()
679    }
680
681    /// Create an iterator over the defined data elements
682    /// of the file meta group,
683    /// consuming the file meta table.
684    ///
685    /// See [`to_element_iter`](FileMetaTable::to_element_iter)
686    /// for a version which copies the element from the table.
687    pub fn into_element_iter(self) -> impl Iterator<Item = DataElement<EmptyObject, [u8; 0]>> {
688        let mut elems = vec![
689            // file information group length
690            DataElement::new(
691                Tag(0x0002, 0x0000),
692                VR::UL,
693                Value::Primitive(self.information_group_length.into()),
694            ),
695            DataElement::new(
696                Tag(0x0002, 0x0001),
697                VR::OB,
698                Value::Primitive(dicom_value!(
699                    U8,
700                    [self.information_version[0], self.information_version[1]]
701                )),
702            ),
703            DataElement::new(
704                Tag(0x0002, 0x0002),
705                VR::UI,
706                Value::Primitive(self.media_storage_sop_class_uid.into()),
707            ),
708            DataElement::new(
709                Tag(0x0002, 0x0003),
710                VR::UI,
711                Value::Primitive(self.media_storage_sop_instance_uid.into()),
712            ),
713            DataElement::new(
714                Tag(0x0002, 0x0010),
715                VR::UI,
716                Value::Primitive(self.transfer_syntax.into()),
717            ),
718            DataElement::new(
719                Tag(0x0002, 0x0012),
720                VR::UI,
721                Value::Primitive(self.implementation_class_uid.into()),
722            ),
723        ];
724        if let Some(v) = self.implementation_version_name {
725            elems.push(DataElement::new(
726                Tag(0x0002, 0x0013),
727                VR::SH,
728                Value::Primitive(v.into()),
729            ));
730        }
731        if let Some(v) = self.source_application_entity_title {
732            elems.push(DataElement::new(
733                Tag(0x0002, 0x0016),
734                VR::AE,
735                Value::Primitive(v.into()),
736            ));
737        }
738        if let Some(v) = self.sending_application_entity_title {
739            elems.push(DataElement::new(
740                Tag(0x0002, 0x0017),
741                VR::AE,
742                Value::Primitive(v.into()),
743            ));
744        }
745        if let Some(v) = self.receiving_application_entity_title {
746            elems.push(DataElement::new(
747                Tag(0x0002, 0x0018),
748                VR::AE,
749                Value::Primitive(v.into()),
750            ));
751        }
752        if let Some(v) = self.private_information_creator_uid {
753            elems.push(DataElement::new(
754                Tag(0x0002, 0x0100),
755                VR::UI,
756                Value::Primitive(v.into()),
757            ));
758        }
759        if let Some(v) = self.private_information {
760            elems.push(DataElement::new(
761                Tag(0x0002, 0x0102),
762                VR::OB,
763                Value::Primitive(PrimitiveValue::U8(v.into())),
764            ));
765        }
766
767        elems.into_iter()
768    }
769
770    /// Create an iterator of data elements copied from the file meta group.
771    ///
772    /// See [`into_element_iter`](FileMetaTable::into_element_iter)
773    /// for a version which consumes the table.
774    pub fn to_element_iter(&self) -> impl Iterator<Item = DataElement<EmptyObject, [u8; 0]>> + '_ {
775        self.clone().into_element_iter()
776    }
777
778    pub fn write<W: Write>(&self, writer: W) -> Result<()> {
779        let mut dset = DataSetWriter::new(
780            writer,
781            EncoderFor::new(ExplicitVRLittleEndianEncoder::default()),
782        );
783        //There are no sequences in the `FileMetaTable`, so the value of `invalidate_sq_len` is
784        //not important
785        dset.write_sequence(
786            self.clone()
787                .into_element_iter()
788                .flat_map(IntoTokens::into_tokens),
789        )
790        .context(WriteSetSnafu)
791    }
792}
793
794impl ApplyOp for FileMetaTable {
795    type Err = ApplyError;
796
797    /// Apply the given attribute operation on this file meta information table.
798    ///
799    /// See the [`dicom_core::ops`] module
800    /// for more information.
801    fn apply(&mut self, op: AttributeOp) -> ApplyResult {
802        self.apply(op)
803    }
804}
805
806/// A builder for DICOM meta information tables.
807#[derive(Debug, Default, Clone)]
808pub struct FileMetaTableBuilder {
809    /// File Meta Information Group Length (UL)
810    information_group_length: Option<u32>,
811    /// File Meta Information Version (OB)
812    information_version: Option<[u8; 2]>,
813    /// Media Storage SOP Class UID (UI)
814    media_storage_sop_class_uid: Option<String>,
815    /// Media Storage SOP Instance UID (UI)
816    media_storage_sop_instance_uid: Option<String>,
817    /// Transfer Syntax UID (UI)
818    transfer_syntax: Option<String>,
819    /// Implementation Class UID (UI)
820    implementation_class_uid: Option<String>,
821
822    /// Implementation Version Name (SH)
823    implementation_version_name: Option<String>,
824    /// Source Application Entity Title (AE)
825    source_application_entity_title: Option<String>,
826    /// Sending Application Entity Title (AE)
827    sending_application_entity_title: Option<String>,
828    /// Receiving Application Entity Title (AE)
829    receiving_application_entity_title: Option<String>,
830    /// Private Information Creator UID (UI)
831    private_information_creator_uid: Option<String>,
832    /// Private Information (OB)
833    private_information: Option<Vec<u8>>,
834}
835
836/// Ensure that the string is even lengthed, by adding a trailing character
837/// if not.
838#[inline]
839fn padded<T>(s: T, pad: char) -> String
840where
841    T: Into<String>,
842{
843    let mut s = s.into();
844    if s.len() % 2 == 1 {
845        s.push(pad);
846    }
847    s
848}
849
850/// Ensure that the string is even lengthed with trailing '\0's.
851fn ui_padded<T>(s: T) -> String
852where
853    T: Into<String>,
854{
855    padded(s, '\0')
856}
857
858/// Ensure that the string is even lengthed with trailing spaces.
859fn txt_padded<T>(s: T) -> String
860where
861    T: Into<String>,
862{
863    padded(s, ' ')
864}
865
866impl FileMetaTableBuilder {
867    /// Create a new, empty builder.
868    pub fn new() -> FileMetaTableBuilder {
869        FileMetaTableBuilder::default()
870    }
871
872    /// Define the meta information group length.
873    pub fn group_length(mut self, value: u32) -> FileMetaTableBuilder {
874        self.information_group_length = Some(value);
875        self
876    }
877
878    /// Define the meta information version.
879    pub fn information_version(mut self, value: [u8; 2]) -> FileMetaTableBuilder {
880        self.information_version = Some(value);
881        self
882    }
883
884    /// Define the media storage SOP class UID.
885    pub fn media_storage_sop_class_uid<T>(mut self, value: T) -> FileMetaTableBuilder
886    where
887        T: Into<String>,
888    {
889        self.media_storage_sop_class_uid = Some(ui_padded(value));
890        self
891    }
892
893    /// Define the media storage SOP instance UID.
894    pub fn media_storage_sop_instance_uid<T>(mut self, value: T) -> FileMetaTableBuilder
895    where
896        T: Into<String>,
897    {
898        self.media_storage_sop_instance_uid = Some(ui_padded(value));
899        self
900    }
901
902    /// Define the transfer syntax UID.
903    pub fn transfer_syntax<T>(mut self, value: T) -> FileMetaTableBuilder
904    where
905        T: Into<String>,
906    {
907        self.transfer_syntax = Some(ui_padded(value));
908        self
909    }
910
911    /// Define the implementation class UID.
912    pub fn implementation_class_uid<T>(mut self, value: T) -> FileMetaTableBuilder
913    where
914        T: Into<String>,
915    {
916        self.implementation_class_uid = Some(ui_padded(value));
917        self
918    }
919
920    /// Define the implementation version name.
921    pub fn implementation_version_name<T>(mut self, value: T) -> FileMetaTableBuilder
922    where
923        T: Into<String>,
924    {
925        self.implementation_version_name = Some(txt_padded(value));
926        self
927    }
928
929    /// Define the source application entity title.
930    pub fn source_application_entity_title<T>(mut self, value: T) -> FileMetaTableBuilder
931    where
932        T: Into<String>,
933    {
934        self.source_application_entity_title = Some(txt_padded(value));
935        self
936    }
937
938    /// Define the sending application entity title.
939    pub fn sending_application_entity_title<T>(mut self, value: T) -> FileMetaTableBuilder
940    where
941        T: Into<String>,
942    {
943        self.sending_application_entity_title = Some(txt_padded(value));
944        self
945    }
946
947    /// Define the receiving application entity title.
948    pub fn receiving_application_entity_title<T>(mut self, value: T) -> FileMetaTableBuilder
949    where
950        T: Into<String>,
951    {
952        self.receiving_application_entity_title = Some(txt_padded(value));
953        self
954    }
955
956    /// Define the private information creator UID.
957    pub fn private_information_creator_uid<T>(mut self, value: T) -> FileMetaTableBuilder
958    where
959        T: Into<String>,
960    {
961        self.private_information_creator_uid = Some(ui_padded(value));
962        self
963    }
964
965    /// Define the private information as a vector of bytes.
966    pub fn private_information<T>(mut self, value: T) -> FileMetaTableBuilder
967    where
968        T: Into<Vec<u8>>,
969    {
970        self.private_information = Some(value.into());
971        self
972    }
973
974    /// Build the table.
975    pub fn build(self) -> Result<FileMetaTable> {
976        let information_version = self.information_version.unwrap_or(
977            // Missing information version, will assume (00H, 01H). See #28
978            [0, 1],
979        );
980        let media_storage_sop_class_uid = self.media_storage_sop_class_uid.unwrap_or_else(|| {
981            tracing::warn!("MediaStorageSOPClassUID is missing. Defaulting to empty string.");
982            String::default()
983        });
984        let media_storage_sop_instance_uid =
985            self.media_storage_sop_instance_uid.unwrap_or_else(|| {
986                tracing::warn!(
987                    "MediaStorageSOPInstanceUID is missing. Defaulting to empty string."
988                );
989                String::default()
990            });
991        let transfer_syntax = self.transfer_syntax.context(MissingElementSnafu {
992            alias: "TransferSyntax",
993        })?;
994        let mut implementation_version_name = self.implementation_version_name;
995        let implementation_class_uid = self.implementation_class_uid.unwrap_or_else(|| {
996            // override implementation version name
997            implementation_version_name = Some(IMPLEMENTATION_VERSION_NAME.to_string());
998
999            IMPLEMENTATION_CLASS_UID.to_string()
1000        });
1001
1002        let mut table = FileMetaTable {
1003            // placeholder value which will be replaced on update
1004            information_group_length: 0x00,
1005            information_version,
1006            media_storage_sop_class_uid,
1007            media_storage_sop_instance_uid,
1008            transfer_syntax,
1009            implementation_class_uid,
1010            implementation_version_name,
1011            source_application_entity_title: self.source_application_entity_title,
1012            sending_application_entity_title: self.sending_application_entity_title,
1013            receiving_application_entity_title: self.receiving_application_entity_title,
1014            private_information_creator_uid: self.private_information_creator_uid,
1015            private_information: self.private_information,
1016        };
1017        table.update_information_group_length();
1018        debug_assert!(table.information_group_length > 0);
1019        Ok(table)
1020    }
1021}
1022
1023fn dicom_len<T: AsRef<str>>(x: T) -> u32 {
1024    (x.as_ref().len() as u32 + 1) & !1
1025}
1026
1027#[cfg(test)]
1028mod tests {
1029    use crate::{IMPLEMENTATION_CLASS_UID, IMPLEMENTATION_VERSION_NAME};
1030
1031    use super::{dicom_len, FileMetaTable, FileMetaTableBuilder};
1032    use dicom_core::ops::{AttributeAction, AttributeOp};
1033    use dicom_core::value::Value;
1034    use dicom_core::{dicom_value, DataElement, PrimitiveValue, Tag, VR};
1035    use dicom_dictionary_std::tags;
1036
1037    const TEST_META_1: &[u8] = &[
1038        // magic code
1039        b'D', b'I', b'C', b'M',
1040        // File Meta Information Group Length: (0000,0002) ; UL ; 4 ; 200
1041        0x02, 0x00, 0x00, 0x00, b'U', b'L', 0x04, 0x00, 0xc8, 0x00, 0x00, 0x00,
1042        // File Meta Information Version: (0002, 0001) ; OB ; 2 ; [0x00, 0x01]
1043        0x02, 0x00, 0x01, 0x00, b'O', b'B', 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x01,
1044        // Media Storage SOP Class UID (0002, 0002) ; UI ; 26 ; "1.2.840.10008.5.1.4.1.1.1\0" (ComputedRadiographyImageStorage)
1045        0x02, 0x00, 0x02, 0x00, b'U', b'I', 0x1a, 0x00, 0x31, 0x2e, 0x32, 0x2e, 0x38, 0x34, 0x30,
1046        0x2e, 0x31, 0x30, 0x30, 0x30, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e,
1047        0x31, 0x2e, 0x31, 0x00,
1048        // Media Storage SOP Instance UID (0002, 0003) ; UI ; 56 ; "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0"
1049        0x02, 0x00, 0x03, 0x00, b'U', b'I', 0x38, 0x00, 0x31, 0x2e, 0x32, 0x2e, 0x33, 0x2e, 0x34,
1050        0x2e, 0x35, 0x2e, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x2e, 0x31, 0x32, 0x33,
1051        0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x2e, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
1052        0x2e, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2e, 0x31, 0x32, 0x33, 0x34,
1053        0x35, 0x36, 0x37, 0x00,
1054        // Transfer Syntax UID (0002, 0010) ; UI ; 20 ; "1.2.840.10008.1.2.1\0" (LittleEndianExplicit)
1055        0x02, 0x00, 0x10, 0x00, b'U', b'I', 0x14, 0x00, 0x31, 0x2e, 0x32, 0x2e, 0x38, 0x34, 0x30,
1056        0x2e, 0x31, 0x30, 0x30, 0x30, 0x38, 0x2e, 0x31, 0x2e, 0x32, 0x2e, 0x31, 0x00,
1057        // Implementation Class UID (0002, 0012) ; UI ; 20 ; "1.2.345.6.7890.1.234"
1058        0x02, 0x00, 0x12, 0x00, b'U', b'I', 0x14, 0x00, 0x31, 0x2e, 0x32, 0x2e, 0x33, 0x34, 0x35,
1059        0x2e, 0x36, 0x2e, 0x37, 0x38, 0x39, 0x30, 0x2e, 0x31, 0x2e, 0x32, 0x33, 0x34,
1060        // optional elements:
1061
1062        // Implementation Version Name (0002,0013) ; SH ; "RUSTY_DICOM_269"
1063        0x02, 0x00, 0x13, 0x00, b'S', b'H', 0x10, 0x00, 0x52, 0x55, 0x53, 0x54, 0x59, 0x5f, 0x44,
1064        0x49, 0x43, 0x4f, 0x4d, 0x5f, 0x32, 0x36, 0x39, 0x20,
1065        // Source Application Entity Title (0002, 0016) ; AE ; 0 (no data)
1066        0x02, 0x00, 0x16, 0x00, b'A', b'E', 0x00, 0x00,
1067    ];
1068
1069    #[test]
1070    fn read_meta_table_from_reader() {
1071        let mut source = TEST_META_1;
1072
1073        let table = FileMetaTable::from_reader(&mut source).unwrap();
1074
1075        let gt = FileMetaTable {
1076            information_group_length: 200,
1077            information_version: [0u8, 1u8],
1078            media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.1\0".to_owned(),
1079            media_storage_sop_instance_uid:
1080                "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".to_owned(),
1081            transfer_syntax: "1.2.840.10008.1.2.1\0".to_owned(),
1082            implementation_class_uid: "1.2.345.6.7890.1.234".to_owned(),
1083            implementation_version_name: Some("RUSTY_DICOM_269 ".to_owned()),
1084            source_application_entity_title: Some("".to_owned()),
1085            sending_application_entity_title: None,
1086            receiving_application_entity_title: None,
1087            private_information_creator_uid: None,
1088            private_information: None,
1089        };
1090
1091        assert_eq!(table.information_group_length, 200);
1092        assert_eq!(table.information_version, [0u8, 1u8]);
1093        assert_eq!(
1094            table.media_storage_sop_class_uid,
1095            "1.2.840.10008.5.1.4.1.1.1\0"
1096        );
1097        assert_eq!(
1098            table.media_storage_sop_instance_uid,
1099            "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0"
1100        );
1101        assert_eq!(table.transfer_syntax, "1.2.840.10008.1.2.1\0");
1102        assert_eq!(table.implementation_class_uid, "1.2.345.6.7890.1.234");
1103        assert_eq!(
1104            table.implementation_version_name,
1105            Some("RUSTY_DICOM_269 ".to_owned())
1106        );
1107        assert_eq!(table.source_application_entity_title, Some("".into()));
1108        assert_eq!(table.sending_application_entity_title, None);
1109        assert_eq!(table.receiving_application_entity_title, None);
1110        assert_eq!(table.private_information_creator_uid, None);
1111        assert_eq!(table.private_information, None);
1112
1113        assert_eq!(table, gt);
1114    }
1115
1116    #[test]
1117    fn create_meta_table_with_builder() {
1118        let table = FileMetaTableBuilder::new()
1119            .information_version([0, 1])
1120            .media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.1")
1121            .media_storage_sop_instance_uid(
1122                "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567",
1123            )
1124            .transfer_syntax("1.2.840.10008.1.2.1")
1125            .implementation_class_uid("1.2.345.6.7890.1.234")
1126            .implementation_version_name("RUSTY_DICOM_269")
1127            .source_application_entity_title("")
1128            .build()
1129            .unwrap();
1130
1131        let gt = FileMetaTable {
1132            information_group_length: 200,
1133            information_version: [0u8, 1u8],
1134            media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.1\0".to_owned(),
1135            media_storage_sop_instance_uid:
1136                "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".to_owned(),
1137            transfer_syntax: "1.2.840.10008.1.2.1\0".to_owned(),
1138            implementation_class_uid: "1.2.345.6.7890.1.234".to_owned(),
1139            implementation_version_name: Some("RUSTY_DICOM_269 ".to_owned()),
1140            source_application_entity_title: Some("".to_owned()),
1141            sending_application_entity_title: None,
1142            receiving_application_entity_title: None,
1143            private_information_creator_uid: None,
1144            private_information: None,
1145        };
1146
1147        assert_eq!(table.information_group_length, gt.information_group_length);
1148        assert_eq!(table, gt);
1149    }
1150
1151    /// Build a file meta table with the minimum set of parameters.
1152    #[test]
1153    fn create_meta_table_with_builder_minimal() {
1154        let table = FileMetaTableBuilder::new()
1155            .media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.1")
1156            .media_storage_sop_instance_uid(
1157                "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567",
1158            )
1159            .transfer_syntax("1.2.840.10008.1.2")
1160            .build()
1161            .unwrap();
1162
1163        let gt = FileMetaTable {
1164            information_group_length: 154
1165                + dicom_len(IMPLEMENTATION_CLASS_UID)
1166                + dicom_len(IMPLEMENTATION_VERSION_NAME),
1167            information_version: [0u8, 1u8],
1168            media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.1\0".to_owned(),
1169            media_storage_sop_instance_uid:
1170                "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".to_owned(),
1171            transfer_syntax: "1.2.840.10008.1.2\0".to_owned(),
1172            implementation_class_uid: IMPLEMENTATION_CLASS_UID.to_owned(),
1173            implementation_version_name: Some(IMPLEMENTATION_VERSION_NAME.to_owned()),
1174            source_application_entity_title: None,
1175            sending_application_entity_title: None,
1176            receiving_application_entity_title: None,
1177            private_information_creator_uid: None,
1178            private_information: None,
1179        };
1180
1181        assert_eq!(table.information_group_length, gt.information_group_length);
1182        assert_eq!(table, gt);
1183    }
1184
1185    /// Changing the transfer syntax updates the file meta group length.
1186    #[test]
1187    fn change_transfer_syntax_update_table() {
1188        let mut table = FileMetaTableBuilder::new()
1189            .media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.1")
1190            .media_storage_sop_instance_uid(
1191                "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567",
1192            )
1193            .transfer_syntax("1.2.840.10008.1.2.1")
1194            .build()
1195            .unwrap();
1196
1197        assert_eq!(
1198            table.information_group_length,
1199            156 + dicom_len(IMPLEMENTATION_CLASS_UID) + dicom_len(IMPLEMENTATION_VERSION_NAME)
1200        );
1201
1202        table.set_transfer_syntax(
1203            &dicom_transfer_syntax_registry::entries::IMPLICIT_VR_LITTLE_ENDIAN,
1204        );
1205        assert_eq!(
1206            table.information_group_length,
1207            154 + dicom_len(IMPLEMENTATION_CLASS_UID) + dicom_len(IMPLEMENTATION_VERSION_NAME)
1208        );
1209    }
1210
1211    #[test]
1212    fn read_meta_table_into_iter() {
1213        let table = FileMetaTable {
1214            information_group_length: 200,
1215            information_version: [0u8, 1u8],
1216            media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.1\0".to_owned(),
1217            media_storage_sop_instance_uid:
1218                "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".to_owned(),
1219            transfer_syntax: "1.2.840.10008.1.2.1\0".to_owned(),
1220            implementation_class_uid: "1.2.345.6.7890.1.234".to_owned(),
1221            implementation_version_name: Some("RUSTY_DICOM_269 ".to_owned()),
1222            source_application_entity_title: Some("".to_owned()),
1223            sending_application_entity_title: None,
1224            receiving_application_entity_title: None,
1225            private_information_creator_uid: None,
1226            private_information: None,
1227        };
1228
1229        assert_eq!(table.calculate_information_group_length(), 200);
1230
1231        let gt = vec![
1232            // Information Group Length
1233            DataElement::new(Tag(0x0002, 0x0000), VR::UL, dicom_value!(U32, 200)),
1234            // Information Version
1235            DataElement::new(Tag(0x0002, 0x0001), VR::OB, dicom_value!(U8, [0, 1])),
1236            // Media Storage SOP Class UID
1237            DataElement::new(
1238                Tag(0x0002, 0x0002),
1239                VR::UI,
1240                Value::Primitive("1.2.840.10008.5.1.4.1.1.1\0".into()),
1241            ),
1242            // Media Storage SOP Instance UID
1243            DataElement::new(
1244                Tag(0x0002, 0x0003),
1245                VR::UI,
1246                Value::Primitive(
1247                    "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".into(),
1248                ),
1249            ),
1250            // Transfer Syntax
1251            DataElement::new(
1252                Tag(0x0002, 0x0010),
1253                VR::UI,
1254                Value::Primitive("1.2.840.10008.1.2.1\0".into()),
1255            ),
1256            // Implementation Class UID
1257            DataElement::new(
1258                Tag(0x0002, 0x0012),
1259                VR::UI,
1260                Value::Primitive("1.2.345.6.7890.1.234".into()),
1261            ),
1262            // Implementation Version Name
1263            DataElement::new(
1264                Tag(0x0002, 0x0013),
1265                VR::SH,
1266                Value::Primitive("RUSTY_DICOM_269 ".into()),
1267            ),
1268            // Source Application Entity Title
1269            DataElement::new(Tag(0x0002, 0x0016), VR::AE, Value::Primitive("".into())),
1270        ];
1271
1272        let elems: Vec<_> = table.into_element_iter().collect();
1273        assert_eq!(elems, gt);
1274    }
1275
1276    #[test]
1277    fn update_table_with_length() {
1278        let mut table = FileMetaTable {
1279            information_group_length: 55, // dummy value
1280            information_version: [0u8, 1u8],
1281            media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.1\0".to_owned(),
1282            media_storage_sop_instance_uid:
1283                "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".to_owned(),
1284            transfer_syntax: "1.2.840.10008.1.2.1\0".to_owned(),
1285            implementation_class_uid: "1.2.345.6.7890.1.234".to_owned(),
1286            implementation_version_name: Some("RUSTY_DICOM_269 ".to_owned()),
1287            source_application_entity_title: Some("".to_owned()),
1288            sending_application_entity_title: None,
1289            receiving_application_entity_title: None,
1290            private_information_creator_uid: None,
1291            private_information: None,
1292        };
1293
1294        table.update_information_group_length();
1295
1296        assert_eq!(table.information_group_length, 200);
1297    }
1298
1299    #[test]
1300    fn table_ops() {
1301        let mut table = FileMetaTable {
1302            information_group_length: 200,
1303            information_version: [0u8, 1u8],
1304            media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.1\0".to_owned(),
1305            media_storage_sop_instance_uid:
1306                "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".to_owned(),
1307            transfer_syntax: "1.2.840.10008.1.2.1\0".to_owned(),
1308            implementation_class_uid: "1.2.345.6.7890.1.234".to_owned(),
1309            implementation_version_name: None,
1310            source_application_entity_title: None,
1311            sending_application_entity_title: None,
1312            receiving_application_entity_title: None,
1313            private_information_creator_uid: None,
1314            private_information: None,
1315        };
1316
1317        // replace does not set missing attributes
1318        table
1319            .apply(AttributeOp::new(
1320                tags::IMPLEMENTATION_VERSION_NAME,
1321                AttributeAction::ReplaceStr("MY_DICOM_1.1".into()),
1322            ))
1323            .unwrap();
1324
1325        assert_eq!(table.implementation_version_name, None);
1326
1327        // but SetStr does
1328        table
1329            .apply(AttributeOp::new(
1330                tags::IMPLEMENTATION_VERSION_NAME,
1331                AttributeAction::SetStr("MY_DICOM_1.1".into()),
1332            ))
1333            .unwrap();
1334
1335        assert_eq!(
1336            table.implementation_version_name.as_deref(),
1337            Some("MY_DICOM_1.1"),
1338        );
1339
1340        // Set (primitive) also works
1341        table
1342            .apply(AttributeOp::new(
1343                tags::SOURCE_APPLICATION_ENTITY_TITLE,
1344                AttributeAction::Set(PrimitiveValue::Str("RICOOGLE-STORAGE".into())),
1345            ))
1346            .unwrap();
1347
1348        assert_eq!(
1349            table.source_application_entity_title.as_deref(),
1350            Some("RICOOGLE-STORAGE"),
1351        );
1352
1353        // set if missing works only if value isn't set yet
1354        table
1355            .apply(AttributeOp::new(
1356                tags::SOURCE_APPLICATION_ENTITY_TITLE,
1357                AttributeAction::SetStrIfMissing("STORE-SCU".into()),
1358            ))
1359            .unwrap();
1360
1361        assert_eq!(
1362            table.source_application_entity_title.as_deref(),
1363            Some("RICOOGLE-STORAGE"),
1364        );
1365
1366        table
1367            .apply(AttributeOp::new(
1368                tags::SENDING_APPLICATION_ENTITY_TITLE,
1369                AttributeAction::SetStrIfMissing("STORE-SCU".into()),
1370            ))
1371            .unwrap();
1372
1373        assert_eq!(
1374            table.sending_application_entity_title.as_deref(),
1375            Some("STORE-SCU"),
1376        );
1377
1378        // replacing mandatory field
1379        table
1380            .apply(AttributeOp::new(
1381                tags::MEDIA_STORAGE_SOP_CLASS_UID,
1382                AttributeAction::Replace(PrimitiveValue::Str("1.2.840.10008.5.1.4.1.1.7".into())),
1383            ))
1384            .unwrap();
1385
1386        assert_eq!(
1387            table.media_storage_sop_class_uid(),
1388            "1.2.840.10008.5.1.4.1.1.7",
1389        );
1390    }
1391
1392    /// writing file meta information and reading it back
1393    /// should not fail and the the group length should be the same
1394    #[test]
1395    fn write_read_does_not_fail() {
1396        let mut table = FileMetaTable {
1397            information_group_length: 0,
1398            information_version: [0u8, 1u8],
1399            media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.7".to_owned(),
1400            media_storage_sop_instance_uid: "2.25.137731752600317795446120660167595746868".to_owned(),
1401            transfer_syntax: "1.2.840.10008.1.2.4.91".to_owned(),
1402            implementation_class_uid: "2.25.305828488182831875890203105390285383139".to_owned(),
1403            implementation_version_name: Some("MYTOOL100".to_owned()),
1404            source_application_entity_title: Some("RUSTY".to_owned()),
1405            receiving_application_entity_title: None,
1406            sending_application_entity_title: None,
1407            private_information_creator_uid: None,
1408            private_information: None,
1409        };
1410
1411        table.update_information_group_length();
1412
1413        let mut buf = vec![b'D', b'I', b'C', b'M'];
1414        table.write(&mut buf).unwrap();
1415
1416        let table2 = FileMetaTable::from_reader(&mut buf.as_slice())
1417            .expect("Should not fail to read the table from the written data");
1418
1419        assert_eq!(table.information_group_length, table2.information_group_length);
1420    }
1421}