1pub mod reader;
8pub mod writer;
9
10use std::fmt::Display;
11
12pub use reader::read_pdu;
13pub use writer::write_pdu;
14
15#[derive(Clone, Eq, PartialEq, PartialOrd, Hash, Debug)]
17pub struct PresentationContextProposed {
18 pub id: u8,
20 pub abstract_syntax: String,
23 pub transfer_syntaxes: Vec<String>,
25}
26
27#[derive(Clone, Eq, PartialEq, PartialOrd, Hash, Debug)]
28pub struct PresentationContextResult {
29 pub id: u8,
30 pub reason: PresentationContextResultReason,
31 pub transfer_syntax: String,
32}
33
34#[derive(Clone, Eq, PartialEq, PartialOrd, Hash, Debug)]
35pub enum PresentationContextResultReason {
36 Acceptance = 0,
37 UserRejection = 1,
38 NoReason = 2,
39 AbstractSyntaxNotSupported = 3,
40 TransferSyntaxesNotSupported = 4,
41}
42
43impl PresentationContextResultReason {
44 fn from(reason: u8) -> Option<PresentationContextResultReason> {
45 let result = match reason {
46 0 => PresentationContextResultReason::Acceptance,
47 1 => PresentationContextResultReason::UserRejection,
48 2 => PresentationContextResultReason::NoReason,
49 3 => PresentationContextResultReason::AbstractSyntaxNotSupported,
50 4 => PresentationContextResultReason::TransferSyntaxesNotSupported,
51 _ => {
52 return None;
53 }
54 };
55
56 Some(result)
57 }
58}
59
60impl Display for PresentationContextResultReason {
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 let msg = match self {
63 PresentationContextResultReason::Acceptance => "acceptance",
64 PresentationContextResultReason::UserRejection => "user rejection",
65 PresentationContextResultReason::NoReason => "no reason",
66 PresentationContextResultReason::AbstractSyntaxNotSupported => {
67 "abstract syntax not supported"
68 }
69 PresentationContextResultReason::TransferSyntaxesNotSupported => {
70 "transfer syntaxes not supported"
71 }
72 };
73 f.write_str(msg)
74 }
75}
76
77#[derive(Clone, Eq, PartialEq, PartialOrd, Hash, Debug)]
78pub enum AssociationRJResult {
79 Permanent = 1,
80 Transient = 2,
81}
82
83impl AssociationRJResult {
84 fn from(value: u8) -> Option<AssociationRJResult> {
85 match value {
86 1 => Some(AssociationRJResult::Permanent),
87 2 => Some(AssociationRJResult::Transient),
88 _ => None,
89 }
90 }
91}
92
93#[derive(Clone, Eq, PartialEq, PartialOrd, Hash, Debug)]
94pub enum AssociationRJSource {
95 ServiceUser(AssociationRJServiceUserReason),
96 ServiceProviderASCE(AssociationRJServiceProviderASCEReason),
97 ServiceProviderPresentation(AssociationRJServiceProviderPresentationReason),
98}
99
100impl AssociationRJSource {
101 fn from(source: u8, reason: u8) -> Option<AssociationRJSource> {
102 let result = match (source, reason) {
103 (1, 1) => {
104 AssociationRJSource::ServiceUser(AssociationRJServiceUserReason::NoReasonGiven)
105 }
106 (1, 2) => AssociationRJSource::ServiceUser(
107 AssociationRJServiceUserReason::ApplicationContextNameNotSupported,
108 ),
109 (1, 3) => AssociationRJSource::ServiceUser(
110 AssociationRJServiceUserReason::CallingAETitleNotRecognized,
111 ),
112 (1, x) if x == 4 || x == 5 || x == 6 => {
113 AssociationRJSource::ServiceUser(AssociationRJServiceUserReason::Reserved(x))
114 }
115 (1, 7) => AssociationRJSource::ServiceUser(
116 AssociationRJServiceUserReason::CalledAETitleNotRecognized,
117 ),
118 (1, x) if x == 8 || x == 9 || x == 10 => {
120 AssociationRJSource::ServiceUser(AssociationRJServiceUserReason::Reserved(x))
121 }
122 (1, _) => {
123 return None;
124 }
125 (2, 1) => AssociationRJSource::ServiceProviderASCE(
126 AssociationRJServiceProviderASCEReason::NoReasonGiven,
127 ),
128 (2, 2) => AssociationRJSource::ServiceProviderASCE(
129 AssociationRJServiceProviderASCEReason::ProtocolVersionNotSupported,
130 ),
131 (2, _) => {
132 return None;
133 }
134 (3, 0) => AssociationRJSource::ServiceProviderPresentation(
135 AssociationRJServiceProviderPresentationReason::Reserved(0),
136 ),
137 (3, 1) => AssociationRJSource::ServiceProviderPresentation(
138 AssociationRJServiceProviderPresentationReason::TemporaryCongestion,
139 ),
140 (3, 2) => AssociationRJSource::ServiceProviderPresentation(
141 AssociationRJServiceProviderPresentationReason::LocalLimitExceeded,
142 ),
143 (3, x) if x == 3 || x == 4 || x == 5 || x == 6 || x == 7 => {
144 AssociationRJSource::ServiceProviderPresentation(
145 AssociationRJServiceProviderPresentationReason::Reserved(x),
146 )
147 }
148 _ => {
149 return None;
150 }
151 };
152 Some(result)
153 }
154}
155
156impl Display for AssociationRJSource {
157 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158 match self {
159 AssociationRJSource::ServiceUser(r) => Display::fmt(r, f),
160 AssociationRJSource::ServiceProviderASCE(r) => Display::fmt(r, f),
161 AssociationRJSource::ServiceProviderPresentation(r) => Display::fmt(r, f),
162 }
163 }
164}
165
166#[derive(Clone, Eq, PartialEq, PartialOrd, Hash, Debug)]
167pub enum AssociationRJServiceUserReason {
168 NoReasonGiven,
169 ApplicationContextNameNotSupported,
170 CallingAETitleNotRecognized,
171 CalledAETitleNotRecognized,
172 Reserved(u8),
173}
174
175impl Display for AssociationRJServiceUserReason {
176 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
177 match self {
178 AssociationRJServiceUserReason::NoReasonGiven => f.write_str("no reason given"),
179 AssociationRJServiceUserReason::ApplicationContextNameNotSupported => {
180 f.write_str("application context name not supported")
181 }
182 AssociationRJServiceUserReason::CallingAETitleNotRecognized => {
183 f.write_str("calling AE title not recognized")
184 }
185 AssociationRJServiceUserReason::CalledAETitleNotRecognized => {
186 f.write_str("called AE title not recognized")
187 }
188 AssociationRJServiceUserReason::Reserved(code) => write!(f, "reserved code {}", code),
189 }
190 }
191}
192
193#[derive(Clone, Eq, PartialEq, PartialOrd, Hash, Debug)]
194pub enum AssociationRJServiceProviderASCEReason {
195 NoReasonGiven,
196 ProtocolVersionNotSupported,
197}
198
199impl Display for AssociationRJServiceProviderASCEReason {
200 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
201 match self {
202 AssociationRJServiceProviderASCEReason::NoReasonGiven => f.write_str("no reason given"),
203 AssociationRJServiceProviderASCEReason::ProtocolVersionNotSupported => {
204 f.write_str("protocol version not supported")
205 }
206 }
207 }
208}
209
210#[derive(Clone, Eq, PartialEq, PartialOrd, Hash, Debug)]
211pub enum AssociationRJServiceProviderPresentationReason {
212 TemporaryCongestion,
213 LocalLimitExceeded,
214 Reserved(u8),
215}
216
217impl Display for AssociationRJServiceProviderPresentationReason {
218 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
219 match self {
220 AssociationRJServiceProviderPresentationReason::TemporaryCongestion => {
221 f.write_str("temporary congestion")
222 }
223 AssociationRJServiceProviderPresentationReason::LocalLimitExceeded => {
224 f.write_str("local limit exceeded")
225 }
226 AssociationRJServiceProviderPresentationReason::Reserved(code) => {
227 write!(f, "reserved code {}", code)
228 }
229 }
230 }
231}
232
233#[derive(Clone, Eq, PartialEq, PartialOrd, Hash, Debug)]
234pub struct PDataValue {
235 pub presentation_context_id: u8,
236 pub value_type: PDataValueType,
237 pub is_last: bool,
238 pub data: Vec<u8>,
239}
240
241#[derive(Clone, Eq, PartialEq, PartialOrd, Hash, Debug)]
242pub enum PDataValueType {
243 Command,
244 Data,
245}
246
247#[derive(Clone, Eq, PartialEq, PartialOrd, Hash, Debug)]
248pub enum AbortRQSource {
249 ServiceUser,
250 ServiceProvider(AbortRQServiceProviderReason),
251 Reserved,
252}
253
254impl AbortRQSource {
255 fn from(source: u8, reason: u8) -> Option<AbortRQSource> {
256 let result = match (source, reason) {
257 (0, _) => AbortRQSource::ServiceUser,
258 (1, _) => AbortRQSource::Reserved,
259 (2, 0) => {
260 AbortRQSource::ServiceProvider(AbortRQServiceProviderReason::ReasonNotSpecified)
261 }
262 (2, 1) => AbortRQSource::ServiceProvider(AbortRQServiceProviderReason::UnrecognizedPdu),
263 (2, 2) => AbortRQSource::ServiceProvider(AbortRQServiceProviderReason::UnexpectedPdu),
264 (2, 3) => AbortRQSource::ServiceProvider(AbortRQServiceProviderReason::Reserved),
265 (2, 4) => AbortRQSource::ServiceProvider(
266 AbortRQServiceProviderReason::UnrecognizedPduParameter,
267 ),
268 (2, 5) => {
269 AbortRQSource::ServiceProvider(AbortRQServiceProviderReason::UnexpectedPduParameter)
270 }
271 (2, 6) => {
272 AbortRQSource::ServiceProvider(AbortRQServiceProviderReason::InvalidPduParameter)
273 }
274 (_, _) => {
275 return None;
276 }
277 };
278
279 Some(result)
280 }
281}
282
283#[derive(Clone, Eq, PartialEq, PartialOrd, Hash, Debug)]
285pub enum AbortRQServiceProviderReason {
286 ReasonNotSpecified,
288 UnrecognizedPdu,
290 UnexpectedPdu,
292 Reserved,
294 UnrecognizedPduParameter,
296 UnexpectedPduParameter,
298 InvalidPduParameter,
300}
301
302impl Display for AbortRQServiceProviderReason {
303 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
304 let msg = match self {
305 AbortRQServiceProviderReason::ReasonNotSpecified => "reason not specified",
306 AbortRQServiceProviderReason::UnrecognizedPdu => "unrecognized PDU",
307 AbortRQServiceProviderReason::UnexpectedPdu => "unexpected PDU",
308 AbortRQServiceProviderReason::Reserved => "reserved code",
309 AbortRQServiceProviderReason::UnrecognizedPduParameter => "unrecognized PDU parameter",
310 AbortRQServiceProviderReason::UnexpectedPduParameter => "unexpected PDU parameter",
311 AbortRQServiceProviderReason::InvalidPduParameter => "invalid PDU parameter",
312 };
313 f.write_str(msg)
314 }
315}
316
317#[derive(Clone, Eq, PartialEq, PartialOrd, Hash, Debug)]
318pub enum PduVariableItem {
319 Unknown(u8),
320 ApplicationContext(String),
321 PresentationContextProposed(PresentationContextProposed),
322 PresentationContextResult(PresentationContextResult),
323 UserVariables(Vec<UserVariableItem>),
324}
325
326#[derive(Clone, Eq, PartialEq, PartialOrd, Hash, Debug)]
327pub enum UserVariableItem {
328 Unknown(u8, Vec<u8>),
329 MaxLength(u32),
330 ImplementationClassUID(String),
331 ImplementationVersionName(String),
332 SopClassExtendedNegotiationSubItem(String, Vec<u8>),
333 UserIdentityItem(UserIdentity),
334}
335
336#[derive(Clone, Eq, PartialEq, PartialOrd, Hash, Debug)]
337pub struct UserIdentity {
338 positive_response_requested: bool,
339 identity_type: UserIdentityType,
340 primary_field: Vec<u8>,
341 secondary_field: Vec<u8>,
342}
343impl UserIdentity {
344 pub fn new(
345 positive_response_requested: bool,
346 identity_type: UserIdentityType,
347 primary_field: Vec<u8>,
348 secondary_field: Vec<u8>,
349 ) -> Self {
350 UserIdentity {
351 positive_response_requested,
352 identity_type,
353 primary_field,
354 secondary_field,
355 }
356 }
357
358 pub fn positive_response_requested(&self) -> bool {
359 self.positive_response_requested
360 }
361
362 pub fn identity_type(&self) -> UserIdentityType {
363 self.identity_type.clone()
364 }
365
366 pub fn primary_field(&self) -> Vec<u8> {
367 self.primary_field.clone()
368 }
369
370 pub fn secondary_field(&self) -> Vec<u8> {
371 self.secondary_field.clone()
372 }
373}
374
375#[derive(Clone, Eq, PartialEq, PartialOrd, Hash, Debug)]
376#[non_exhaustive]
377pub enum UserIdentityType {
378 Username,
379 UsernamePassword,
380 KerberosServiceTicket,
381 SamlAssertion,
382 Jwt,
383}
384impl UserIdentityType {
385 fn from(user_identity_type: u8) -> Option<Self> {
386 match user_identity_type {
387 1 => Some(Self::Username),
388 2 => Some(Self::UsernamePassword),
389 3 => Some(Self::KerberosServiceTicket),
390 4 => Some(Self::SamlAssertion),
391 5 => Some(Self::Jwt),
392 _ => None,
393 }
394 }
395
396 fn to_u8(&self) -> u8 {
397 match self {
398 Self::Username => 1,
399 Self::UsernamePassword => 2,
400 Self::KerberosServiceTicket => 3,
401 Self::SamlAssertion => 4,
402 Self::Jwt => 5,
403 }
404 }
405}
406
407#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Hash)]
409pub enum Pdu {
410 Unknown { pdu_type: u8, data: Vec<u8> },
412 AssociationRQ(AssociationRQ),
414 AssociationAC(AssociationAC),
416 AssociationRJ(AssociationRJ),
418 PData { data: Vec<PDataValue> },
420 ReleaseRQ,
422 ReleaseRP,
424 AbortRQ { source: AbortRQSource },
426}
427
428impl Pdu {
429 pub fn short_description(&self) -> impl std::fmt::Display + '_ {
431 PduShortDescription(self)
432 }
433}
434
435struct PduShortDescription<'a>(&'a Pdu);
436
437impl std::fmt::Display for PduShortDescription<'_> {
438 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
439 match self.0 {
440 Pdu::Unknown { pdu_type, data } => {
441 write!(
442 f,
443 "Unknown {{pdu_type: {}, data: {} bytes }}",
444 pdu_type,
445 data.len()
446 )
447 }
448 Pdu::AssociationRQ { .. }
449 | Pdu::AssociationAC { .. }
450 | Pdu::AssociationRJ { .. }
451 | Pdu::ReleaseRQ
452 | Pdu::ReleaseRP
453 | Pdu::AbortRQ { .. } => std::fmt::Debug::fmt(self.0, f),
454 Pdu::PData { data } => {
455 if data.len() == 1 {
456 write!(
457 f,
458 "PData [({:?}, {} bytes)]",
459 data[0].value_type,
460 data[0].data.len()
461 )
462 } else if data.len() == 2 {
463 write!(
464 f,
465 "PData [({:?}, {} bytes), ({:?}, {} bytes)]",
466 data[0].value_type,
467 data[0].data.len(),
468 data[1].value_type,
469 data[1].data.len(),
470 )
471 } else {
472 write!(f, "PData [{} p-data values]", data.len())
473 }
474 }
475 }
476 }
477}
478
479#[derive(Debug, Clone, Eq, Hash, PartialEq, PartialOrd)]
481pub struct AssociationRQ {
482 pub protocol_version: u16,
483 pub calling_ae_title: String,
484 pub called_ae_title: String,
485 pub application_context_name: String,
486 pub presentation_contexts: Vec<PresentationContextProposed>,
487 pub user_variables: Vec<UserVariableItem>,
488}
489
490impl From<AssociationRQ> for Pdu {
491 fn from(value: AssociationRQ) -> Self {
492 Pdu::AssociationRQ(value)
493 }
494}
495
496#[derive(Debug, Clone, Eq, Hash, PartialEq, PartialOrd)]
498pub struct AssociationAC {
499 pub protocol_version: u16,
500 pub calling_ae_title: String,
501 pub called_ae_title: String,
502 pub application_context_name: String,
503 pub presentation_contexts: Vec<PresentationContextResult>,
504 pub user_variables: Vec<UserVariableItem>,
505}
506
507impl From<AssociationAC> for Pdu {
508 fn from(value: AssociationAC) -> Self {
509 Pdu::AssociationAC(value)
510 }
511}
512
513#[derive(Debug, Clone, Eq, Hash, PartialEq, PartialOrd)]
515pub struct AssociationRJ {
516 pub result: AssociationRJResult,
517 pub source: AssociationRJSource,
518}
519
520impl From<AssociationRJ> for Pdu {
521 fn from(value: AssociationRJ) -> Self {
522 Pdu::AssociationRJ(value)
523 }
524}
525
526#[cfg(test)]
527mod tests {
528 use crate::pdu::{PDataValue, PDataValueType};
529
530 use super::Pdu;
531
532 #[test]
533 fn pdu_short_description() {
534 let pdu = Pdu::AbortRQ {
535 source: super::AbortRQSource::ServiceUser,
536 };
537 assert_eq!(
538 &pdu.short_description().to_string(),
539 "AbortRQ { source: ServiceUser }",
540 );
541
542 let pdu = Pdu::PData {
543 data: vec![PDataValue {
544 is_last: true,
545 presentation_context_id: 2,
546 value_type: PDataValueType::Data,
547 data: vec![0x55; 384],
548 }],
549 };
550 assert_eq!(
551 &pdu.short_description().to_string(),
552 "PData [(Data, 384 bytes)]",
553 );
554 }
555}