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}