Skip to main content

dicom_ul/pdu/
reader.rs

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