Skip to main content

netget/server/m3ua/
tcap.rs

1//! ITU-T TCAP (Q.773): transaction messages, the dialogue portion (AARQ / AARE / ABRT carried in
2//! an EXTERNAL under the dialogue-as-id) and the component portion (Invoke, ReturnResult,
3//! ReturnError, Reject). MAP and CAP ride in the components; the application context name in the
4//! dialogue portion says which.
5//!
6//! Decoding goes through the bounded [`super::ber`] reader. On top of its depth and element
7//! bounds this layer caps the components in one message at [`MAX_COMPONENTS`] and transaction
8//! ids at the four octets Q.773 allows.
9
10use super::ber::{self, BerError, Tag, Tlv};
11use serde_json::{json, Value};
12
13/// Components accepted in one TCAP message. Real MAP and CAP messages carry one to three.
14pub const MAX_COMPONENTS: usize = 16;
15
16/// `dialogue-as-id` {itu-t recommendation q 773 as(1) dialogue-as(1) version1(1)}.
17pub const DIALOGUE_AS_ID: &[u64] = &[0, 0, 17, 773, 1, 1, 1];
18/// `uni-dialogue-as-id`.
19pub const UNI_DIALOGUE_AS_ID: &[u64] = &[0, 0, 17, 773, 1, 2, 1];
20/// MAP's `map-DialogueAS` {0 4 0 0 1 1 1 1}, under which MAP-OpenInfo travels.
21pub const MAP_DIALOGUE_AS: &[u64] = &[0, 4, 0, 0, 1, 1, 1, 1];
22
23/// P-Abort causes (Q.773 section 3.1).
24pub const P_ABORT_UNRECOGNIZED_MESSAGE_TYPE: u8 = 0;
25pub const P_ABORT_UNRECOGNIZED_TRANSACTION_ID: u8 = 1;
26pub const P_ABORT_BADLY_FORMATTED_TRANSACTION_PORTION: u8 = 2;
27pub const P_ABORT_INCORRECT_TRANSACTION_PORTION: u8 = 3;
28pub const P_ABORT_RESOURCE_LIMITATION: u8 = 4;
29
30/// Associate-source-diagnostic values for dialogue-service-user.
31pub const DIAG_USER_NULL: u8 = 0;
32pub const DIAG_USER_NO_REASON_GIVEN: u8 = 1;
33pub const DIAG_USER_ACN_NOT_SUPPORTED: u8 = 2;
34
35/// Reject problem types and the codes used here.
36pub const PROBLEM_GENERAL: u8 = 0;
37pub const PROBLEM_INVOKE: u8 = 1;
38pub const PROBLEM_RETURN_RESULT: u8 = 2;
39pub const PROBLEM_RETURN_ERROR: u8 = 3;
40pub const GENERAL_MISTYPED_COMPONENT: u8 = 1;
41pub const GENERAL_BADLY_STRUCTURED_COMPONENT: u8 = 2;
42pub const INVOKE_UNRECOGNIZED_OPERATION: u8 = 1;
43pub const INVOKE_MISTYPED_PARAMETER: u8 = 2;
44pub const INVOKE_RESOURCE_LIMITATION: u8 = 3;
45
46#[derive(Clone, Copy, Debug, PartialEq, Eq)]
47pub enum TcKind {
48    Unidirectional,
49    Begin,
50    End,
51    Continue,
52    Abort,
53}
54
55impl TcKind {
56    pub fn name(self) -> &'static str {
57        match self {
58            TcKind::Unidirectional => "unidirectional",
59            TcKind::Begin => "begin",
60            TcKind::End => "end",
61            TcKind::Continue => "continue",
62            TcKind::Abort => "abort",
63        }
64    }
65
66    fn app_number(self) -> u32 {
67        match self {
68            TcKind::Unidirectional => 1,
69            TcKind::Begin => 2,
70            TcKind::End => 4,
71            TcKind::Continue => 5,
72            TcKind::Abort => 7,
73        }
74    }
75}
76
77/// An operation or error code: local (an INTEGER) or global (an OBJECT IDENTIFIER).
78#[derive(Clone, Debug, PartialEq, Eq)]
79pub enum Code {
80    Local(i64),
81    Global(Vec<u64>),
82}
83
84impl Code {
85    fn decode(t: &Tlv) -> Result<Code, BerError> {
86        if t.tag == Tag::INTEGER {
87            Ok(Code::Local(t.as_i64()?))
88        } else if t.tag == Tag::OID {
89            Ok(Code::Global(ber::decode_oid(&t.value)?))
90        } else {
91            Err(BerError(format!(
92                "operation/error code is {}, expected INTEGER or OBJECT IDENTIFIER",
93                t.tag.label()
94            )))
95        }
96    }
97
98    fn encode(&self) -> Vec<u8> {
99        match self {
100            Code::Local(v) => ber::integer(Tag::INTEGER, *v),
101            Code::Global(arcs) => ber::tlv(Tag::OID, &ber::oid_contents(arcs)),
102        }
103    }
104
105    pub fn local(&self) -> Option<i64> {
106        match self {
107            Code::Local(v) => Some(*v),
108            Code::Global(_) => None,
109        }
110    }
111}
112
113/// A component.
114#[derive(Clone, Debug, PartialEq, Eq)]
115pub enum Component {
116    Invoke {
117        invoke_id: i64,
118        linked_id: Option<i64>,
119        opcode: Code,
120        parameter: Option<Tlv>,
121    },
122    ReturnResult {
123        last: bool,
124        invoke_id: i64,
125        opcode: Option<Code>,
126        parameter: Option<Tlv>,
127    },
128    ReturnError {
129        invoke_id: i64,
130        error: Code,
131        parameter: Option<Tlv>,
132    },
133    Reject {
134        invoke_id: Option<i64>,
135        problem_type: u8,
136        problem: u8,
137    },
138}
139
140impl Component {
141    pub fn invoke_id(&self) -> Option<i64> {
142        match self {
143            Component::Invoke { invoke_id, .. }
144            | Component::ReturnResult { invoke_id, .. }
145            | Component::ReturnError { invoke_id, .. } => Some(*invoke_id),
146            Component::Reject { invoke_id, .. } => *invoke_id,
147        }
148    }
149
150    fn decode(t: &Tlv) -> Result<Component, BerError> {
151        if t.tag.class != ber::CLASS_CONTEXT || !t.tag.constructed {
152            return Err(BerError(format!("component tag {}", t.tag.label())));
153        }
154        let kids = &t.children;
155        let invoke_id = |i: usize| -> Result<i64, BerError> {
156            let k = kids
157                .get(i)
158                .filter(|k| k.tag == Tag::INTEGER)
159                .ok_or_else(|| BerError("component without an invokeID".into()))?;
160            let v = k.as_i64()?;
161            if !(-128..=127).contains(&v) {
162                return Err(BerError(format!("invokeID {v} outside -128..127")));
163            }
164            Ok(v)
165        };
166        match t.tag.number {
167            1 => {
168                let id = invoke_id(0)?;
169                let mut i = 1;
170                let mut linked_id = None;
171                if let Some(k) = kids.get(i).filter(|k| k.tag == Tag::context(0, false)) {
172                    linked_id = Some(ber::decode_integer(&k.value)?);
173                    i += 1;
174                }
175                let opcode = Code::decode(
176                    kids.get(i)
177                        .ok_or_else(|| BerError("Invoke without an operation code".into()))?,
178                )?;
179                Ok(Component::Invoke {
180                    invoke_id: id,
181                    linked_id,
182                    opcode,
183                    parameter: kids.get(i + 1).cloned(),
184                })
185            }
186            2 | 7 => {
187                let id = invoke_id(0)?;
188                let (opcode, parameter) = match kids.get(1) {
189                    Some(s) if s.tag == Tag::SEQUENCE => {
190                        let op = s
191                            .children
192                            .first()
193                            .ok_or_else(|| BerError("ReturnResult result without opcode".into()))?;
194                        (Some(Code::decode(op)?), s.children.get(1).cloned())
195                    }
196                    Some(other) => {
197                        return Err(BerError(format!(
198                            "ReturnResult result is {}, expected SEQUENCE",
199                            other.tag.label()
200                        )))
201                    }
202                    None => (None, None),
203                };
204                Ok(Component::ReturnResult {
205                    last: t.tag.number == 2,
206                    invoke_id: id,
207                    opcode,
208                    parameter,
209                })
210            }
211            3 => {
212                let id = invoke_id(0)?;
213                let error = Code::decode(
214                    kids.get(1)
215                        .ok_or_else(|| BerError("ReturnError without an error code".into()))?,
216                )?;
217                Ok(Component::ReturnError {
218                    invoke_id: id,
219                    error,
220                    parameter: kids.get(2).cloned(),
221                })
222            }
223            4 => {
224                let first = kids
225                    .first()
226                    .ok_or_else(|| BerError("Reject without an invokeID".into()))?;
227                let invoke_id = if first.tag == Tag::NULL {
228                    None
229                } else {
230                    Some(invoke_id(0)?)
231                };
232                let p = kids
233                    .get(1)
234                    .ok_or_else(|| BerError("Reject without a problem".into()))?;
235                if p.tag.class != ber::CLASS_CONTEXT || p.tag.number > 3 {
236                    return Err(BerError(format!("Reject problem {}", p.tag.label())));
237                }
238                Ok(Component::Reject {
239                    invoke_id,
240                    problem_type: p.tag.number as u8,
241                    problem: ber::decode_integer(&p.value)? as u8,
242                })
243            }
244            n => Err(BerError(format!("unknown component type [{n}]"))),
245        }
246    }
247
248    pub fn encode(&self) -> Vec<u8> {
249        match self {
250            Component::Invoke {
251                invoke_id,
252                linked_id,
253                opcode,
254                parameter,
255            } => {
256                let mut parts = vec![ber::integer(Tag::INTEGER, *invoke_id)];
257                if let Some(l) = linked_id {
258                    parts.push(ber::integer(Tag::context(0, false), *l));
259                }
260                parts.push(opcode.encode());
261                if let Some(p) = parameter {
262                    parts.push(p.encode());
263                }
264                ber::seq(Tag::context(1, true), &parts)
265            }
266            Component::ReturnResult {
267                last,
268                invoke_id,
269                opcode,
270                parameter,
271            } => {
272                let mut parts = vec![ber::integer(Tag::INTEGER, *invoke_id)];
273                if let Some(op) = opcode {
274                    let mut inner = vec![op.encode()];
275                    if let Some(p) = parameter {
276                        inner.push(p.encode());
277                    }
278                    parts.push(ber::seq(Tag::SEQUENCE, &inner));
279                }
280                ber::seq(Tag::context(if *last { 2 } else { 7 }, true), &parts)
281            }
282            Component::ReturnError {
283                invoke_id,
284                error,
285                parameter,
286            } => {
287                let mut parts = vec![ber::integer(Tag::INTEGER, *invoke_id), error.encode()];
288                if let Some(p) = parameter {
289                    parts.push(p.encode());
290                }
291                ber::seq(Tag::context(3, true), &parts)
292            }
293            Component::Reject {
294                invoke_id,
295                problem_type,
296                problem,
297            } => {
298                let id = match invoke_id {
299                    Some(i) => ber::integer(Tag::INTEGER, *i),
300                    None => ber::tlv(Tag::NULL, &[]),
301                };
302                let p = ber::integer(Tag::context(*problem_type as u32, false), *problem as i64);
303                ber::seq(Tag::context(4, true), &[id, p])
304            }
305        }
306    }
307
308    pub fn kind_name(&self) -> &'static str {
309        match self {
310            Component::Invoke { .. } => "invoke",
311            Component::ReturnResult { last: true, .. } => "return_result_last",
312            Component::ReturnResult { last: false, .. } => "return_result_not_last",
313            Component::ReturnError { .. } => "return_error",
314            Component::Reject { .. } => "reject",
315        }
316    }
317}
318
319/// The dialogue PDU carried in the dialogue portion.
320#[derive(Clone, Debug, PartialEq, Eq)]
321pub enum DialoguePdu {
322    /// AARQ: the initiator proposes an application context.
323    Request {
324        acn: Vec<u64>,
325        user_information: Vec<Tlv>,
326    },
327    /// AARE: the responder accepts (result 0) or rejects (1) it.
328    Response {
329        acn: Vec<u64>,
330        result: u8,
331        diagnostic_provider: bool,
332        diagnostic: u8,
333    },
334    /// ABRT: a user (0) or provider (1) abort.
335    Abort {
336        source: u8,
337        user_information: Vec<Tlv>,
338    },
339    /// AUDT on a unidirectional message.
340    Unidirectional { acn: Vec<u64> },
341}
342
343impl DialoguePdu {
344    pub fn acn(&self) -> Option<&[u64]> {
345        match self {
346            DialoguePdu::Request { acn, .. }
347            | DialoguePdu::Response { acn, .. }
348            | DialoguePdu::Unidirectional { acn } => Some(acn),
349            DialoguePdu::Abort { .. } => None,
350        }
351    }
352
353    fn decode(portion: &Tlv) -> Result<DialoguePdu, BerError> {
354        let external = portion
355            .children
356            .first()
357            .filter(|e| e.tag.is_universal(ber::UNIV_EXTERNAL))
358            .ok_or_else(|| BerError("dialogue portion without an EXTERNAL".into()))?;
359        let as_id = external
360            .univ(ber::UNIV_OID)
361            .map(|o| ber::decode_oid(&o.value))
362            .transpose()?
363            .unwrap_or_default();
364        let single = external
365            .ctx(0)
366            .and_then(|s| s.children.first())
367            .ok_or_else(|| BerError("dialogue EXTERNAL without single-ASN1-type".into()))?;
368        let unidirectional = as_id == UNI_DIALOGUE_AS_ID;
369        if as_id != DIALOGUE_AS_ID && !unidirectional {
370            return Err(BerError(format!(
371                "dialogue portion under {} rather than the dialogue-as-id",
372                ber::oid_string(&as_id)
373            )));
374        }
375        let acn = |pdu: &Tlv| -> Result<Vec<u64>, BerError> {
376            let name = pdu
377                .ctx(1)
378                .and_then(|a| a.univ(ber::UNIV_OID))
379                .ok_or_else(|| BerError("dialogue PDU without application-context-name".into()))?;
380            ber::decode_oid(&name.value)
381        };
382        let user_info =
383            |pdu: &Tlv| -> Vec<Tlv> { pdu.ctx(30).map(|u| u.children.clone()).unwrap_or_default() };
384        if single.tag.class != ber::CLASS_APPLICATION {
385            return Err(BerError(format!("dialogue PDU tag {}", single.tag.label())));
386        }
387        Ok(match (single.tag.number, unidirectional) {
388            (0, true) => DialoguePdu::Unidirectional { acn: acn(single)? },
389            (0, false) => DialoguePdu::Request {
390                acn: acn(single)?,
391                user_information: user_info(single),
392            },
393            (1, false) => {
394                let result = single
395                    .ctx(2)
396                    .and_then(|r| r.univ(ber::UNIV_INTEGER))
397                    .map(|i| i.as_i64())
398                    .transpose()?
399                    .unwrap_or(0) as u8;
400                let (provider, diagnostic) = match single.ctx(3).and_then(|d| d.children.first()) {
401                    Some(choice) => (
402                        choice.tag.number == 2,
403                        choice
404                            .univ(ber::UNIV_INTEGER)
405                            .map(|i| i.as_i64())
406                            .transpose()?
407                            .unwrap_or(0) as u8,
408                    ),
409                    None => (false, 0),
410                };
411                DialoguePdu::Response {
412                    acn: acn(single)?,
413                    result,
414                    diagnostic_provider: provider,
415                    diagnostic,
416                }
417            }
418            (4, false) => DialoguePdu::Abort {
419                source: single
420                    .ctx(0)
421                    .map(|s| ber::decode_integer(&s.value))
422                    .transpose()?
423                    .unwrap_or(0) as u8,
424                user_information: user_info(single),
425            },
426            (n, _) => return Err(BerError(format!("unknown dialogue PDU [APPLICATION {n}]"))),
427        })
428    }
429
430    pub fn encode(&self) -> Vec<u8> {
431        let version = ber::tlv(Tag::context(0, false), &[0x07, 0x80]);
432        let acn_field = |acn: &[u64]| {
433            ber::seq(
434                Tag::context(1, true),
435                &[ber::tlv(Tag::OID, &ber::oid_contents(acn))],
436            )
437        };
438        let (as_id, pdu) = match self {
439            DialoguePdu::Request {
440                acn,
441                user_information,
442            } => {
443                let mut parts = vec![version, acn_field(acn)];
444                if !user_information.is_empty() {
445                    let ui: Vec<Vec<u8>> = user_information.iter().map(|u| u.encode()).collect();
446                    parts.push(ber::seq(Tag::context(30, true), &ui));
447                }
448                (DIALOGUE_AS_ID, ber::seq(Tag::application(0, true), &parts))
449            }
450            DialoguePdu::Response {
451                acn,
452                result,
453                diagnostic_provider,
454                diagnostic,
455            } => {
456                let result_field = ber::seq(
457                    Tag::context(2, true),
458                    &[ber::integer(Tag::INTEGER, *result as i64)],
459                );
460                let diag = ber::seq(
461                    Tag::context(3, true),
462                    &[ber::seq(
463                        Tag::context(if *diagnostic_provider { 2 } else { 1 }, true),
464                        &[ber::integer(Tag::INTEGER, *diagnostic as i64)],
465                    )],
466                );
467                (
468                    DIALOGUE_AS_ID,
469                    ber::seq(
470                        Tag::application(1, true),
471                        &[version, acn_field(acn), result_field, diag],
472                    ),
473                )
474            }
475            DialoguePdu::Abort {
476                source,
477                user_information,
478            } => {
479                let mut parts = vec![ber::integer(Tag::context(0, false), *source as i64)];
480                if !user_information.is_empty() {
481                    let ui: Vec<Vec<u8>> = user_information.iter().map(|u| u.encode()).collect();
482                    parts.push(ber::seq(Tag::context(30, true), &ui));
483                }
484                (DIALOGUE_AS_ID, ber::seq(Tag::application(4, true), &parts))
485            }
486            DialoguePdu::Unidirectional { acn } => (
487                UNI_DIALOGUE_AS_ID,
488                ber::seq(Tag::application(0, true), &[version, acn_field(acn)]),
489            ),
490        };
491        let external = ber::seq(
492            Tag::universal(ber::UNIV_EXTERNAL, true),
493            &[
494                ber::tlv(Tag::OID, &ber::oid_contents(as_id)),
495                ber::seq(Tag::context(0, true), &[pdu]),
496            ],
497        );
498        ber::seq(Tag::application(11, true), &[external])
499    }
500
501    pub fn to_json(&self) -> Value {
502        match self {
503            DialoguePdu::Request {
504                acn,
505                user_information,
506            } => json!({
507                "pdu": "AARQ",
508                "application_context": ber::oid_string(acn),
509                "map_open": map_open_info(user_information),
510            }),
511            DialoguePdu::Response {
512                acn,
513                result,
514                diagnostic_provider,
515                diagnostic,
516            } => json!({
517                "pdu": "AARE",
518                "application_context": ber::oid_string(acn),
519                "result": if *result == 0 { "accepted" } else { "rejected" },
520                "diagnostic_source": if *diagnostic_provider { "provider" } else { "user" },
521                "diagnostic": diagnostic_name(*diagnostic_provider, *diagnostic),
522            }),
523            DialoguePdu::Abort { source, .. } => json!({
524                "pdu": "ABRT",
525                "abort_source": if *source == 0 { "dialogue_service_user" } else { "dialogue_service_provider" },
526            }),
527            DialoguePdu::Unidirectional { acn } => json!({
528                "pdu": "AUDT",
529                "application_context": ber::oid_string(acn),
530            }),
531        }
532    }
533}
534
535fn diagnostic_name(provider: bool, v: u8) -> &'static str {
536    match (provider, v) {
537        (false, 0) => "null",
538        (false, 1) => "no_reason_given",
539        (false, 2) => "application_context_name_not_supported",
540        (true, 0) => "null",
541        (true, 1) => "no_reason_given",
542        (true, 2) => "no_common_dialogue_portion",
543        _ => "unknown",
544    }
545}
546
547/// MAP-OpenInfo (TS 29.002 section 7.3) as the AARQ user information: an EXTERNAL under
548/// map-DialogueAS carrying map-open with the destination and origination references.
549pub fn map_open_user_information(
550    destination: Option<&super::gsm::Address>,
551    origination: Option<&super::gsm::Address>,
552) -> Result<Tlv, String> {
553    let mut refs = Vec::new();
554    if let Some(d) = destination {
555        refs.push(ber::tlv(
556            Tag::context(0, false),
557            &d.encode(super::gsm::MAX_ADDRESS_DIGITS)?,
558        ));
559    }
560    if let Some(o) = origination {
561        refs.push(ber::tlv(
562            Tag::context(1, false),
563            &o.encode(super::gsm::MAX_ADDRESS_DIGITS)?,
564        ));
565    }
566    let external = ber::seq(
567        Tag::universal(ber::UNIV_EXTERNAL, true),
568        &[
569            ber::tlv(Tag::OID, &ber::oid_contents(MAP_DIALOGUE_AS)),
570            ber::seq(
571                Tag::context(0, true),
572                &[ber::seq(Tag::context(0, true), &refs)],
573            ),
574        ],
575    );
576    ber::decode(&external).map_err(|e| e.to_string())
577}
578
579/// MAP-OpenInfo (TS 29.002 section 7.3) from the AARQ's user information, when present:
580/// destination and origination references.
581fn map_open_info(user_information: &[Tlv]) -> Value {
582    for ext in user_information {
583        let is_map = ext
584            .univ(ber::UNIV_OID)
585            .and_then(|o| ber::decode_oid(&o.value).ok())
586            .map(|o| o == MAP_DIALOGUE_AS)
587            .unwrap_or(false);
588        if !is_map {
589            continue;
590        }
591        let Some(open) = ext
592            .ctx(0)
593            .and_then(|s| s.children.first())
594            .filter(|p| p.tag.is_context(0))
595        else {
596            continue;
597        };
598        let reference = |n: u32| {
599            open.ctx(n)
600                .and_then(|a| super::gsm::Address::decode(&a.value).ok())
601                .map(|a| a.to_json())
602        };
603        return json!({
604            "destination_reference": reference(0),
605            "origination_reference": reference(1),
606        });
607    }
608    Value::Null
609}
610
611/// A decoded TCAP message.
612#[derive(Clone, Debug, PartialEq, Eq)]
613pub struct TcMessage {
614    pub kind: TcKind,
615    pub otid: Option<Vec<u8>>,
616    pub dtid: Option<Vec<u8>>,
617    pub dialogue: Option<DialoguePdu>,
618    pub components: Vec<Component>,
619    pub p_abort_cause: Option<u8>,
620}
621
622fn tid(t: &Tlv, which: &str) -> Result<Vec<u8>, BerError> {
623    if t.value.is_empty() || t.value.len() > 4 {
624        return Err(BerError(format!(
625            "{which} transaction id of {} octets (1..4)",
626            t.value.len()
627        )));
628    }
629    Ok(t.value.clone())
630}
631
632impl TcMessage {
633    /// Decode a TCAP message. Errors carry the best P-Abort cause for a reply.
634    pub fn decode(o: &[u8]) -> Result<TcMessage, (u8, BerError)> {
635        let top = ber::decode(o).map_err(|e| (P_ABORT_BADLY_FORMATTED_TRANSACTION_PORTION, e))?;
636        if top.tag.class != ber::CLASS_APPLICATION || !top.tag.constructed {
637            return Err((
638                P_ABORT_UNRECOGNIZED_MESSAGE_TYPE,
639                BerError(format!("TCAP message tag {}", top.tag.label())),
640            ));
641        }
642        let kind = match top.tag.number {
643            1 => TcKind::Unidirectional,
644            2 => TcKind::Begin,
645            4 => TcKind::End,
646            5 => TcKind::Continue,
647            7 => TcKind::Abort,
648            n => {
649                return Err((
650                    P_ABORT_UNRECOGNIZED_MESSAGE_TYPE,
651                    BerError(format!("TCAP message type [APPLICATION {n}]")),
652                ))
653            }
654        };
655        let bad = |e: BerError| (P_ABORT_BADLY_FORMATTED_TRANSACTION_PORTION, e);
656        let app = |n: u32| top.child(ber::CLASS_APPLICATION, n);
657        let otid = app(8)
658            .map(|t| tid(t, "originating"))
659            .transpose()
660            .map_err(bad)?;
661        let dtid = app(9)
662            .map(|t| tid(t, "destination"))
663            .transpose()
664            .map_err(bad)?;
665        let (need_o, need_d) = match kind {
666            TcKind::Unidirectional => (false, false),
667            TcKind::Begin => (true, false),
668            TcKind::Continue => (true, true),
669            TcKind::End | TcKind::Abort => (false, true),
670        };
671        if need_o != otid.is_some() || need_d != dtid.is_some() {
672            return Err((
673                P_ABORT_INCORRECT_TRANSACTION_PORTION,
674                BerError(format!(
675                    "TC-{} with otid {} and dtid {}",
676                    kind.name(),
677                    if otid.is_some() { "present" } else { "absent" },
678                    if dtid.is_some() { "present" } else { "absent" }
679                )),
680            ));
681        }
682        let dialogue = app(11)
683            .map(DialoguePdu::decode)
684            .transpose()
685            .map_err(|e| (P_ABORT_BADLY_FORMATTED_TRANSACTION_PORTION, e))?;
686        let mut components = Vec::new();
687        if let Some(portion) = app(12) {
688            if portion.children.len() > MAX_COMPONENTS {
689                return Err((
690                    P_ABORT_RESOURCE_LIMITATION,
691                    BerError(format!(
692                        "{} components (at most {MAX_COMPONENTS})",
693                        portion.children.len()
694                    )),
695                ));
696            }
697            for c in &portion.children {
698                // A malformed component is a Reject, not a transaction-level abort; it is kept
699                // as a general-problem Reject marker for the caller to answer.
700                components.push(Component::decode(c).unwrap_or(Component::Reject {
701                    invoke_id: None,
702                    problem_type: PROBLEM_GENERAL,
703                    problem: GENERAL_BADLY_STRUCTURED_COMPONENT,
704                }));
705            }
706        }
707        let p_abort_cause = app(10)
708            .map(|c| ber::decode_integer(&c.value).map(|v| v as u8))
709            .transpose()
710            .map_err(bad)?;
711        Ok(TcMessage {
712            kind,
713            otid,
714            dtid,
715            dialogue,
716            components,
717            p_abort_cause,
718        })
719    }
720
721    pub fn encode(&self) -> Vec<u8> {
722        let mut parts = Vec::new();
723        if let Some(o) = &self.otid {
724            parts.push(ber::tlv(Tag::application(8, false), o));
725        }
726        if let Some(d) = &self.dtid {
727            parts.push(ber::tlv(Tag::application(9, false), d));
728        }
729        if let Some(c) = self.p_abort_cause {
730            parts.push(ber::integer(Tag::application(10, false), c as i64));
731        }
732        if let Some(d) = &self.dialogue {
733            parts.push(d.encode());
734        }
735        if !self.components.is_empty() {
736            let comps: Vec<Vec<u8>> = self.components.iter().map(|c| c.encode()).collect();
737            parts.push(ber::seq(Tag::application(12, true), &comps));
738        }
739        ber::seq(Tag::application(self.kind.app_number(), true), &parts)
740    }
741
742    /// A P-Abort answering a message whose originating transaction id is `their_tid`.
743    pub fn p_abort(their_tid: &[u8], cause: u8) -> TcMessage {
744        TcMessage {
745            kind: TcKind::Abort,
746            otid: None,
747            dtid: Some(their_tid.to_vec()),
748            dialogue: None,
749            components: Vec::new(),
750            p_abort_cause: Some(cause),
751        }
752    }
753}
754
755pub fn p_abort_name(c: u8) -> &'static str {
756    match c {
757        P_ABORT_UNRECOGNIZED_MESSAGE_TYPE => "unrecognized_message_type",
758        P_ABORT_UNRECOGNIZED_TRANSACTION_ID => "unrecognized_transaction_id",
759        P_ABORT_BADLY_FORMATTED_TRANSACTION_PORTION => "badly_formatted_transaction_portion",
760        P_ABORT_INCORRECT_TRANSACTION_PORTION => "incorrect_transaction_portion",
761        P_ABORT_RESOURCE_LIMITATION => "resource_limitation",
762        _ => "unknown",
763    }
764}
765
766pub fn reject_problem_name(problem_type: u8, problem: u8) -> String {
767    let (t, names): (&str, &[&str]) = match problem_type {
768        PROBLEM_GENERAL => (
769            "general",
770            &[
771                "unrecognized_component",
772                "mistyped_component",
773                "badly_structured_component",
774            ],
775        ),
776        PROBLEM_INVOKE => (
777            "invoke",
778            &[
779                "duplicate_invoke_id",
780                "unrecognized_operation",
781                "mistyped_parameter",
782                "resource_limitation",
783                "initiating_release",
784                "unrecognized_linked_id",
785                "linked_response_unexpected",
786                "unexpected_linked_operation",
787            ],
788        ),
789        PROBLEM_RETURN_RESULT => (
790            "return_result",
791            &[
792                "unrecognized_invoke_id",
793                "return_result_unexpected",
794                "mistyped_parameter",
795            ],
796        ),
797        PROBLEM_RETURN_ERROR => (
798            "return_error",
799            &[
800                "unrecognized_invoke_id",
801                "return_error_unexpected",
802                "unrecognized_error",
803                "unexpected_error",
804                "mistyped_parameter",
805            ],
806        ),
807        _ => ("unknown", &[]),
808    };
809    format!(
810        "{t}/{}",
811        names.get(problem as usize).copied().unwrap_or("unknown")
812    )
813}