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}