dicom_ul/pdu/
reader.rs

1/// PDU reader module
2use crate::pdu::*;
3use byteordered::byteorder::{BigEndian, ReadBytesExt};
4use dicom_encoding::text::{DefaultCharacterSetCodec, TextCodec};
5use snafu::{ensure, Backtrace, OptionExt, ResultExt, Snafu};
6use std::io::{Cursor, ErrorKind, Read, Seek, SeekFrom};
7use tracing::warn;
8
9/// The default maximum PDU size
10pub const DEFAULT_MAX_PDU: u32 = 16_384;
11
12/// The minimum PDU size,
13/// as specified by the standard
14pub const MINIMUM_PDU_SIZE: u32 = 4_096;
15
16/// The maximum PDU size,
17/// as specified by the standard
18pub const MAXIMUM_PDU_SIZE: u32 = 131_072;
19
20/// The length of the PDU header in bytes,
21/// comprising the PDU type (1 byte),
22/// reserved byte (1 byte),
23/// and PDU length (4 bytes).
24pub const PDU_HEADER_SIZE: u32 = 6;
25
26#[derive(Debug, Snafu)]
27#[non_exhaustive]
28pub enum Error {
29    #[snafu(display("Invalid max PDU length {}", max_pdu_length))]
30    InvalidMaxPdu {
31        max_pdu_length: u32,
32        backtrace: Backtrace,
33    },
34
35    #[snafu(display("No PDU available"))]
36    NoPduAvailable { backtrace: Backtrace },
37
38    #[snafu(display("Could not read PDU"))]
39    ReadPdu {
40        source: std::io::Error,
41        backtrace: Backtrace,
42    },
43
44    #[snafu(display("Could not read PDU item"))]
45    ReadPduItem {
46        source: std::io::Error,
47        backtrace: Backtrace,
48    },
49
50    #[snafu(display("Could not read PDU field `{}`", field))]
51    ReadPduField {
52        field: &'static str,
53        source: std::io::Error,
54        backtrace: Backtrace,
55    },
56
57    #[snafu(display("Invalid item length {} (must be >=2)", length))]
58    InvalidItemLength { length: u32 },
59
60    #[snafu(display("Could not read {} reserved bytes", bytes))]
61    ReadReserved {
62        bytes: u32,
63        source: std::io::Error,
64        backtrace: Backtrace,
65    },
66
67    #[snafu(display(
68        "Incoming pdu was too large: length {}, maximum is {}",
69        pdu_length,
70        max_pdu_length
71    ))]
72    PduTooLarge {
73        pdu_length: u32,
74        max_pdu_length: u32,
75        backtrace: Backtrace,
76    },
77    #[snafu(display("PDU contained an invalid value {:?}", var_item))]
78    InvalidPduVariable {
79        var_item: PduVariableItem,
80        backtrace: Backtrace,
81    },
82    #[snafu(display("Multiple transfer syntaxes were accepted"))]
83    MultipleTransferSyntaxesAccepted { backtrace: Backtrace },
84    #[snafu(display("Invalid reject source or reason"))]
85    InvalidRejectSourceOrReason { backtrace: Backtrace },
86    #[snafu(display("Invalid abort service provider"))]
87    InvalidAbortSourceOrReason { backtrace: Backtrace },
88    #[snafu(display("Invalid presentation context result reason"))]
89    InvalidPresentationContextResultReason { backtrace: Backtrace },
90    #[snafu(display("invalid transfer syntax sub-item"))]
91    InvalidTransferSyntaxSubItem { backtrace: Backtrace },
92    #[snafu(display("unknown presentation context sub-item"))]
93    UnknownPresentationContextSubItem { backtrace: Backtrace },
94    #[snafu(display("Could not decode text field `{}`", field))]
95    DecodeText {
96        field: &'static str,
97        #[snafu(backtrace)]
98        source: dicom_encoding::text::DecodeTextError,
99    },
100    #[snafu(display("Missing application context name"))]
101    MissingApplicationContextName { backtrace: Backtrace },
102    #[snafu(display("Missing abstract syntax"))]
103    MissingAbstractSyntax { backtrace: Backtrace },
104    #[snafu(display("Missing transfer syntax"))]
105    MissingTransferSyntax { backtrace: Backtrace },
106}
107
108pub type Result<T> = std::result::Result<T, Error>;
109
110pub fn read_pdu<R>(reader: &mut R, max_pdu_length: u32, strict: bool) -> Result<Pdu>
111where
112    R: Read,
113{
114    ensure!(
115        (MINIMUM_PDU_SIZE..=MAXIMUM_PDU_SIZE).contains(&max_pdu_length),
116        InvalidMaxPduSnafu { max_pdu_length }
117    );
118
119    // If we can't read 2 bytes here, that means that there is no PDU
120    // available. Normally, we want to just return the UnexpectedEof error. However,
121    // this method can block and wake up when stream is closed, so in this case, we
122    // want to know if we had trouble even beginning to read a PDU. We still return
123    // UnexpectedEof if we get after we have already began reading a PDU message.
124    let mut bytes = [0; 2];
125    if let Err(e) = reader.read_exact(&mut bytes) {
126        ensure!(e.kind() != ErrorKind::UnexpectedEof, NoPduAvailableSnafu);
127        return Err(e).context(ReadPduFieldSnafu { field: "type" });
128    }
129
130    let pdu_type = bytes[0];
131    let pdu_length = reader
132        .read_u32::<BigEndian>()
133        .context(ReadPduFieldSnafu { field: "length" })?;
134
135    // Check max_pdu_length
136    if strict {
137        ensure!(
138            pdu_length <= max_pdu_length,
139            PduTooLargeSnafu {
140                pdu_length,
141                max_pdu_length
142            }
143        );
144    } else if pdu_length > max_pdu_length {
145        ensure!(
146            pdu_length <= MAXIMUM_PDU_SIZE,
147            PduTooLargeSnafu {
148                pdu_length,
149                max_pdu_length: MAXIMUM_PDU_SIZE
150            }
151        );
152        tracing::warn!(
153            "Incoming pdu was too large: length {}, maximum is {}",
154            pdu_length,
155            max_pdu_length
156        );
157    }
158
159    let bytes = read_n(reader, pdu_length as usize).context(ReadPduSnafu)?;
160    let mut cursor = Cursor::new(bytes);
161    let codec = DefaultCharacterSetCodec;
162
163    match pdu_type {
164        0x01 => {
165            // A-ASSOCIATE-RQ PDU Structure
166
167            let mut application_context_name: Option<String> = None;
168            let mut presentation_contexts = vec![];
169            let mut user_variables = vec![];
170
171            // 7-8 - Protocol-version - This two byte field shall use one bit to identify each
172            // version of the DICOM UL protocol supported by the calling end-system. This is
173            // Version 1 and shall be identified with bit 0 set. A receiver of this PDU
174            // implementing only this version of the DICOM UL protocol shall only test that bit
175            // 0 is set.
176            let protocol_version = cursor.read_u16::<BigEndian>().context(ReadPduFieldSnafu {
177                field: "Protocol-version",
178            })?;
179
180            // 9-10 - Reserved - This reserved field shall be sent with a value 0000H but not
181            // tested to this value when received.
182            cursor
183                .read_u16::<BigEndian>()
184                .context(ReadReservedSnafu { bytes: 2_u32 })?;
185
186            // 11-26 - Called-AE-title - Destination DICOM Application Name. It shall be encoded
187            // as 16 characters as defined by the ISO 646:1990-Basic G0 Set with leading and
188            // trailing spaces (20H) being non-significant. The value made of 16 spaces (20H)
189            // meaning "no Application Name specified" shall not be used. For a complete
190            // description of the use of this field, see Section 7.1.1.4.
191            let mut ae_bytes = [0; 16];
192            cursor
193                .read_exact(&mut ae_bytes)
194                .context(ReadPduFieldSnafu {
195                    field: "Called-AE-title",
196                })?;
197            let called_ae_title = codec
198                .decode(&ae_bytes)
199                .context(DecodeTextSnafu {
200                    field: "Called-AE-title",
201                })?
202                .trim()
203                .to_string();
204
205            // 27-42 - Calling-AE-title - Source DICOM Application Name. It shall be encoded as
206            // 16 characters as defined by the ISO 646:1990-Basic G0 Set with leading and
207            // trailing spaces (20H) being non-significant. The value made of 16 spaces (20H)
208            // meaning "no Application Name specified" shall not be used. For a complete
209            // description of the use of this field, see Section 7.1.1.3.
210            let mut ae_bytes = [0; 16];
211            cursor
212                .read_exact(&mut ae_bytes)
213                .context(ReadPduFieldSnafu {
214                    field: "Calling-AE-title",
215                })?;
216            let calling_ae_title = codec
217                .decode(&ae_bytes)
218                .context(DecodeTextSnafu {
219                    field: "Calling-AE-title",
220                })?
221                .trim()
222                .to_string();
223
224            // 43-74 - Reserved - This reserved field shall be sent with a value 00H for all
225            // bytes but not tested to this value when received
226            cursor
227                .seek(SeekFrom::Current(32))
228                .context(ReadReservedSnafu { bytes: 32_u32 })?;
229
230            // 75-xxx - Variable items - This variable field shall contain the following items:
231            // one Application Context Item, one or more Presentation Context Items and one User
232            // Information Item. For a complete description of the use of these items see
233            // Section 7.1.1.2, Section 7.1.1.13, and Section 7.1.1.6.
234            while cursor.position() < cursor.get_ref().len() as u64 {
235                match read_pdu_variable(&mut cursor, &codec)? {
236                    PduVariableItem::ApplicationContext(val) => {
237                        application_context_name = Some(val);
238                    }
239                    PduVariableItem::PresentationContextProposed(val) => {
240                        presentation_contexts.push(val);
241                    }
242                    PduVariableItem::UserVariables(val) => {
243                        user_variables = val;
244                    }
245                    var_item => {
246                        return InvalidPduVariableSnafu { var_item }.fail();
247                    }
248                }
249            }
250
251            Ok(Pdu::AssociationRQ(AssociationRQ {
252                protocol_version,
253                application_context_name: application_context_name
254                    .context(MissingApplicationContextNameSnafu)?,
255                called_ae_title,
256                calling_ae_title,
257                presentation_contexts,
258                user_variables,
259            }))
260        }
261        0x02 => {
262            // A-ASSOCIATE-AC PDU Structure
263
264            let mut application_context_name: Option<String> = None;
265            let mut presentation_contexts = vec![];
266            let mut user_variables = vec![];
267
268            // 7-8 - Protocol-version - This two byte field shall use one bit to identify each
269            // version of the DICOM UL protocol supported by the calling end-system. This is
270            // Version 1 and shall be identified with bit 0 set. A receiver of this PDU
271            // implementing only this version of the DICOM UL protocol shall only test that bit
272            // 0 is set.
273            let protocol_version = cursor.read_u16::<BigEndian>().context(ReadPduFieldSnafu {
274                field: "Protocol-version",
275            })?;
276
277            // 9-10 - Reserved - This reserved field shall be sent with a value 0000H but not
278            // tested to this value when received.
279            cursor
280                .read_u16::<BigEndian>()
281                .context(ReadReservedSnafu { bytes: 2_u32 })?;
282
283            // 11-26 - Reserved - This reserved field shall be sent with a value identical to
284            // the value received in the same field of the A-ASSOCIATE-RQ PDU, but its value
285            // shall not be tested when received.
286            let mut ae_bytes = [0; 16];
287            cursor
288                .read_exact(&mut ae_bytes)
289                .context(ReadPduFieldSnafu {
290                    field: "Called-AE-title",
291                })?;
292            let called_ae_title = codec
293                .decode(&ae_bytes)
294                .context(DecodeTextSnafu {
295                    field: "Called-AE-title",
296                })?
297                .trim()
298                .to_string();
299
300            // 27-42 - Reserved - This reserved field shall be sent with a value identical to
301            // the value received in the same field of the A-ASSOCIATE-RQ PDU, but its value
302            // shall not be tested when received.
303            let mut ae_bytes = [0; 16];
304            cursor
305                .read_exact(&mut ae_bytes)
306                .context(ReadPduFieldSnafu {
307                    field: "Calling-AE-title",
308                })?;
309            let calling_ae_title = codec
310                .decode(&ae_bytes)
311                .context(DecodeTextSnafu {
312                    field: "Calling-AE-title",
313                })?
314                .trim()
315                .to_string();
316
317            // 43-74 - Reserved - This reserved field shall be sent with a value identical to
318            // the value received in the same field of the A-ASSOCIATE-RQ PDU, but its value
319            // shall not be tested when received.
320            cursor
321                .seek(SeekFrom::Current(32))
322                .context(ReadReservedSnafu { bytes: 32_u32 })?;
323
324            // 75-xxx - Variable items - This variable field shall contain the following items:
325            // one Application Context Item, one or more Presentation Context Item(s) and one
326            // User Information Item. For a complete description of these items see Section
327            // 7.1.1.2, Section 7.1.1.14, and Section 7.1.1.6.
328            while cursor.position() < cursor.get_ref().len() as u64 {
329                match read_pdu_variable(&mut cursor, &codec)? {
330                    PduVariableItem::ApplicationContext(val) => {
331                        application_context_name = Some(val);
332                    }
333                    PduVariableItem::PresentationContextResult(val) => {
334                        presentation_contexts.push(val);
335                    }
336                    PduVariableItem::UserVariables(val) => {
337                        user_variables = val;
338                    }
339                    var_item => {
340                        return InvalidPduVariableSnafu { var_item }.fail();
341                    }
342                }
343            }
344
345            Ok(Pdu::AssociationAC(AssociationAC {
346                protocol_version,
347                application_context_name: application_context_name
348                    .context(MissingApplicationContextNameSnafu)?,
349                called_ae_title,
350                calling_ae_title,
351                presentation_contexts,
352                user_variables,
353            }))
354        }
355        0x03 => {
356            // A-ASSOCIATE-RJ PDU Structure
357
358            // 7 - Reserved - This reserved field shall be sent with a value 00H but not tested to
359            // this value when received.
360            cursor
361                .read_u8()
362                .context(ReadReservedSnafu { bytes: 1_u32 })?;
363
364            // 8 - Result - This Result field shall contain an integer value encoded as an unsigned
365            // binary number. One of the following values shall be used:
366            //   1 - rejected-permanent
367            //   2 - rejected-transient
368            let result = AssociationRJResult::from(
369                cursor
370                    .read_u8()
371                    .context(ReadPduFieldSnafu { field: "Result" })?,
372            )
373            .context(InvalidRejectSourceOrReasonSnafu)?;
374
375            // 9 - Source - This Source field shall contain an integer value encoded as an unsigned
376            // binary number. One of the following values shall be used:   1 - DICOM UL
377            // service-user   2 - DICOM UL service-provider (ACSE related function)
378            //   3 - DICOM UL service-provider (Presentation related function)
379            // 10 - Reason/Diag. - This field shall contain an integer value encoded as an unsigned
380            // binary number.   If the Source field has the value (1) "DICOM UL
381            // service-user", it shall take one of the following:
382            //     1 - no-reason-given
383            //     2 - application-context-name-not-supported
384            //     3 - calling-AE-title-not-recognized
385            //     4-6 - reserved
386            //     7 - called-AE-title-not-recognized
387            //     8-10 - reserved
388            //   If the Source field has the value (2) "DICOM UL service provided (ACSE related
389            // function)", it shall take one of the following:     1 - no-reason-given
390            //     2 - protocol-version-not-supported
391            //   If the Source field has the value (3) "DICOM UL service provided (Presentation
392            // related function)", it shall take one of the following:     0 - reserved
393            //     1 - temporary-congestio
394            //     2 - local-limit-exceeded
395            //     3-7 - reserved
396            let source = AssociationRJSource::from(
397                cursor
398                    .read_u8()
399                    .context(ReadPduFieldSnafu { field: "Source" })?,
400                cursor.read_u8().context(ReadPduFieldSnafu {
401                    field: "Reason/Diag.",
402                })?,
403            )
404            .context(InvalidRejectSourceOrReasonSnafu)?;
405
406            Ok(Pdu::AssociationRJ(AssociationRJ { result, source }))
407        }
408        0x04 => {
409            // P-DATA-TF PDU Structure
410
411            // 7-xxx - Presentation-data-value Item(s) - This variable data field shall contain one
412            // or more Presentation-data-value Items(s). For a complete description of the use of
413            // this field see Section 9.3.5.1
414            let mut values = vec![];
415            while cursor.position() < cursor.get_ref().len() as u64 {
416                // Presentation Data Value Item Structure
417
418                // 1-4 - Item-length - This Item-length shall be the number of bytes from the first
419                // byte of the following field to the last byte of the Presentation-data-value
420                // field. It shall be encoded as an unsigned binary number.
421                let item_length = cursor.read_u32::<BigEndian>().context(ReadPduFieldSnafu {
422                    field: "Item-Length",
423                })?;
424
425                ensure!(
426                    item_length >= 2,
427                    InvalidItemLengthSnafu {
428                        length: item_length
429                    }
430                );
431
432                // 5 - Presentation-context-ID - Presentation-context-ID values shall be odd
433                // integers between 1 and 255, encoded as an unsigned binary number. For a complete
434                // description of the use of this field see Section 7.1.1.13.
435                let presentation_context_id = cursor.read_u8().context(ReadPduFieldSnafu {
436                    field: "Presentation-context-ID",
437                })?;
438
439                // 6-xxx - Presentation-data-value - This Presentation-data-value field shall
440                // contain DICOM message information (command and/or data set) with a message
441                // control header. For a complete description of the use of this field see Annex E.
442
443                // The Message Control Header shall be made of one byte with the least significant
444                // bit (bit 0) taking one of the following values: If bit 0 is set
445                // to 1, the following fragment shall contain Message Command information.
446                // If bit 0 is set to 0, the following fragment shall contain Message Data Set
447                // information. The next least significant bit (bit 1) shall be
448                // defined by the following rules: If bit 1 is set to 1, the
449                // following fragment shall contain the last fragment of a Message Data Set or of a
450                // Message Command. If bit 1 is set to 0, the following fragment
451                // does not contain the last fragment of a Message Data Set or of a Message Command.
452                let header = cursor.read_u8().context(ReadPduFieldSnafu {
453                    field: "Message Control Header",
454                })?;
455
456                let value_type = if header & 0x01 > 0 {
457                    PDataValueType::Command
458                } else {
459                    PDataValueType::Data
460                };
461                let is_last = (header & 0x02) > 0;
462
463                let data =
464                    read_n(&mut cursor, (item_length - 2) as usize).context(ReadPduFieldSnafu {
465                        field: "Presentation-data-value",
466                    })?;
467
468                values.push(PDataValue {
469                    presentation_context_id,
470                    value_type,
471                    is_last,
472                    data,
473                })
474            }
475
476            Ok(Pdu::PData { data: values })
477        }
478        0x05 => {
479            // A-RELEASE-RQ PDU Structure
480
481            // 7-10 - Reserved - This reserved field shall be sent with a value 00000000H but not
482            // tested to this value when received.
483            cursor
484                .seek(SeekFrom::Current(4))
485                .context(ReadReservedSnafu { bytes: 4_u32 })?;
486
487            Ok(Pdu::ReleaseRQ)
488        }
489        0x06 => {
490            // A-RELEASE-RP PDU Structure
491
492            // 7-10 - Reserved - This reserved field shall be sent with a value 00000000H but not
493            // tested to this value when received.
494            cursor
495                .seek(SeekFrom::Current(4))
496                .context(ReadReservedSnafu { bytes: 4_u32 })?;
497
498            Ok(Pdu::ReleaseRP)
499        }
500        0x07 => {
501            // A-ABORT PDU Structure
502
503            // 7 - Reserved - This reserved field shall be sent with a value 00H but not tested to
504            // this value when received.
505            // 8 - Reserved - This reserved field shall be sent with a value 00H but not tested to
506            // this value when received.
507            let mut buf = [0u8; 2];
508            cursor
509                .read_exact(&mut buf)
510                .context(ReadReservedSnafu { bytes: 2_u32 })?;
511
512            // 9 - Source - This Source field shall contain an integer value encoded as an unsigned
513            // binary number. One of the following values shall be used:
514            // - 0 - DICOM UL service-user (initiated abort)
515            // - 1 - reserved
516            // - 2 - DICOM UL service-provider (initiated abort)
517            // 10 - Reason/Diag - This field shall contain an integer value encoded as an unsigned
518            // binary number. If the Source field has the value (2) "DICOM UL
519            // service-provider", it shall take one of the following:
520            // - 0 - reason-not-specified1 - unrecognized-PDU
521            // - 2 - unexpected-PDU
522            // - 3 - reserved
523            // - 4 - unrecognized-PDU parameter
524            // - 5 - unexpected-PDU parameter
525            // - 6 - invalid-PDU-parameter value
526            let source = AbortRQSource::from(
527                cursor
528                    .read_u8()
529                    .context(ReadPduFieldSnafu { field: "Source" })?,
530                cursor.read_u8().context(ReadPduFieldSnafu {
531                    field: "Reason/Diag",
532                })?,
533            )
534            .context(InvalidAbortSourceOrReasonSnafu)?;
535
536            Ok(Pdu::AbortRQ { source })
537        }
538        _ => {
539            let data = read_n(&mut cursor, pdu_length as usize)
540                .context(ReadPduFieldSnafu { field: "Unknown" })?;
541            Ok(Pdu::Unknown { pdu_type, data })
542        }
543    }
544}
545
546fn read_n<R>(reader: &mut R, bytes_to_read: usize) -> std::io::Result<Vec<u8>>
547where
548    R: Read,
549{
550    let mut result = Vec::new();
551    reader.take(bytes_to_read as u64).read_to_end(&mut result)?;
552    Ok(result)
553}
554
555fn read_pdu_variable<R>(reader: &mut R, codec: &dyn TextCodec) -> Result<PduVariableItem>
556where
557    R: Read,
558{
559    // 1 - Item-type - XXH
560    let item_type = reader
561        .read_u8()
562        .context(ReadPduFieldSnafu { field: "Item-type" })?;
563
564    // 2 - Reserved
565    reader
566        .read_u8()
567        .context(ReadReservedSnafu { bytes: 1_u32 })?;
568
569    // 3-4 - Item-length
570    let item_length = reader.read_u16::<BigEndian>().context(ReadPduFieldSnafu {
571        field: "Item-length",
572    })?;
573
574    let bytes = read_n(reader, item_length as usize).context(ReadPduItemSnafu)?;
575    let mut cursor = Cursor::new(bytes);
576
577    match item_type {
578        0x10 => {
579            // Application Context Item Structure
580
581            // 5-xxx - Application-context-name - A valid Application-context-name shall be encoded
582            // as defined in Annex F. For a description of the use of this field see Section
583            // 7.1.1.2. Application-context-names are structured as UIDs as defined in PS3.5 (see
584            // Annex A for an overview of this concept). DICOM Application-context-names are
585            // registered in PS3.7.
586            let val = codec
587                .decode(&cursor.into_inner())
588                .context(DecodeTextSnafu {
589                    field: "Application-context-name",
590                })?;
591            Ok(PduVariableItem::ApplicationContext(val))
592        }
593        0x20 => {
594            // Presentation Context Item Structure (proposed)
595
596            let mut abstract_syntax: Option<String> = None;
597            let mut transfer_syntaxes = vec![];
598
599            // 5 - Presentation-context-ID - Presentation-context-ID values shall be odd integers
600            // between 1 and 255, encoded as an unsigned binary number. For a complete description
601            // of the use of this field see Section 7.1.1.13.
602            let presentation_context_id = cursor.read_u8().context(ReadPduFieldSnafu {
603                field: "Presentation-context-ID",
604            })?;
605
606            // 6 - Reserved - This reserved field shall be sent with a value 00H but not tested to
607            // this value when received.
608            cursor
609                .read_u8()
610                .context(ReadReservedSnafu { bytes: 1_u32 })?;
611
612            // 7 - Reserved - This reserved field shall be sent with a value 00H but not tested to
613            // this value when received.
614            cursor
615                .read_u8()
616                .context(ReadReservedSnafu { bytes: 1_u32 })?;
617
618            // 8 - Reserved - This reserved field shall be sent with a value 00H but not tested to
619            // this value when received.
620            cursor
621                .read_u8()
622                .context(ReadReservedSnafu { bytes: 1_u32 })?;
623
624            // 9-xxx - Abstract/Transfer Syntax Sub-Items - This variable field shall contain the
625            // following sub-items: one Abstract Syntax and one or more Transfer Syntax(es). For a
626            // complete description of the use and encoding of these sub-items see Section 9.3.2.2.1
627            // and Section 9.3.2.2.2.
628            while cursor.position() < cursor.get_ref().len() as u64 {
629                // 1 - Item-type - XXH
630                let item_type = cursor
631                    .read_u8()
632                    .context(ReadPduFieldSnafu { field: "Item-type" })?;
633
634                // 2 - Reserved - This reserved field shall be sent with a value 00H but not tested
635                // to this value when received.
636                cursor
637                    .read_u8()
638                    .context(ReadReservedSnafu { bytes: 1_u32 })?;
639
640                // 3-4 - Item-length
641                let item_length = cursor.read_u16::<BigEndian>().context(ReadPduFieldSnafu {
642                    field: "Item-length",
643                })?;
644
645                match item_type {
646                    0x30 => {
647                        // Abstract Syntax Sub-Item Structure
648
649                        // 5-xxx - Abstract-syntax-name - This variable field shall contain the
650                        // Abstract-syntax-name related to the proposed presentation context. A
651                        // valid Abstract-syntax-name shall be encoded as defined in Annex F. For a
652                        // description of the use of this field see Section 7.1.1.13.
653                        // Abstract-syntax-names are structured as UIDs as defined in PS3.5 (see
654                        // Annex B for an overview of this concept). DICOM Abstract-syntax-names are
655                        // registered in PS3.4.
656                        abstract_syntax = Some(
657                            codec
658                                .decode(&read_n(&mut cursor, item_length as usize).context(
659                                    ReadPduFieldSnafu {
660                                        field: "Abstract-syntax-name",
661                                    },
662                                )?)
663                                .context(DecodeTextSnafu {
664                                    field: "Abstract-syntax-name",
665                                })?
666                                .trim()
667                                .to_string(),
668                        );
669                    }
670                    0x40 => {
671                        // Transfer Syntax Sub-Item Structure
672
673                        // 5-xxx - Transfer-syntax-name(s) - This variable field shall contain the
674                        // Transfer-syntax-name proposed for this presentation context. A valid
675                        // Transfer-syntax-name shall be encoded as defined in Annex F. For a
676                        // description of the use of this field see Section 7.1.1.13.
677                        // Transfer-syntax-names are structured as UIDs as defined in PS3.5 (see
678                        // Annex B for an overview of this concept). DICOM Transfer-syntax-names are
679                        // registered in PS3.5.
680                        transfer_syntaxes.push(
681                            codec
682                                .decode(&read_n(&mut cursor, item_length as usize).context(
683                                    ReadPduFieldSnafu {
684                                        field: "Transfer-syntax-name",
685                                    },
686                                )?)
687                                .context(DecodeTextSnafu {
688                                    field: "Transfer-syntax-name",
689                                })?
690                                .trim()
691                                .to_string(),
692                        );
693                    }
694                    _ => {
695                        return UnknownPresentationContextSubItemSnafu.fail();
696                    }
697                }
698            }
699
700            Ok(PduVariableItem::PresentationContextProposed(
701                PresentationContextProposed {
702                    id: presentation_context_id,
703                    abstract_syntax: abstract_syntax.context(MissingAbstractSyntaxSnafu)?,
704                    transfer_syntaxes,
705                },
706            ))
707        }
708        0x21 => {
709            // Presentation Context Item Structure (result)
710
711            let mut transfer_syntax: Option<String> = None;
712
713            // 5 - Presentation-context-ID - Presentation-context-ID values shall be odd integers
714            // between 1 and 255, encoded as an unsigned binary number. For a complete description
715            // of the use of this field see Section 7.1.1.13.
716            let presentation_context_id = cursor.read_u8().context(ReadPduFieldSnafu {
717                field: "Presentation-context-ID",
718            })?;
719
720            // 6 - Reserved - This reserved field shall be sent with a value 00H but not tested to
721            // this value when received.
722            cursor
723                .read_u8()
724                .context(ReadReservedSnafu { bytes: 1_u32 })?;
725
726            // 7 - Result/Reason - This Result/Reason field shall contain an integer value encoded
727            // as an unsigned binary number. One of the following values shall be used:
728            //   0 - acceptance
729            //   1 - user-rejection
730            //   2 - no-reason (provider rejection)
731            //   3 - abstract-syntax-not-supported (provider rejection)
732            //   4 - transfer-syntaxes-not-supported (provider rejection)
733            let reason = PresentationContextResultReason::from(cursor.read_u8().context(
734                ReadPduFieldSnafu {
735                    field: "Result/Reason",
736                },
737            )?)
738            .context(InvalidPresentationContextResultReasonSnafu)?;
739
740            // 8 - Reserved - This reserved field shall be sent with a value 00H but not tested to
741            // this value when received.
742            cursor
743                .read_u8()
744                .context(ReadReservedSnafu { bytes: 1_u32 })?;
745
746            // 9-xxx - Transfer syntax sub-item - This variable field shall contain one Transfer
747            // Syntax Sub-Item. When the Result/Reason field has a value other than acceptance (0),
748            // this field shall not be significant and its value shall not be tested when received.
749            // For a complete description of the use and encoding of this item see Section
750            // 9.3.3.2.1.
751            while cursor.position() < cursor.get_ref().len() as u64 {
752                // 1 - Item-type - XXH
753                let item_type = cursor
754                    .read_u8()
755                    .context(ReadPduFieldSnafu { field: "Item-type" })?;
756
757                // 2 - Reserved - This reserved field shall be sent with a value 00H but not tested
758                // to this value when received.
759                cursor
760                    .read_u8()
761                    .context(ReadReservedSnafu { bytes: 1_u32 })?;
762
763                // 3-4 - Item-length
764                let item_length = cursor.read_u16::<BigEndian>().context(ReadPduFieldSnafu {
765                    field: "Item-length",
766                })?;
767
768                match item_type {
769                    0x40 => {
770                        // Transfer Syntax Sub-Item Structure
771
772                        // 5-xxx - Transfer-syntax-name(s) - This variable field shall contain the
773                        // Transfer-syntax-name proposed for this presentation context. A valid
774                        // Transfer-syntax-name shall be encoded as defined in Annex F. For a
775                        // description of the use of this field see Section 7.1.1.13.
776                        // Transfer-syntax-names are structured as UIDs as defined in PS3.5 (see
777                        // Annex B for an overview of this concept). DICOM Transfer-syntax-names are
778                        // registered in PS3.5.
779                        match transfer_syntax {
780                            Some(_) => {
781                                // Multiple transfer syntax values cannot be proposed.
782                                return MultipleTransferSyntaxesAcceptedSnafu.fail();
783                            }
784                            None => {
785                                transfer_syntax = Some(
786                                    codec
787                                        .decode(
788                                            &read_n(&mut cursor, item_length as usize).context(
789                                                ReadPduFieldSnafu {
790                                                    field: "Transfer-syntax-name",
791                                                },
792                                            )?,
793                                        )
794                                        .context(DecodeTextSnafu {
795                                            field: "Transfer-syntax-name",
796                                        })?
797                                        .trim()
798                                        .to_string(),
799                                );
800                            }
801                        }
802                    }
803                    _ => {
804                        return InvalidTransferSyntaxSubItemSnafu.fail();
805                    }
806                }
807            }
808
809            Ok(PduVariableItem::PresentationContextResult(
810                PresentationContextResult {
811                    id: presentation_context_id,
812                    reason,
813                    transfer_syntax: transfer_syntax.context(MissingTransferSyntaxSnafu)?,
814                },
815            ))
816        }
817        0x50 => {
818            // User Information Item Structure
819
820            let mut user_variables = vec![];
821
822            // 5-xxx - User-data - This variable field shall contain User-data sub-items as defined
823            // by the DICOM Application Entity. The structure and content of these sub-items is
824            // defined in Annex D.
825            while cursor.position() < cursor.get_ref().len() as u64 {
826                // 1 - Item-type - XXH
827                let item_type = cursor
828                    .read_u8()
829                    .context(ReadPduFieldSnafu { field: "Item-type" })?;
830
831                // 2 - Reserved
832                cursor
833                    .read_u8()
834                    .context(ReadReservedSnafu { bytes: 1_u32 })?;
835
836                // 3-4 - Item-length
837                let item_length = cursor.read_u16::<BigEndian>().context(ReadPduFieldSnafu {
838                    field: "Item-length",
839                })?;
840
841                match item_type {
842                    0x51 => {
843                        // Maximum Length Sub-Item Structure
844
845                        // 5-8 - Maximum-length-received - This parameter allows the
846                        // association-requestor to restrict the maximum length of the variable
847                        // field of the P-DATA-TF PDUs sent by the acceptor on the association once
848                        // established. This length value is indicated as a number of bytes encoded
849                        // as an unsigned binary number. The value of (0) indicates that no maximum
850                        // length is specified. This maximum length value shall never be exceeded by
851                        // the PDU length values used in the PDU-length field of the P-DATA-TF PDUs
852                        // received by the association-requestor. Otherwise, it shall be a protocol
853                        // error.
854                        user_variables.push(UserVariableItem::MaxLength(
855                            cursor.read_u32::<BigEndian>().context(ReadPduFieldSnafu {
856                                field: "Maximum-length-received",
857                            })?,
858                        ));
859                    }
860                    0x52 => {
861                        // Implementation Class UID Sub-Item Structure
862
863                        // 5 - xxx - Implementation-class-uid - This variable field shall contain
864                        // the Implementation-class-uid of the Association-acceptor as defined in
865                        // Section D.3.3.2. The Implementation-class-uid field is structured as a
866                        // UID as defined in PS3.5.
867                        let implementation_class_uid = codec
868                            .decode(&read_n(&mut cursor, item_length as usize).context(
869                                ReadPduFieldSnafu {
870                                    field: "Implementation-class-uid",
871                                },
872                            )?)
873                            .context(DecodeTextSnafu {
874                                field: "Implementation-class-uid",
875                            })?
876                            .trim()
877                            .to_string();
878                        user_variables.push(UserVariableItem::ImplementationClassUID(
879                            implementation_class_uid,
880                        ));
881                    }
882                    0x55 => {
883                        // Implementation Version Name Structure
884
885                        // 5 - xxx - Implementation-version-name - This variable field shall contain
886                        // the Implementation-version-name of the Association-acceptor as defined in
887                        // Section D.3.3.2. It shall be encoded as a string of 1 to 16 ISO 646:1990
888                        // (basic G0 set) characters.
889                        let implementation_version_name = codec
890                            .decode(&read_n(&mut cursor, item_length as usize).context(
891                                ReadPduFieldSnafu {
892                                    field: "Implementation-version-name",
893                                },
894                            )?)
895                            .context(DecodeTextSnafu {
896                                field: "Implementation-version-name",
897                            })?
898                            .trim()
899                            .to_string();
900                        user_variables.push(UserVariableItem::ImplementationVersionName(
901                            implementation_version_name,
902                        ));
903                    }
904                    0x56 => {
905                        // SOP Class Extended Negotiation Sub-Item
906
907                        // 5-6 - SOP-class-uid-length - The SOP-class-uid-length shall be the number
908                        // of bytes from the first byte of the following field to the last byte of the
909                        // SOP-class-uid field. It shall be encoded as an unsigned binary number.
910                        let sop_class_uid_length =
911                            cursor.read_u16::<BigEndian>().context(ReadPduFieldSnafu {
912                                field: "SOP-class-uid-length",
913                            })?;
914
915                        // 7 - xxx - SOP-class-uid - The SOP Class or Meta SOP Class identifier
916                        // encoded as a UID as defined in Section 9 “Unique Identifiers (UIDs)” in PS3.5.
917                        let sop_class_uid = codec
918                            .decode(&read_n(&mut cursor, sop_class_uid_length as usize).context(
919                                ReadPduFieldSnafu {
920                                    field: "SOP-class-uid",
921                                },
922                            )?)
923                            .context(DecodeTextSnafu {
924                                field: "SOP-class-uid",
925                            })?
926                            .trim()
927                            .to_string();
928
929                        let data_length =
930                            cursor.read_u16::<BigEndian>().context(ReadPduFieldSnafu {
931                                field: "Service-class-application-information-length",
932                            })?;
933
934                        // xxx-xxx - Service-class-application-information -This field shall contain
935                        // the application information specific to the Service Class specification
936                        // identified by the SOP-class-uid. The semantics and value of this field
937                        // is defined in the identified Service Class specification.
938                        let data = read_n(&mut cursor, data_length as usize).context(
939                            ReadPduFieldSnafu {
940                                field: "Service-class-application-information",
941                            },
942                        )?;
943
944                        user_variables.push(UserVariableItem::SopClassExtendedNegotiationSubItem(
945                            sop_class_uid,
946                            data,
947                        ));
948                    }
949                    0x58 => {
950                        // User Identity Negotiation
951
952                        // 5 - User Identity Type
953                        let user_identity_type = cursor.read_u8().context(ReadPduFieldSnafu {
954                            field: "User-Identity-type",
955                        })?;
956
957                        // 6 - Positive-response-requested
958                        let positive_response_requested =
959                            cursor.read_u8().context(ReadPduFieldSnafu {
960                                field: "User-Identity-positive-response-requested",
961                            })?;
962
963                        // 7-8 - Primary Field Length
964                        let primary_field_length =
965                            cursor.read_u16::<BigEndian>().context(ReadPduFieldSnafu {
966                                field: "User-Identity-primary-field-length",
967                            })?;
968
969                        // 9-n - Primary Field
970                        let primary_field = read_n(&mut cursor, primary_field_length as usize)
971                            .context(ReadPduFieldSnafu {
972                                field: "User-Identity-primary-field",
973                            })?;
974
975                        // n+1-n+2 - Secondary Field Length
976                        // Only non-zero if user identity type is 2 (username and password)
977                        let secondary_field_length =
978                            cursor.read_u16::<BigEndian>().context(ReadPduFieldSnafu {
979                                field: "User-Identity-secondary-field-length",
980                            })?;
981
982                        // n+3-m - Secondary Field
983                        let secondary_field = read_n(&mut cursor, secondary_field_length as usize)
984                            .context(ReadPduFieldSnafu {
985                                field: "User-Identity-secondary-field",
986                            })?;
987
988                        match UserIdentityType::from(user_identity_type) {
989                            Some(user_identity_type) => {
990                                user_variables.push(UserVariableItem::UserIdentityItem(
991                                    UserIdentity::new(
992                                        positive_response_requested == 1,
993                                        user_identity_type,
994                                        primary_field,
995                                        secondary_field,
996                                    ),
997                                ));
998                            }
999                            None => {
1000                                warn!("Unknown User Identity Type code {}", user_identity_type);
1001                            }
1002                        }
1003                    }
1004                    _ => {
1005                        user_variables.push(UserVariableItem::Unknown(
1006                            item_type,
1007                            read_n(&mut cursor, item_length as usize)
1008                                .context(ReadPduFieldSnafu { field: "Unknown" })?,
1009                        ));
1010                    }
1011                }
1012            }
1013
1014            Ok(PduVariableItem::UserVariables(user_variables))
1015        }
1016        _ => Ok(PduVariableItem::Unknown(item_type)),
1017    }
1018}