1use super::ber::{self, BerError, Tag, Tlv};
11use serde_json::{json, Value};
12
13pub const MAX_COMPONENTS: usize = 16;
15
16pub const DIALOGUE_AS_ID: &[u64] = &[0, 0, 17, 773, 1, 1, 1];
18pub const UNI_DIALOGUE_AS_ID: &[u64] = &[0, 0, 17, 773, 1, 2, 1];
20pub const MAP_DIALOGUE_AS: &[u64] = &[0, 4, 0, 0, 1, 1, 1, 1];
22
23pub 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
30pub 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
35pub 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#[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#[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#[derive(Clone, Debug, PartialEq, Eq)]
321pub enum DialoguePdu {
322 Request {
324 acn: Vec<u64>,
325 user_information: Vec<Tlv>,
326 },
327 Response {
329 acn: Vec<u64>,
330 result: u8,
331 diagnostic_provider: bool,
332 diagnostic: u8,
333 },
334 Abort {
336 source: u8,
337 user_information: Vec<Tlv>,
338 },
339 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
547pub 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
579fn 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#[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 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 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 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}