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