dicom_ul/pdu/writer.rs
1/// PDU writer module
2use crate::pdu::*;
3use byteordered::byteorder::{BigEndian, WriteBytesExt};
4use dicom_encoding::text::TextCodec;
5use snafu::{Backtrace, ResultExt, Snafu};
6use std::io::Write;
7
8#[derive(Debug, Snafu)]
9#[non_exhaustive]
10pub enum Error {
11 #[snafu(display("Could not write chunk of {} PDU structure", name))]
12 WriteChunk {
13 /// the name of the PDU structure
14 name: &'static str,
15 source: WriteChunkError,
16 },
17
18 #[snafu(display("Could not write field `{}`", field))]
19 WriteField {
20 field: &'static str,
21 backtrace: Backtrace,
22 source: std::io::Error,
23 },
24
25 #[snafu(display("Could not write {} reserved bytes", bytes))]
26 WriteReserved {
27 bytes: u32,
28 backtrace: Backtrace,
29 source: std::io::Error,
30 },
31
32 #[snafu(display("Could not write field `{}`", field))]
33 EncodeField {
34 field: &'static str,
35 #[snafu(backtrace)]
36 source: dicom_encoding::text::EncodeTextError,
37 },
38}
39
40pub type Result<T> = std::result::Result<T, Error>;
41
42#[derive(Debug, Snafu)]
43pub enum WriteChunkError {
44 #[snafu(display("Failed to build chunk"))]
45 BuildChunk {
46 #[snafu(backtrace)]
47 source: Box<Error>,
48 },
49 #[snafu(display("Failed to write chunk length"))]
50 WriteLength {
51 backtrace: Backtrace,
52 source: std::io::Error,
53 },
54 #[snafu(display("Failed to write chunk data"))]
55 WriteData {
56 backtrace: Backtrace,
57 source: std::io::Error,
58 },
59}
60
61fn write_chunk_u32<F>(writer: &mut dyn Write, func: F) -> std::result::Result<(), WriteChunkError>
62where
63 F: FnOnce(&mut Vec<u8>) -> Result<()>,
64{
65 let mut data = vec![];
66 func(&mut data)
67 .map_err(Box::from)
68 .context(BuildChunkSnafu)?;
69
70 let length = data.len() as u32;
71 writer
72 .write_u32::<BigEndian>(length)
73 .context(WriteLengthSnafu)?;
74
75 writer.write_all(&data).context(WriteDataSnafu)?;
76
77 Ok(())
78}
79
80fn write_chunk_u16<F>(writer: &mut dyn Write, func: F) -> std::result::Result<(), WriteChunkError>
81where
82 F: FnOnce(&mut Vec<u8>) -> Result<()>,
83{
84 let mut data = vec![];
85 func(&mut data)
86 .map_err(Box::from)
87 .context(BuildChunkSnafu)?;
88
89 let length = data.len() as u16;
90 writer
91 .write_u16::<BigEndian>(length)
92 .context(WriteLengthSnafu)?;
93
94 writer.write_all(&data).context(WriteDataSnafu)?;
95
96 Ok(())
97}
98
99pub fn write_pdu<W>(writer: &mut W, pdu: &Pdu) -> Result<()>
100where
101 W: Write,
102{
103 let codec = dicom_encoding::text::DefaultCharacterSetCodec;
104 match pdu {
105 Pdu::AssociationRQ(AssociationRQ {
106 protocol_version,
107 calling_ae_title,
108 called_ae_title,
109 application_context_name,
110 presentation_contexts,
111 user_variables,
112 }) => {
113 // A-ASSOCIATE-RQ PDU Structure
114
115 // 1 - PDU-type - 01H
116 writer
117 .write_u8(0x01)
118 .context(WriteFieldSnafu { field: "PDU-type" })?;
119
120 // 2 - Reserved - This reserved field shall be sent with a value 00H but not
121 // tested to this value when received.
122 writer
123 .write_u8(0x00)
124 .context(WriteReservedSnafu { bytes: 1_u32 })?;
125
126 write_chunk_u32(writer, |writer| {
127 // 7-8 Protocol-version - This two byte field shall use one bit to identify
128 // each version of the DICOM UL protocol supported by the calling end-system.
129 // This is Version 1 and shall be identified with bit 0 set. A receiver of this
130 // PDU implementing only this version of the DICOM UL protocol shall only test
131 // that bit 0 is set.
132 writer
133 .write_u16::<BigEndian>(*protocol_version)
134 .context(WriteFieldSnafu {
135 field: "Protocol-version",
136 })?;
137
138 // 9-10 - Reserved - This reserved field shall be sent with a value 0000H but
139 // not tested to this value when received.
140 writer
141 .write_u16::<BigEndian>(0x00)
142 .context(WriteReservedSnafu { bytes: 2_u32 })?;
143
144 // 11-26 - Called-AE-title - Destination DICOM Application Name. It shall be
145 // encoded as 16 characters as defined by the ISO 646:1990-Basic G0 Set with
146 // leading and trailing spaces (20H) being non-significant. The value made of 16
147 // spaces (20H) meaning "no Application Name specified" shall not be used. For a
148 // complete description of the use of this field, see Section 7.1.1.4.
149 let mut ae_title_bytes =
150 codec.encode(called_ae_title).context(EncodeFieldSnafu {
151 field: "Called-AE-title",
152 })?;
153 ae_title_bytes.resize(16, b' ');
154 writer.write_all(&ae_title_bytes).context(WriteFieldSnafu {
155 field: "Called-AE-title",
156 })?;
157
158 // 27-42 - Calling-AE-title - Source DICOM Application Name. It shall be encoded
159 // as 16 characters as defined by the ISO 646:1990-Basic G0 Set with leading and
160 // trailing spaces (20H) being non-significant. The value made of 16 spaces
161 // (20H) meaning "no Application Name specified" shall not be used. For a
162 // complete description of the use of this field, see Section 7.1.1.3.
163 let mut ae_title_bytes =
164 codec.encode(calling_ae_title).context(EncodeFieldSnafu {
165 field: "Calling-AE-title",
166 })?;
167 ae_title_bytes.resize(16, b' ');
168 writer.write_all(&ae_title_bytes).context(WriteFieldSnafu {
169 field: "Called-AE-title",
170 })?;
171
172 // 43-74 - Reserved - This reserved field shall be sent with a value 00H for all
173 // bytes but not tested to this value when received
174 writer
175 .write_all(&[0; 32])
176 .context(WriteReservedSnafu { bytes: 32_u32 })?;
177
178 write_pdu_variable_application_context_name(
179 writer,
180 application_context_name,
181 &codec,
182 )?;
183
184 for presentation_context in presentation_contexts {
185 write_pdu_variable_presentation_context_proposed(
186 writer,
187 presentation_context,
188 &codec,
189 )?;
190 }
191
192 write_pdu_variable_user_variables(writer, user_variables, &codec)?;
193
194 Ok(())
195 })
196 .context(WriteChunkSnafu {
197 name: "A-ASSOCIATE-RQ",
198 })?;
199
200 Ok(())
201 }
202 Pdu::AssociationAC(AssociationAC {
203 protocol_version,
204 application_context_name,
205 called_ae_title,
206 calling_ae_title,
207 presentation_contexts,
208 user_variables,
209 }) => {
210 // A-ASSOCIATE-AC PDU Structure
211
212 // 1 - PDU-type - 02H
213 writer
214 .write_u8(0x02)
215 .context(WriteFieldSnafu { field: "PDU-type" })?;
216
217 // 2 - Reserved - This reserved field shall be sent with a value 00H but not tested to
218 // this value when received.
219 writer
220 .write_u8(0x00)
221 .context(WriteReservedSnafu { bytes: 1_u32 })?;
222
223 write_chunk_u32(writer, |writer| {
224 // 7-8 - Protocol-version - This two byte field shall use one bit to identify each
225 // version of the DICOM UL protocol supported by the calling end-system. This is
226 // Version 1 and shall be identified with bit 0 set. A receiver of this PDU
227 // implementing only this version of the DICOM UL protocol shall only test that bit
228 // 0 is set.
229 writer
230 .write_u16::<BigEndian>(*protocol_version)
231 .context(WriteFieldSnafu {
232 field: "Protocol-version",
233 })?;
234
235 // 9-10 - Reserved - This reserved field shall be sent with a value 0000H but not
236 // tested to this value when received.
237 writer
238 .write_u16::<BigEndian>(0x00)
239 .context(WriteReservedSnafu { bytes: 2_u32 })?;
240
241 // 11-26 - Reserved - This reserved field shall be sent with a value identical to
242 // the value received in the same field of the A-ASSOCIATE-RQ PDU, but its value
243 // shall not be tested when received.
244 let mut ae_title_bytes =
245 codec.encode(called_ae_title).context(EncodeFieldSnafu {
246 field: "Called-AE-title",
247 })?;
248 ae_title_bytes.resize(16, b' ');
249 writer.write_all(&ae_title_bytes).context(WriteFieldSnafu {
250 field: "Called-AE-title",
251 })?;
252 // 27-42 - Reserved - This reserved field shall be sent with a value identical to
253 // the value received in the same field of the A-ASSOCIATE-RQ PDU, but its value
254 // shall not be tested when received.
255 let mut ae_title_bytes =
256 codec.encode(calling_ae_title).context(EncodeFieldSnafu {
257 field: "Calling-AE-title",
258 })?;
259 ae_title_bytes.resize(16, b' ');
260 writer.write_all(&ae_title_bytes).context(WriteFieldSnafu {
261 field: "Calling-AE-title",
262 })?;
263
264 // 43-74 - Reserved - This reserved field shall be sent with a value identical to
265 // the value received in the same field of the A-ASSOCIATE-RQ PDU, but its value
266 // shall not be tested when received.
267 writer
268 .write_all(&[0; 32])
269 .context(WriteReservedSnafu { bytes: 32_u32 })?;
270
271 // 75-xxx - Variable items - This variable field shall contain the following items:
272 // one Application Context Item, one or more Presentation Context Item(s) and one
273 // User Information Item. For a complete description of these items see Section
274 // 7.1.1.2, Section 7.1.1.14, and Section 7.1.1.6.
275 write_pdu_variable_application_context_name(
276 writer,
277 application_context_name,
278 &codec,
279 )?;
280
281 for presentation_context in presentation_contexts {
282 write_pdu_variable_presentation_context_result(
283 writer,
284 presentation_context,
285 &codec,
286 )?;
287 }
288
289 write_pdu_variable_user_variables(writer, user_variables, &codec)?;
290
291 Ok(())
292 })
293 .context(WriteChunkSnafu {
294 name: "A-ASSOCIATE-AC",
295 })
296 }
297 Pdu::AssociationRJ(AssociationRJ { result, source }) => {
298 // 1 - PDU-type - 03H
299 writer
300 .write_u8(0x03)
301 .context(WriteFieldSnafu { field: "PDU-type" })?;
302
303 // 2 - Reserved - This reserved field shall be sent with a value 00H but not tested to this value when received.
304 writer
305 .write_u8(0x00)
306 .context(WriteReservedSnafu { bytes: 1_u32 })?;
307
308 write_chunk_u32(writer, |writer| {
309 // 7 - Reserved - This reserved field shall be sent with a value 00H but not tested to this value when received.
310 writer.write_u8(0x00).context(WriteReservedSnafu { bytes: 1_u32 })?;
311
312 // 8 - Result - This Result field shall contain an integer value encoded as an unsigned binary number. One of the following values shall be used:
313 // - 1 - rejected-permanent
314 // - 2 - rejected-transient
315 writer.write_u8(match result {
316 AssociationRJResult::Permanent => {
317 0x01
318 }
319 AssociationRJResult::Transient => {
320 0x02
321 }
322 }).context(WriteFieldSnafu { field: "AssociationRJResult" })?;
323
324 // 9 - Source - This Source field shall contain an integer value encoded as an unsigned binary number. One of the following values shall be used:
325 // - 1 - DICOM UL service-user
326 // - 2 - DICOM UL service-provider (ACSE related function)
327 // - 3 - DICOM UL service-provider (Presentation related function)
328 // 10 - Reason/Diag - This field shall contain an integer value encoded as an unsigned binary number.
329 // If the Source field has the value (1) "DICOM UL service-user", it shall take one of the following:
330 // - 1 - no-reason-given
331 // - 2 - application-context-name-not-supported
332 // - 3 - calling-AE-title-not-recognized
333 // - 4-6 - reserved
334 // - 7 - called-AE-title-not-recognized
335 // - 8-10 - reserved
336 // If the Source field has the value (2) "DICOM UL service provided (ACSE related function)", it shall take one of the following:
337 // - 1 - no-reason-given
338 // - 2 - protocol-version-not-supported
339 // If the Source field has the value (3) "DICOM UL service provided (Presentation related function)", it shall take one of the following:
340 // 0 - reserved
341 // 1 - temporary-congestion
342 // 2 - local-limit-exceeded
343 // 3-7 - reserved
344 match source {
345 AssociationRJSource::ServiceUser(reason) => {
346 writer.write_u8(0x01).context(WriteFieldSnafu { field: "AssociationRJServiceUserReason" })?;
347 writer.write_u8(match reason {
348 AssociationRJServiceUserReason::NoReasonGiven => {
349 0x01
350 }
351 AssociationRJServiceUserReason::ApplicationContextNameNotSupported => {
352 0x02
353 }
354 AssociationRJServiceUserReason::CallingAETitleNotRecognized => {
355 0x03
356 }
357 AssociationRJServiceUserReason::CalledAETitleNotRecognized => {
358 0x07
359 }
360 AssociationRJServiceUserReason::Reserved(data) => {
361 *data
362 }
363 }).context(WriteFieldSnafu { field: "AssociationRJServiceUserReason (2)" })?;
364 }
365 AssociationRJSource::ServiceProviderASCE(reason) => {
366 writer.write_u8(0x02).context(WriteFieldSnafu { field: "AssociationRJServiceProvider" })?;
367 writer.write_u8(match reason {
368 AssociationRJServiceProviderASCEReason::NoReasonGiven => {
369 0x01
370 }
371 AssociationRJServiceProviderASCEReason::ProtocolVersionNotSupported => {
372 0x02
373 }
374 }).context(WriteFieldSnafu { field: "AssociationRJServiceProvider (2)" })?;
375 }
376 AssociationRJSource::ServiceProviderPresentation(reason) => {
377 writer.write_u8(0x03).context(WriteFieldSnafu { field: "AssociationRJServiceProviderPresentationReason" })?;
378 writer.write_u8(match reason {
379 AssociationRJServiceProviderPresentationReason::TemporaryCongestion => {
380 0x01
381 }
382 AssociationRJServiceProviderPresentationReason::LocalLimitExceeded => {
383 0x02
384 }
385 AssociationRJServiceProviderPresentationReason::Reserved(data) => {
386 *data
387 }
388 }).context(WriteFieldSnafu { field: "AssociationRJServiceProviderPresentationReason (2)" })?;
389 }
390 }
391
392 Ok(())
393 }).context(WriteChunkSnafu { name: "AssociationRJ" })?;
394
395 Ok(())
396 }
397 Pdu::PData { data } => {
398 // 1 - PDU-type - 04H
399 writer
400 .write_u8(0x04)
401 .context(WriteFieldSnafu { field: "PDU-type" })?;
402
403 // 2 - Reserved - This reserved field shall be sent with a value 00H but not tested to
404 // this value when received.
405 writer
406 .write_u8(0x00)
407 .context(WriteReservedSnafu { bytes: 1_u32 })?;
408
409 write_chunk_u32(writer, |writer| {
410 // 7-xxx - Presentation-data-value Item(s) - This variable data field shall contain
411 // one or more Presentation-data-value Items(s). For a complete description of the
412 // use of this field see Section 9.3.5.1
413
414 for presentation_data_value in data {
415 write_chunk_u32(writer, |writer| {
416 // 5 - Presentation-context-ID - Presentation-context-ID values shall be odd
417 // integers between 1 and 255, encoded as an unsigned binary number. For a
418 // complete description of the use of this field see Section 7.1.1.13.
419 writer.push(presentation_data_value.presentation_context_id);
420
421 // 6-xxx - Presentation-data-value - This Presentation-data-value field
422 // shall contain DICOM message information (command and/or data set) with a
423 // message control header. For a complete description of the use of this
424 // field see Annex E.
425
426 // The Message Control Header shall be made of one byte with the least
427 // significant bit (bit 0) taking one of the following values:
428 // - If bit 0 is set to 1, the following fragment shall contain Message
429 // Command information.
430 // - If bit 0 is set to 0, the following fragment shall contain Message Data
431 // Set information.
432 // The next least significant bit (bit 1) shall be defined by the following
433 // rules: If bit 1 is set to 1, the following fragment shall contain the
434 // last fragment of a Message Data Set or of a Message Command.
435 // - If bit 1 is set to 0, the following fragment does not contain the last
436 // fragment of a Message Data Set or of a Message Command.
437 let mut message_header = 0x00;
438 if let PDataValueType::Command = presentation_data_value.value_type {
439 message_header |= 0x01;
440 }
441 if presentation_data_value.is_last {
442 message_header |= 0x02;
443 }
444 writer.push(message_header);
445
446 // Message fragment
447 writer.extend(&presentation_data_value.data);
448
449 Ok(())
450 })
451 .context(WriteChunkSnafu {
452 name: "Presentation-data-value item",
453 })?;
454 }
455
456 Ok(())
457 })
458 .context(WriteChunkSnafu { name: "PData" })
459 }
460 Pdu::ReleaseRQ => {
461 // 1 - PDU-type - 05H
462 writer
463 .write_u8(0x05)
464 .context(WriteFieldSnafu { field: "PDU-type" })?;
465
466 // 2 - Reserved - This reserved field shall be sent with a value 00H but not tested to
467 // this value when received.
468 writer
469 .write_u8(0x00)
470 .context(WriteReservedSnafu { bytes: 1_u32 })?;
471
472 write_chunk_u32(writer, |writer| {
473 writer.extend([0u8; 4]);
474 Ok(())
475 })
476 .context(WriteChunkSnafu { name: "ReleaseRQ" })?;
477
478 Ok(())
479 }
480 Pdu::ReleaseRP => {
481 // 1 - PDU-type - 06H
482 writer
483 .write_u8(0x06)
484 .context(WriteFieldSnafu { field: "PDU-type" })?;
485
486 // 2 - Reserved - This reserved field shall be sent with a value 00H but not tested to
487 // this value when received.
488 writer
489 .write_u8(0x00)
490 .context(WriteReservedSnafu { bytes: 1_u32 })?;
491
492 write_chunk_u32(writer, |writer| {
493 writer.extend([0u8; 4]);
494 Ok(())
495 })
496 .context(WriteChunkSnafu { name: "ReleaseRP" })?;
497
498 Ok(())
499 }
500 Pdu::AbortRQ { source } => {
501 // 1 - PDU-type - 07H
502 writer
503 .write_u8(0x07)
504 .context(WriteFieldSnafu { field: "PDU-type" })?;
505
506 // 2 - Reserved - This reserved field shall be sent with a value 00H but not tested to
507 // this value when received.
508 writer
509 .write_u8(0x00)
510 .context(WriteReservedSnafu { bytes: 1_u32 })?;
511
512 write_chunk_u32(writer, |writer| {
513 // 7 - Reserved - This reserved field shall be sent with a value 00H but not tested
514 // to this value when received.
515 writer.push(0);
516 // 8 - Reserved - This reserved field shall be sent with a value 00H but not tested
517 // to this value when received.
518 writer.push(0);
519
520 // 9 - Source - This Source field shall contain an integer value encoded as an
521 // unsigned binary number. One of the following values shall be used:
522 // - 0 - DICOM UL service-user (initiated abort)
523 // - 1 - reserved
524 // - 2 - DICOM UL service-provider (initiated abort)
525 // 10 - Reason/Diag - This field shall contain an integer value encoded as an
526 // unsigned binary number. If the Source field has the value (2) "DICOM UL
527 // service-provider", it shall take one of the following:
528 // - 0 - reason-not-specified1 - unrecognized-PDU
529 // - 2 - unexpected-PDU
530 // - 3 - reserved
531 // - 4 - unrecognized-PDU parameter
532 // - 5 - unexpected-PDU parameter
533 // - 6 - invalid-PDU-parameter value
534 // If the Source field has the value (0) "DICOM UL service-user", this reason field
535 // shall not be significant. It shall be sent with a value 00H but not tested to
536 // this value when received.
537 let source_word = match source {
538 AbortRQSource::ServiceUser => [0x00; 2],
539 AbortRQSource::Reserved => [0x01, 0x00],
540 AbortRQSource::ServiceProvider(reason) => match reason {
541 AbortRQServiceProviderReason::ReasonNotSpecified => [0x02, 0x00],
542 AbortRQServiceProviderReason::UnrecognizedPdu => [0x02, 0x01],
543 AbortRQServiceProviderReason::UnexpectedPdu => [0x02, 0x02],
544 AbortRQServiceProviderReason::Reserved => [0x02, 0x03],
545 AbortRQServiceProviderReason::UnrecognizedPduParameter => [0x02, 0x04],
546 AbortRQServiceProviderReason::UnexpectedPduParameter => [0x02, 0x05],
547 AbortRQServiceProviderReason::InvalidPduParameter => [0x02, 0x06],
548 },
549 };
550 writer.extend(source_word);
551
552 Ok(())
553 })
554 .context(WriteChunkSnafu { name: "AbortRQ" })?;
555
556 Ok(())
557 }
558 Pdu::Unknown { pdu_type, data } => {
559 // 1 - PDU-type - XXH
560 writer
561 .write_u8(*pdu_type)
562 .context(WriteFieldSnafu { field: "PDU-type" })?;
563
564 // 2 - Reserved - This reserved field shall be sent with a value 00H but not tested to
565 // this value when received.
566 writer
567 .write_u8(0x00)
568 .context(WriteReservedSnafu { bytes: 1_u32 })?;
569
570 write_chunk_u32(writer, |writer| {
571 writer.extend(data);
572 Ok(())
573 })
574 .context(WriteChunkSnafu { name: "Unknown" })?;
575
576 Ok(())
577 }
578 }
579}
580
581fn write_pdu_variable_application_context_name(
582 writer: &mut dyn Write,
583 application_context_name: &str,
584 codec: &dyn TextCodec,
585) -> Result<()> {
586 // Application Context Item Structure
587 // 1 - Item-type - 10H
588 writer
589 .write_u8(0x10)
590 .context(WriteFieldSnafu { field: "Item-type" })?;
591
592 // 2 - Reserved - This reserved field shall be sent with a value 00H but not
593 // tested to this value when received.
594 writer
595 .write_u8(0x00)
596 .context(WriteReservedSnafu { bytes: 1_u32 })?;
597
598 write_chunk_u16(writer, |writer| {
599 // 5-xxx - Application-context-name -A valid Application-context-name shall
600 // be encoded as defined in Annex F. For a description of the use of this
601 // field see Section 7.1.1.2. Application-context-names are structured as
602 // UIDs as defined in PS3.5 (see Annex A for an overview of this concept).
603 // DICOM Application-context-names are registered in PS3.7.
604 writer
605 .write_all(
606 &codec
607 .encode(application_context_name)
608 .context(EncodeFieldSnafu {
609 field: "Application-context-name",
610 })?,
611 )
612 .context(WriteFieldSnafu {
613 field: "Application-context-name",
614 })
615 })
616 .context(WriteChunkSnafu {
617 name: "Application Context Item",
618 })?;
619
620 Ok(())
621}
622
623fn write_pdu_variable_presentation_context_proposed(
624 writer: &mut dyn Write,
625 presentation_context: &PresentationContextProposed,
626 codec: &dyn TextCodec,
627) -> Result<()> {
628 // Presentation Context Item Structure
629 // 1 - tem-type - 20H
630 writer
631 .write_u8(0x20)
632 .context(WriteFieldSnafu { field: "Item-type" })?;
633
634 // 2 - Reserved - This reserved field shall be sent with a value 00H but not
635 // tested to this value when received.
636 writer
637 .write_u8(0x00)
638 .context(WriteReservedSnafu { bytes: 1_u32 })?;
639
640 write_chunk_u16(writer, |writer| {
641 // 5 - Presentation-context-ID - Presentation-context-ID values shall be
642 // odd integers between 1 and 255, encoded as an unsigned binary number.
643 // For a complete description of the use of this field see Section
644 // 7.1.1.13.
645 writer
646 .write_u8(presentation_context.id)
647 .context(WriteFieldSnafu {
648 field: "Presentation-context-ID",
649 })?;
650
651 // 6 - Reserved - This reserved field shall be sent with a value 00H but
652 // not tested to this value when received.
653 writer
654 .write_u8(0x00)
655 .context(WriteReservedSnafu { bytes: 1_u32 })?;
656
657 // 7 - Reserved - This reserved field shall be sent with a value 00H but
658 // not tested to this value when received
659 writer
660 .write_u8(0x00)
661 .context(WriteReservedSnafu { bytes: 1_u32 })?;
662
663 // 8 - Reserved - This reserved field shall be sent with a value 00H but
664 // not tested to this value when received.
665 writer
666 .write_u8(0x00)
667 .context(WriteReservedSnafu { bytes: 1_u32 })?;
668
669 // 9-xxx - Abstract/Transfer Syntax Sub-Items - This variable field
670 // shall contain the following sub-items: one Abstract Syntax and one or
671 // more Transfer Syntax(es). For a complete description of the use and
672 // encoding of these sub-items see Section 9.3.2.2.1 and Section
673 // 9.3.2.2.2.
674
675 // Abstract Syntax Sub-Item Structure
676 // 1 - Item-type 30H
677 writer
678 .write_u8(0x30)
679 .context(WriteFieldSnafu { field: "Item-type" })?;
680
681 // 2 - Reserved - This reserved field shall be sent with a value 00H
682 // but not tested to this value when
683 // received.
684 writer
685 .write_u8(0x00)
686 .context(WriteReservedSnafu { bytes: 1_u32 })?;
687
688 write_chunk_u16(writer, |writer| {
689 // 5-xxx - Abstract-syntax-name - This variable field shall
690 // contain
691 // the Abstract-syntax-name related to the proposed presentation
692 // context. A valid Abstract-syntax-name shall be encoded as
693 // defined in Annex F. For a
694 // description of the use of this field see
695 // Section 7.1.1.13. Abstract-syntax-names are structured as
696 // UIDs as defined in PS3.5
697 // (see Annex B for an overview of this concept).
698 // DICOM Abstract-syntax-names are registered in PS3.4.
699 writer
700 .write_all(
701 &codec
702 .encode(&presentation_context.abstract_syntax)
703 .context(EncodeFieldSnafu {
704 field: "Abstract-syntax-name",
705 })?,
706 )
707 .context(WriteFieldSnafu {
708 field: "Abstract-syntax-name",
709 })
710 })
711 .context(WriteChunkSnafu {
712 name: "Abstract Syntax Item",
713 })?;
714
715 for transfer_syntax in &presentation_context.transfer_syntaxes {
716 // Transfer Syntax Sub-Item Structure
717 // 1 - Item-type - 40H
718 writer.write_u8(0x40).context(WriteFieldSnafu {
719 field: "Presentation-context Item-type",
720 })?;
721
722 // 2 - Reserved - This reserved field shall be sent with a value 00H
723 // but not tested to this value when received.
724 writer
725 .write_u8(0x00)
726 .context(WriteReservedSnafu { bytes: 1_u32 })?;
727
728 write_chunk_u16(writer, |writer| {
729 // 5-xxx - Transfer-syntax-name(s) - This variable field shall
730 // contain the Transfer-syntax-name proposed for this
731 // presentation context. A valid Transfer-syntax-name shall be
732 // encoded as defined in Annex F. For a description of the use
733 // of this field see Section 7.1.1.13. Transfer-syntax-names are
734 // structured as UIDs as defined in PS3.5 (see Annex B for an
735 // overview of this concept). DICOM Transfer-syntax-names are
736 // registered in PS3.5.
737 writer
738 .write_all(&codec.encode(transfer_syntax).context(EncodeFieldSnafu {
739 field: "Transfer-syntax-name",
740 })?)
741 .context(WriteFieldSnafu {
742 field: "Transfer-syntax-name",
743 })
744 })
745 .context(WriteChunkSnafu {
746 name: "Transfer Syntax Sub-Item",
747 })?;
748 }
749
750 Ok(())
751 })
752 .context(WriteChunkSnafu {
753 name: "Presentation Context Item",
754 })?;
755
756 Ok(())
757}
758
759fn write_pdu_variable_presentation_context_result(
760 writer: &mut dyn Write,
761 presentation_context: &PresentationContextResult,
762 codec: &dyn TextCodec,
763) -> Result<()> {
764 // 1 - Item-type - 21H
765 writer
766 .write_u8(0x21)
767 .context(WriteFieldSnafu { field: "Item-type" })?;
768
769 // 2 - Reserved - This reserved field shall be sent with a value 00H but not tested to this
770 // value when received.
771 writer
772 .write_u8(0x00)
773 .context(WriteReservedSnafu { bytes: 1_u32 })?;
774
775 write_chunk_u16(writer, |writer| {
776 // 5 - Presentation-context-ID - Presentation-context-ID values shall be odd integers
777 // between 1 and 255, encoded as an unsigned binary number. For a complete description of
778 // the use of this field see Section 7.1.1.13.
779 writer
780 .write_u8(presentation_context.id)
781 .context(WriteFieldSnafu {
782 field: "Presentation-context-ID",
783 })?;
784
785 // 6 - Reserved - This reserved field shall be sent with a value 00H but not tested to this
786 // value when received.
787 writer
788 .write_u8(0x00)
789 .context(WriteReservedSnafu { bytes: 1_u32 })?;
790
791 // 7 - Result/Reason - This Result/Reason field shall contain an integer value encoded as an
792 // unsigned binary number. One of the following values shall be used:
793 // 0 - acceptance
794 // 1 - user-rejection
795 // 2 - no-reason (provider rejection)
796 // 3 - abstract-syntax-not-supported (provider rejection)
797 // 4 - transfer-syntaxes-not-supported (provider rejection)
798 writer
799 .write_u8(match &presentation_context.reason {
800 PresentationContextResultReason::Acceptance => 0,
801 PresentationContextResultReason::UserRejection => 1,
802 PresentationContextResultReason::NoReason => 2,
803 PresentationContextResultReason::AbstractSyntaxNotSupported => 3,
804 PresentationContextResultReason::TransferSyntaxesNotSupported => 4,
805 })
806 .context(WriteFieldSnafu {
807 field: "Presentation Context Result/Reason",
808 })?;
809
810 // 8 - Reserved - This reserved field shall be sent with a value 00H but not tested to this
811 // value when received.
812 writer
813 .write_u8(0x00)
814 .context(WriteReservedSnafu { bytes: 1_u32 })?;
815
816 // 9-xxx - Transfer syntax sub-item - This variable field shall contain one Transfer Syntax
817 // Sub-Item. When the Result/Reason field has a value other than acceptance (0), this field
818 // shall not be significant and its value shall not be tested when received. For a complete
819 // description of the use and encoding of this item see Section 9.3.3.2.1.
820
821 // 1 - Item-type - 40H
822 writer
823 .write_u8(0x40)
824 .context(WriteFieldSnafu { field: "Item-type" })?;
825
826 // 2 - Reserved - This reserved field shall be sent with a value 00H but not tested to this
827 // value when received.
828 writer
829 .write_u8(0x40)
830 .context(WriteReservedSnafu { bytes: 1_u32 })?;
831
832 write_chunk_u16(writer, |writer| {
833 // 5-xxx - Transfer-syntax-name - This variable field shall contain the
834 // Transfer-syntax-name proposed for this presentation context. A valid
835 // Transfer-syntax-name shall be encoded as defined in Annex F. For a description of the
836 // use of this field see Section 7.1.1.14. Transfer-syntax-names are structured as UIDs
837 // as defined in PS3.5 (see Annex B for an overview of this concept). DICOM
838 // Transfer-syntax-names are registered in PS3.5.
839 writer
840 .write_all(
841 &codec
842 .encode(&presentation_context.transfer_syntax)
843 .context(EncodeFieldSnafu {
844 field: "Transfer-syntax-name",
845 })?,
846 )
847 .context(WriteFieldSnafu {
848 field: "Transfer-syntax-name",
849 })?;
850
851 Ok(())
852 })
853 .context(WriteChunkSnafu {
854 name: "Transfer Syntax sub-item",
855 })?;
856
857 Ok(())
858 })
859 .context(WriteChunkSnafu {
860 name: "Presentation-context",
861 })
862}
863
864fn write_pdu_variable_user_variables(
865 writer: &mut dyn Write,
866 user_variables: &[UserVariableItem],
867 codec: &dyn TextCodec,
868) -> Result<()> {
869 if user_variables.is_empty() {
870 return Ok(());
871 }
872
873 // 1 - Item-type - 50H
874 writer
875 .write_u8(0x50)
876 .context(WriteFieldSnafu { field: "Item-type" })?;
877
878 // 2 - Reserved - This reserved field shall be sent with a value 00H but not tested to this
879 // value when received.
880 writer
881 .write_u8(0x00)
882 .context(WriteReservedSnafu { bytes: 1_u32 })?;
883
884 write_chunk_u16(writer, |writer| {
885 // 5-xxx - User-data - This variable field shall contain User-data sub-items as defined by
886 // the DICOM Application Entity. The structure and content of these sub-items is defined in
887 // Annex D.
888 for user_variable in user_variables {
889 match user_variable {
890 UserVariableItem::MaxLength(max_length) => {
891 // 1 - Item-type - 51H
892 writer
893 .write_u8(0x51)
894 .context(WriteFieldSnafu { field: "Item-type" })?;
895
896 // 2 - Reserved - This reserved field shall be sent with a value 00H but not
897 // tested to this value when received.
898 writer
899 .write_u8(0x00)
900 .context(WriteReservedSnafu { bytes: 1_u32 })?;
901
902 write_chunk_u16(writer, |writer| {
903 // 5-8 - Maximum-length-received - This parameter allows the
904 // association-requestor to restrict the maximum length of the variable
905 // field of the P-DATA-TF PDUs sent by the acceptor on the association once
906 // established. This length value is indicated as a number of bytes encoded
907 // as an unsigned binary number. The value of (0) indicates that no maximum
908 // length is specified. This maximum length value shall never be exceeded by
909 // the PDU length values used in the PDU-length field of the P-DATA-TF PDUs
910 // received by the association-requestor. Otherwise, it shall be a protocol
911 // error.
912 writer
913 .write_u32::<BigEndian>(*max_length)
914 .context(WriteFieldSnafu {
915 field: "Maximum-length-received",
916 })
917 })
918 .context(WriteChunkSnafu {
919 name: "Maximum-length-received",
920 })?;
921 }
922 UserVariableItem::ImplementationVersionName(implementation_version_name) => {
923 // 1 - Item-type - 55H
924 writer
925 .write_u8(0x55)
926 .context(WriteFieldSnafu { field: "Item-type" })?;
927
928 // 2 - Reserved - This reserved field shall be sent with a value 00H but not
929 // tested to this value when received.
930 writer
931 .write_u8(0x00)
932 .context(WriteReservedSnafu { bytes: 1_u32 })?;
933
934 write_chunk_u16(writer, |writer| {
935 // 5 - xxx - Implementation-version-name - This variable field shall contain
936 // the Implementation-version-name of the Association-acceptor as defined in
937 // Section D.3.3.2. It shall be encoded as a string of 1 to 16 ISO 646:1990
938 // (basic G0 set) characters.
939 writer
940 .write_all(&codec.encode(implementation_version_name).context(
941 EncodeFieldSnafu {
942 field: "Implementation-version-name",
943 },
944 )?)
945 .context(WriteFieldSnafu {
946 field: "Implementation-version-name",
947 })
948 })
949 .context(WriteChunkSnafu {
950 name: "Implementation-version-name",
951 })?;
952 }
953 UserVariableItem::ImplementationClassUID(implementation_class_uid) => {
954 // 1 - Item-type - 52H
955 writer
956 .write_u8(0x52)
957 .context(WriteFieldSnafu { field: "Item-type" })?;
958
959 // 2 - Reserved - This reserved field shall be sent with a value 00H but not
960 // tested to this value when received.
961 writer
962 .write_u8(0x00)
963 .context(WriteReservedSnafu { bytes: 1_u32 })?;
964
965 write_chunk_u16(writer, |writer| {
966 //5 - xxx - Implementation-class-uid - This variable field shall contain
967 // the Implementation-class-uid of the Association-acceptor as defined in
968 // Section D.3.3.2. The Implementation-class-uid field is structured as a
969 // UID as defined in PS3.5.
970 writer
971 .write_all(&codec.encode(implementation_class_uid).context(
972 EncodeFieldSnafu {
973 field: "Implementation-class-uid",
974 },
975 )?)
976 .context(WriteFieldSnafu {
977 field: "Implementation-class-uid",
978 })
979 })
980 .context(WriteChunkSnafu {
981 name: "Implementation-class-uid",
982 })?;
983 }
984 UserVariableItem::SopClassExtendedNegotiationSubItem(sop_class_uid, data) => {
985 // 1 - Item-type - 56H
986 writer
987 .write_u8(0x56)
988 .context(WriteFieldSnafu { field: "Item-type" })?;
989 // 2 - Reserved - This reserved field shall be sent with a value 00H but not
990 // tested to this value when received.
991 writer
992 .write_u8(0x00)
993 .context(WriteReservedSnafu { bytes: 1_u32 })?;
994
995 write_chunk_u16(writer, |writer| {
996 write_chunk_u16(writer, |writer| {
997 // 7-xxx - The SOP Class or Meta SOP Class identifier encoded as a UID
998 // as defined in Section 9 “Unique Identifiers (UIDs)” in PS3.5.
999 writer
1000 .write_all(&codec.encode(sop_class_uid).context(
1001 EncodeFieldSnafu {
1002 field: "SOP-class-uid",
1003 },
1004 )?)
1005 .context(WriteFieldSnafu {
1006 field: "SOP-class-uid",
1007 })
1008 })
1009 .context(WriteChunkSnafu {
1010 name: "SOP-class-uid",
1011 })?;
1012
1013 write_chunk_u16(writer, |writer| {
1014 // xxx-xxx Service-class-application-information - This field shall contain
1015 // the application information specific to the Service Class specification
1016 // identified by the SOP-class-uid. The semantics and value of this field is
1017 // defined in the identified Service Class specification.
1018 writer.write_all(data).context(WriteFieldSnafu {
1019 field: "Service-class-application-information",
1020 })
1021 })
1022 .context(WriteChunkSnafu {
1023 name: "Service-class-application-information",
1024 })
1025 })
1026 .context(WriteChunkSnafu { name: "Sub-item" })?;
1027 }
1028 UserVariableItem::UserIdentityItem(user_identity) => {
1029 // 1 - Item-type - 58H
1030 writer
1031 .write_u8(0x58)
1032 .context(WriteFieldSnafu { field: "Item-type" })?;
1033
1034 // 2 - Reserved - This reserved field shall be sent with a value 00H but not
1035 // tested to this value when received.
1036 writer
1037 .write_u8(0x00)
1038 .context(WriteReservedSnafu { bytes: 1_u32 })?;
1039
1040 // 3-4 - Item-length
1041 write_chunk_u16(writer, |writer| {
1042 // 5 - User-Identity-Type
1043 writer
1044 .write_u8(user_identity.identity_type().to_u8())
1045 .context(WriteFieldSnafu {
1046 field: "User-Identity-Type",
1047 })?;
1048
1049 // 6 - Positive-response-requested
1050 let positive_response_requested_out: u8 =
1051 if user_identity.positive_response_requested() {
1052 1
1053 } else {
1054 0
1055 };
1056 writer.write_u8(positive_response_requested_out).context(
1057 WriteFieldSnafu {
1058 field: "Positive-response-requested",
1059 },
1060 )?;
1061
1062 // 7-8 - Primary-field-length
1063 write_chunk_u16(writer, |writer| {
1064 // 9-n - Primary-field
1065 writer
1066 .write_all(user_identity.primary_field().as_slice())
1067 .context(WriteFieldSnafu {
1068 field: "Primary-field",
1069 })
1070 })
1071 .context(WriteChunkSnafu {
1072 name: "Primary-field",
1073 })?;
1074
1075 // n+1-n+2 - Secondary-field-length
1076 write_chunk_u16(writer, |writer| {
1077 // n+3-m - Secondary-field
1078 writer
1079 .write_all(user_identity.secondary_field().as_slice())
1080 .context(WriteFieldSnafu {
1081 field: "Secondary-field",
1082 })
1083 })
1084 .context(WriteChunkSnafu {
1085 name: "Secondary-field",
1086 })
1087 })
1088 .context(WriteChunkSnafu {
1089 name: "Item-length",
1090 })?;
1091 }
1092 UserVariableItem::Unknown(item_type, data) => {
1093 writer
1094 .write_u8(*item_type)
1095 .context(WriteFieldSnafu { field: "Item-type" })?;
1096
1097 writer
1098 .write_u8(0x00)
1099 .context(WriteReservedSnafu { bytes: 1_u32 })?;
1100
1101 write_chunk_u16(writer, |writer| {
1102 writer.write_all(data).context(WriteFieldSnafu {
1103 field: "Unknown Data",
1104 })
1105 })
1106 .context(WriteChunkSnafu { name: "Unknown" })?;
1107 }
1108 }
1109 }
1110
1111 Ok(())
1112 })
1113 .context(WriteChunkSnafu { name: "User-data" })
1114}
1115
1116#[cfg(test)]
1117mod tests {
1118 use super::*;
1119
1120 #[test]
1121 fn can_write_chunks_with_preceding_u32_length() -> Result<()> {
1122 let mut bytes = vec![0u8; 0];
1123 write_chunk_u32(&mut bytes, |writer| {
1124 writer
1125 .write_u8(0x02)
1126 .context(WriteFieldSnafu { field: "Field1" })?;
1127 write_chunk_u32(writer, |writer| {
1128 writer
1129 .write_u8(0x03)
1130 .context(WriteFieldSnafu { field: "Field2" })?;
1131 Ok(())
1132 })
1133 .context(WriteChunkSnafu { name: "Chunk2" })
1134 })
1135 .context(WriteChunkSnafu { name: "Chunk1" })?;
1136
1137 assert_eq!(bytes.len(), 10);
1138 assert_eq!(bytes, &[0, 0, 0, 6, 2, 0, 0, 0, 1, 3]);
1139
1140 Ok(())
1141 }
1142
1143 #[test]
1144 fn can_write_chunks_with_preceding_u16_length() -> Result<()> {
1145 let mut bytes = vec![0u8; 0];
1146 write_chunk_u16(&mut bytes, |writer| {
1147 writer
1148 .write_u8(0x02)
1149 .context(WriteFieldSnafu { field: "Field1" })?;
1150 write_chunk_u16(writer, |writer| {
1151 writer
1152 .write_u8(0x03)
1153 .context(WriteFieldSnafu { field: "Field2" })?;
1154 Ok(())
1155 })
1156 .context(WriteChunkSnafu { name: "Chunk2" })
1157 })
1158 .context(WriteChunkSnafu { name: "Chunk1" })?;
1159
1160 assert_eq!(bytes.len(), 6);
1161 assert_eq!(bytes, &[0, 4, 2, 0, 1, 3]);
1162
1163 Ok(())
1164 }
1165
1166 #[test]
1167 fn write_abort_rq() {
1168 let mut out = vec![];
1169
1170 // abort by request of SCU
1171 let pdu = Pdu::AbortRQ {
1172 source: AbortRQSource::ServiceUser,
1173 };
1174 write_pdu(&mut out, &pdu).unwrap();
1175 assert_eq!(
1176 &out,
1177 &[
1178 // code 7 + reserved byte
1179 0x07, 0x00, //
1180 // PDU length: 4 bytes
1181 0x00, 0x00, 0x00, 0x04, //
1182 // reserved 2 bytes + source: service user (0) + reason (0)
1183 0x00, 0x00, 0x00, 0x00,
1184 ]
1185 );
1186 out.clear();
1187
1188 // Reserved
1189 let pdu = Pdu::AbortRQ {
1190 source: AbortRQSource::Reserved,
1191 };
1192 write_pdu(&mut out, &pdu).unwrap();
1193 assert_eq!(
1194 &out,
1195 &[
1196 // code 7 + reserved byte
1197 0x07, 0x00, //
1198 // PDU length: 4 bytes
1199 0x00, 0x00, 0x00, 0x04, //
1200 // reserved 2 bytes + source: reserved (1) + reason (0)
1201 0x00, 0x00, 0x01, 0x00,
1202 ]
1203 );
1204 out.clear();
1205
1206 // abort by request of SCP
1207 let pdu = Pdu::AbortRQ {
1208 source: AbortRQSource::ServiceProvider(
1209 AbortRQServiceProviderReason::InvalidPduParameter,
1210 ),
1211 };
1212 write_pdu(&mut out, &pdu).unwrap();
1213 assert_eq!(
1214 &out,
1215 &[
1216 // code 7 + reserved byte
1217 0x07, 0x00, //
1218 // PDU length: 4 bytes
1219 0x00, 0x00, 0x00, 0x04, //
1220 // reserved 2 bytes
1221 0x00, 0x00, //
1222 // source: service provider (2), invalid parameter value (6)
1223 0x02, 0x06,
1224 ]
1225 );
1226 }
1227}