1use super::ber;
27use super::codec::ProtocolData;
28use super::sccp::{self, SccpAddress, Scmg, Unitdata};
29use super::tcap::{self, Code, Component, DialoguePdu, TcKind, TcMessage};
30use super::{cap, gsm, map};
31use crate::utils::clock::Instant;
32use serde_json::{json, Value};
33use std::collections::HashMap;
34use std::time::Duration;
35
36pub const MAX_OPEN_DIALOGUES: usize = 1024;
38pub const DIALOGUE_IDLE_TIMEOUT: Duration = Duration::from_secs(60);
40pub const SI_SCCP: u8 = 3;
42pub const SS7_INTENT: &str = "ss7_intent";
44
45#[derive(Clone, Copy, Debug, PartialEq, Eq)]
46pub enum Family {
47 Map,
48 Cap,
49}
50
51impl Family {
52 pub fn name(self) -> &'static str {
53 match self {
54 Family::Map => "map",
55 Family::Cap => "cap",
56 }
57 }
58}
59
60#[derive(Clone, Debug, PartialEq, Eq)]
63pub struct Route {
64 pub opc: u32,
65 pub dpc: u32,
66 pub ni: u8,
67 pub mp: u8,
68 pub sls: u8,
69 pub remote: SccpAddress,
71 pub local: SccpAddress,
73 pub message_type: u8,
75 pub protocol_class: u8,
76 pub routing_context: Option<u32>,
77 pub network_appearance: Option<u32>,
78}
79
80impl Route {
81 pub fn reply_to(
83 pd: &ProtocolData,
84 ud: &Unitdata,
85 routing_context: Option<u32>,
86 network_appearance: Option<u32>,
87 ) -> Route {
88 Route {
89 opc: pd.dpc,
90 dpc: pd.opc,
91 ni: pd.ni,
92 mp: pd.mp,
93 sls: pd.sls,
94 remote: ud.calling.clone(),
95 local: ud.called.clone(),
96 message_type: if ud.message_type == sccp::MSG_XUDT {
97 sccp::MSG_XUDT
98 } else {
99 sccp::MSG_UDT
100 },
101 protocol_class: ud.protocol_class & 0x01,
102 routing_context,
103 network_appearance,
104 }
105 }
106
107 fn to_json(&self) -> Value {
108 json!({
109 "opc": self.opc,
110 "dpc": self.dpc,
111 "sls": self.sls,
112 "remote_address": self.remote.to_json(),
113 "local_address": self.local.to_json(),
114 })
115 }
116}
117
118#[derive(Clone, Debug, PartialEq, Eq)]
120pub struct Outgoing {
121 pub protocol_data: ProtocolData,
122 pub routing_context: Option<u32>,
123 pub network_appearance: Option<u32>,
124 pub label: String,
126}
127
128fn wrap(route: &Route, sccp_bytes: Vec<u8>, label: String) -> Outgoing {
129 Outgoing {
130 protocol_data: ProtocolData {
131 opc: route.opc,
132 dpc: route.dpc,
133 si: SI_SCCP,
134 ni: route.ni,
135 mp: route.mp,
136 sls: route.sls,
137 payload: sccp_bytes,
138 },
139 routing_context: route.routing_context,
140 network_appearance: route.network_appearance,
141 label,
142 }
143}
144
145fn unitdata(route: &Route, tcap_bytes: &[u8]) -> Result<Vec<u8>, String> {
146 if route.message_type == sccp::MSG_XUDT {
147 sccp::encode_xudt(
148 route.protocol_class,
149 false,
150 sccp::DEFAULT_HOP_COUNTER,
151 &route.remote,
152 &route.local,
153 tcap_bytes,
154 )
155 } else {
156 sccp::encode_udt(
157 route.protocol_class,
158 false,
159 &route.remote,
160 &route.local,
161 tcap_bytes,
162 )
163 }
164}
165
166fn send_tcap(route: &Route, m: &TcMessage, label: String) -> Result<Outgoing, String> {
167 Ok(wrap(route, unitdata(route, &m.encode())?, label))
168}
169
170#[derive(Clone, Debug)]
172pub struct Dialogue {
173 pub id: u32,
174 pub remote_tid: Option<Vec<u8>>,
175 pub acn: Option<Vec<u64>>,
176 pub family: Family,
177 pub aare_pending: bool,
179 pub route: Route,
180 pub last_activity: Instant,
181 pub peer_invokes: Vec<(i64, i64)>,
183 pub own_invokes: Vec<(i64, i64)>,
185 pub next_invoke_id: i64,
186 pub awaiting_answer: bool,
188 pub depth: u32,
190}
191
192impl Dialogue {
193 fn tid(&self) -> Vec<u8> {
194 self.id.to_be_bytes().to_vec()
195 }
196
197 fn take_invoke_id(&mut self) -> i64 {
198 let id = self.next_invoke_id;
199 self.next_invoke_id = if id >= 127 { 1 } else { id + 1 };
200 id
201 }
202}
203
204#[derive(Debug)]
206pub enum Inbound {
207 NotSccp,
209 Undecoded(String),
211 Handled {
213 replies: Vec<Outgoing>,
214 note: String,
215 },
216 Event(Ss7Event),
218}
219
220#[derive(Debug)]
221pub struct Ss7Event {
222 pub family: Family,
223 pub dialogue_id: u32,
224 pub kind: TcKind,
225 pub data: Value,
226 pub replies: Vec<Outgoing>,
228 pub ended: bool,
230 pub depth: u32,
231}
232
233pub struct Engine {
235 dialogues: HashMap<u32, Dialogue>,
236 next_tid: u32,
237 pub max_dialogues: usize,
239 pub last_route: Option<Route>,
241}
242
243impl Default for Engine {
244 fn default() -> Self {
245 Self::new()
246 }
247}
248
249impl Engine {
250 pub fn new() -> Self {
251 let seed = crate::utils::clock::process_id().wrapping_mul(2654435761) & 0x00ff_ffff;
253 Engine {
254 dialogues: HashMap::new(),
255 next_tid: 0x0100_0000 | seed,
256 max_dialogues: MAX_OPEN_DIALOGUES,
257 last_route: None,
258 }
259 }
260
261 pub fn open_dialogues(&self) -> usize {
262 self.dialogues.len()
263 }
264
265 pub fn dialogue(&self, id: u32) -> Option<&Dialogue> {
266 self.dialogues.get(&id)
267 }
268
269 fn allocate(&mut self) -> u32 {
270 loop {
271 let id = self.next_tid;
272 self.next_tid = self.next_tid.wrapping_add(1).max(1);
273 if !self.dialogues.contains_key(&id) {
274 return id;
275 }
276 }
277 }
278
279 fn find_by_local_tid(&mut self, tid: &[u8]) -> Option<&mut Dialogue> {
280 if tid.len() != 4 {
281 return None;
282 }
283 let id = u32::from_be_bytes([tid[0], tid[1], tid[2], tid[3]]);
284 self.dialogues.get_mut(&id)
285 }
286
287 pub fn on_data(
289 &mut self,
290 pd: &ProtocolData,
291 routing_context: Option<u32>,
292 network_appearance: Option<u32>,
293 now: Instant,
294 ) -> Inbound {
295 if pd.si != SI_SCCP {
296 return Inbound::NotSccp;
297 }
298 let ud = match sccp::decode(&pd.payload) {
299 Ok(u) => u,
300 Err(e) => return Inbound::Undecoded(e),
301 };
302 let route = Route::reply_to(pd, &ud, routing_context, network_appearance);
303 self.last_route = Some(route.clone());
304
305 if ud.is_service_message() {
306 return self.on_service_message(&ud);
307 }
308 if ud.called.ssn == Some(sccp::SSN_SCMG) {
309 return on_scmg(&ud, &route);
310 }
311 if let Some(seg) = ud.segmentation {
312 if !seg.is_whole_message() {
313 let mut replies = Vec::new();
314 if ud.return_on_error {
315 if let Ok(b) = sccp::encode_service(&ud, sccp::CAUSE_SEGMENTATION_NOT_SUPPORTED)
316 {
317 replies.push(wrap(&route, b, "XUDTS segmentation not supported".into()));
318 }
319 }
320 return Inbound::Handled {
321 replies,
322 note: format!(
323 "decision=refused_segmented: XUDT segment (first={}, remaining={}) — \
324 NetGet does not reassemble",
325 seg.first, seg.remaining
326 ),
327 };
328 }
329 }
330 let message = match TcMessage::decode(&ud.data) {
331 Ok(m) => m,
332 Err((cause, e)) => return refuse_undecodable(&ud.data, &route, cause, e),
333 };
334 match message.kind {
335 TcKind::Begin => self.on_begin(message, route, now),
336 TcKind::Continue => self.on_continue(message, route, now),
337 TcKind::End | TcKind::Abort => self.on_end_or_abort(message, now),
338 TcKind::Unidirectional => Inbound::Handled {
339 replies: vec![],
340 note: "TC-UNI ignored: NetGet answers dialogues, and a unidirectional message \
341 cannot be answered"
342 .into(),
343 },
344 }
345 }
346
347 fn on_service_message(&mut self, ud: &Unitdata) -> Inbound {
348 let cause = ud.return_cause.unwrap_or(0);
349 let ours = TcMessage::decode(&ud.data)
351 .ok()
352 .and_then(|m| m.otid.clone().or(m.dtid.clone()));
353 let dialogue = ours.and_then(|tid| {
354 if tid.len() == 4 {
355 let id = u32::from_be_bytes([tid[0], tid[1], tid[2], tid[3]]);
356 self.dialogues.remove(&id)
357 } else {
358 None
359 }
360 });
361 match dialogue {
362 Some(d) => Inbound::Event(Ss7Event {
363 family: d.family,
364 dialogue_id: d.id,
365 kind: TcKind::Abort,
366 data: json!({
367 "dialogue_id": d.id,
368 "tcap": "sccp_returned",
369 "sccp_return_cause": cause,
370 "note": "SCCP returned NetGet's message as undeliverable; the dialogue is closed",
371 "route": d.route.to_json(),
372 }),
373 replies: vec![],
374 ended: true,
375 depth: d.depth,
376 }),
377 None => Inbound::Handled {
378 replies: vec![],
379 note: format!(
380 "{} cause 0x{cause:02x} for no open dialogue; ignored",
381 ud.type_name()
382 ),
383 },
384 }
385 }
386
387 fn on_begin(&mut self, m: TcMessage, route: Route, now: Instant) -> Inbound {
388 let otid = m.otid.clone().unwrap_or_default();
389 if self.dialogues.len() >= self.max_dialogues {
390 return match send_tcap(
391 &route,
392 &TcMessage::p_abort(&otid, tcap::P_ABORT_RESOURCE_LIMITATION),
393 "TC-P-ABORT resourceLimitation".into(),
394 ) {
395 Ok(o) => Inbound::Handled {
396 replies: vec![o],
397 note: format!(
398 "decision=refused_dialogue_cap: {} dialogues already open",
399 self.dialogues.len()
400 ),
401 },
402 Err(e) => Inbound::Handled {
403 replies: vec![],
404 note: e,
405 },
406 };
407 }
408 let acn = m
409 .dialogue
410 .as_ref()
411 .and_then(|d| d.acn().map(|a| a.to_vec()));
412 let family = match &acn {
413 Some(a) if map::context_of(a).is_some() => Some(Family::Map),
414 Some(a) if cap::context_of(a).is_some() => Some(Family::Cap),
415 Some(_) => None,
416 None => Some(Family::Map),
419 };
420 let Some(family) = family else {
421 let reply = TcMessage {
424 kind: TcKind::Abort,
425 otid: None,
426 dtid: Some(otid),
427 dialogue: acn.clone().map(|a| DialoguePdu::Response {
428 acn: a,
429 result: 1,
430 diagnostic_provider: false,
431 diagnostic: tcap::DIAG_USER_ACN_NOT_SUPPORTED,
432 }),
433 components: vec![],
434 p_abort_cause: None,
435 };
436 let name = acn
437 .as_deref()
438 .map(ber::oid_string)
439 .unwrap_or_else(|| "none".into());
440 return match send_tcap(&route, &reply, "TC-U-ABORT ACN not supported".into()) {
441 Ok(o) => Inbound::Handled {
442 replies: vec![o],
443 note: format!(
444 "decision=refused_application_context: {name} is neither a MAP context \
445 nor a CAP gsmSSF-to-gsmSCF context"
446 ),
447 },
448 Err(e) => Inbound::Handled {
449 replies: vec![],
450 note: e,
451 },
452 };
453 };
454
455 let id = self.allocate();
456 let mut d = Dialogue {
457 id,
458 remote_tid: Some(otid),
459 acn: acn.clone(),
460 family,
461 aare_pending: matches!(m.dialogue, Some(DialoguePdu::Request { .. })),
462 route,
463 last_activity: now,
464 peer_invokes: vec![],
465 own_invokes: vec![],
466 next_invoke_id: 1,
467 awaiting_answer: false,
468 depth: 0,
469 };
470 let (components, rejects) = describe_components(&mut d, &m.components);
471 if !rejects.is_empty() {
472 let reply = TcMessage {
475 kind: TcKind::End,
476 otid: None,
477 dtid: d.remote_tid.clone(),
478 dialogue: d.acn.clone().filter(|_| d.aare_pending).map(accept),
479 components: rejects,
480 p_abort_cause: None,
481 };
482 return match send_tcap(&d.route, &reply, "TC-END with Reject".into()) {
483 Ok(o) => Inbound::Handled {
484 replies: vec![o],
485 note: "decision=rejected_operation: an invoke NetGet cannot decode was \
486 answered with Reject"
487 .into(),
488 },
489 Err(e) => Inbound::Handled {
490 replies: vec![],
491 note: e,
492 },
493 };
494 }
495 if let Some(r) = self.rust_answers(&mut d) {
497 return r;
498 }
499 d.awaiting_answer = needs_answer(&d, &components);
500 let data = event_data(&d, &m, components);
501 let ev = Ss7Event {
502 family,
503 dialogue_id: id,
504 kind: TcKind::Begin,
505 data,
506 replies: vec![],
507 ended: false,
508 depth: 0,
509 };
510 self.dialogues.insert(id, d);
511 Inbound::Event(ev)
512 }
513
514 fn rust_answers(&mut self, d: &mut Dialogue) -> Option<Inbound> {
515 if d.family == Family::Cap
516 && !d.peer_invokes.is_empty()
517 && d.peer_invokes
518 .iter()
519 .all(|(_, op)| *op == cap::OP_ACTIVITY_TEST)
520 {
521 let comps = d
522 .peer_invokes
523 .drain(..)
524 .map(|(id, _)| Component::ReturnResult {
525 last: true,
526 invoke_id: id,
527 opcode: None,
528 parameter: None,
529 })
530 .collect();
531 let reply = TcMessage {
532 kind: TcKind::End,
533 otid: None,
534 dtid: d.remote_tid.clone(),
535 dialogue: d.acn.clone().filter(|_| d.aare_pending).map(accept),
536 components: comps,
537 p_abort_cause: None,
538 };
539 self.dialogues.remove(&d.id);
540 return Some(
541 match send_tcap(&d.route, &reply, "activityTest result".into()) {
542 Ok(o) => Inbound::Handled {
543 replies: vec![o],
544 note: "CAP activityTest answered in Rust".into(),
545 },
546 Err(e) => Inbound::Handled {
547 replies: vec![],
548 note: e,
549 },
550 },
551 );
552 }
553 None
554 }
555
556 fn on_continue(&mut self, m: TcMessage, route: Route, now: Instant) -> Inbound {
557 let dtid = m.dtid.clone().unwrap_or_default();
558 let otid = m.otid.clone().unwrap_or_default();
559 let Some(d) = self.find_by_local_tid(&dtid) else {
560 return p_abort_unknown(&route, &otid, &dtid);
561 };
562 match &d.remote_tid {
563 None => d.remote_tid = Some(otid.clone()),
565 Some(known) if *known != otid => {
566 let reply = TcMessage::p_abort(&otid, tcap::P_ABORT_INCORRECT_TRANSACTION_PORTION);
567 return match send_tcap(
568 &route,
569 &reply,
570 "TC-P-ABORT incorrectTransactionPortion".into(),
571 ) {
572 Ok(o) => Inbound::Handled {
573 replies: vec![o],
574 note: format!(
575 "decision=refused_transaction: TC-CONTINUE for dialogue {} from \
576 transaction {} (expected {})",
577 d.id,
578 ber::hex(&otid),
579 ber::hex(known)
580 ),
581 },
582 Err(e) => Inbound::Handled {
583 replies: vec![],
584 note: e,
585 },
586 };
587 }
588 Some(_) => {}
589 }
590 d.last_activity = now;
591 d.route.remote = route.remote.clone();
594 d.route.dpc = route.dpc;
595 d.route.sls = route.sls;
596 let mut d = d.clone();
597 if let Some(DialoguePdu::Response { result, .. }) = &m.dialogue {
598 if *result != 0 {
599 self.dialogues.remove(&d.id);
600 return Inbound::Event(Ss7Event {
601 family: d.family,
602 dialogue_id: d.id,
603 kind: TcKind::Continue,
604 data: event_data(&d, &m, vec![]),
605 replies: vec![],
606 ended: true,
607 depth: d.depth,
608 });
609 }
610 }
611 let (components, rejects) = describe_components(&mut d, &m.components);
612 if !rejects.is_empty() {
613 let reply = TcMessage {
614 kind: TcKind::Continue,
615 otid: Some(d.tid()),
616 dtid: d.remote_tid.clone(),
617 dialogue: None,
618 components: rejects,
619 p_abort_cause: None,
620 };
621 if let Ok(o) = send_tcap(&d.route, &reply, "TC-CONTINUE with Reject".into()) {
622 self.dialogues.insert(d.id, d.clone());
623 return Inbound::Handled {
624 replies: vec![o],
625 note: "decision=rejected_operation: an invoke NetGet cannot decode was \
626 answered with Reject"
627 .into(),
628 };
629 }
630 }
631 if let Some(r) = self.rust_answers(&mut d) {
632 return r;
633 }
634 d.awaiting_answer = needs_answer(&d, &components);
635 let data = event_data(&d, &m, components);
636 let ev = Ss7Event {
637 family: d.family,
638 dialogue_id: d.id,
639 kind: TcKind::Continue,
640 data,
641 replies: vec![],
642 ended: false,
643 depth: d.depth,
644 };
645 self.dialogues.insert(d.id, d);
646 Inbound::Event(ev)
647 }
648
649 fn on_end_or_abort(&mut self, m: TcMessage, _now: Instant) -> Inbound {
650 let dtid = m.dtid.clone().unwrap_or_default();
651 let id = match self.find_by_local_tid(&dtid) {
652 Some(d) => d.id,
653 None => {
654 return Inbound::Handled {
656 replies: vec![],
657 note: format!(
658 "decision=discarded_unknown_transaction: TC-{} for transaction {} which \
659 no dialogue here owns",
660 m.kind.name().to_uppercase(),
661 ber::hex(&dtid)
662 ),
663 };
664 }
665 };
666 let mut d = self.dialogues.remove(&id).expect("found above");
667 let (components, _) = describe_components(&mut d, &m.components);
668 Inbound::Event(Ss7Event {
669 family: d.family,
670 dialogue_id: d.id,
671 kind: m.kind,
672 data: event_data(&d, &m, components),
673 replies: vec![],
674 ended: true,
675 depth: d.depth,
676 })
677 }
678
679 pub fn awaiting_answer(&self, id: u32) -> bool {
681 self.dialogues
682 .get(&id)
683 .map(|d| d.awaiting_answer)
684 .unwrap_or(false)
685 }
686
687 pub fn close_unanswered(&mut self, id: u32, fail_closed: bool) -> Option<Outgoing> {
694 let d = self.dialogues.remove(&id)?;
695 let reply = if fail_closed && d.family == Family::Map && !d.peer_invokes.is_empty() {
696 TcMessage {
697 kind: TcKind::End,
698 otid: None,
699 dtid: d.remote_tid.clone(),
700 dialogue: d.acn.clone().filter(|_| d.aare_pending).map(accept),
701 components: d
702 .peer_invokes
703 .iter()
704 .map(|(inv, _)| Component::ReturnError {
705 invoke_id: *inv,
706 error: Code::Local(map::ERROR_SYSTEM_FAILURE),
707 parameter: None,
708 })
709 .collect(),
710 p_abort_cause: None,
711 }
712 } else {
713 user_abort(&d, tcap::DIAG_USER_NO_REASON_GIVEN)
714 };
715 let label = if reply.kind == TcKind::End {
716 "TC-END ReturnError systemFailure"
717 } else {
718 "TC-U-ABORT"
719 };
720 send_tcap(&d.route, &reply, label.into()).ok()
721 }
722
723 pub fn expire(&mut self, now: Instant) -> Vec<(Dialogue, Option<Outgoing>)> {
725 let stale: Vec<u32> = self
726 .dialogues
727 .values()
728 .filter(|d| now.saturating_duration_since(d.last_activity) >= DIALOGUE_IDLE_TIMEOUT)
729 .map(|d| d.id)
730 .collect();
731 stale
732 .into_iter()
733 .filter_map(|id| self.dialogues.remove(&id))
734 .map(|d| {
735 let out = d.remote_tid.as_ref().and_then(|_| {
736 send_tcap(
737 &d.route,
738 &user_abort(&d, tcap::DIAG_USER_NO_REASON_GIVEN),
739 "TC-U-ABORT idle".into(),
740 )
741 .ok()
742 });
743 (d, out)
744 })
745 .collect()
746 }
747
748 pub fn next_expiry(&self) -> Option<Instant> {
750 self.dialogues
751 .values()
752 .map(|d| d.last_activity + DIALOGUE_IDLE_TIMEOUT)
753 .min()
754 }
755
756 pub fn apply(
759 &mut self,
760 intent: &Value,
761 default_route: Option<&Route>,
762 depth: u32,
763 now: Instant,
764 ) -> Result<(u32, Outgoing), String> {
765 let kind = intent["type"].as_str().unwrap_or_default();
766 let dialogue_id = match intent.get("dialogue_id") {
767 None | Some(Value::Null) => None,
768 Some(v) => Some(
769 v.as_u64()
770 .filter(|n| *n <= u32::MAX as u64)
771 .ok_or("dialogue_id must be the integer from the event")?
772 as u32,
773 ),
774 };
775 match kind {
776 "send_map_request" | "send_cap_operations" if dialogue_id.is_none() => {
777 self.originate(kind, intent, default_route, depth, now)
778 }
779 _ => {
780 let id = dialogue_id.ok_or_else(|| format!("{kind} needs a dialogue_id"))?;
781 let mut d = self.dialogues.remove(&id).ok_or_else(|| {
782 format!("dialogue {id} is not open (it ended or never existed)")
783 })?;
784 let result = continue_dialogue(&mut d, kind, intent, depth, now);
785 match result {
786 Ok((out, keep)) => {
787 if keep {
788 self.dialogues.insert(id, d);
789 }
790 Ok((id, out))
791 }
792 Err(e) => {
793 self.dialogues.insert(id, d);
794 Err(e)
795 }
796 }
797 }
798 }
799 }
800
801 fn originate(
802 &mut self,
803 kind: &str,
804 intent: &Value,
805 default_route: Option<&Route>,
806 depth: u32,
807 now: Instant,
808 ) -> Result<(u32, Outgoing), String> {
809 if self.dialogues.len() >= self.max_dialogues {
810 return Err(format!(
811 "{} dialogues are already open; NetGet opens no more",
812 self.dialogues.len()
813 ));
814 }
815 let mut route = default_route
816 .cloned()
817 .ok_or("no route: this association has no peer address to open a dialogue towards")?;
818 if let Some(called) = intent.get("called_address").filter(|v| !v.is_null()) {
819 route.remote = SccpAddress::from_json(called, "called_address")?;
820 }
821 let mut user_information = Vec::new();
822 let (family, acn, components) = if kind == "send_map_request" {
823 let op = intent["operation"]
824 .as_str()
825 .and_then(map::operation_by_name)
826 .ok_or_else(|| format!("operation must be one of {}", map::operation_names()))?;
827 let version = intent
828 .get("context_version")
829 .and_then(Value::as_u64)
830 .unwrap_or(op.version);
831 let argument = intent.get("argument").unwrap_or(&Value::Null);
832 let arg = map::encode_argument(op.code, argument)?;
833 let reference = |field: &str| -> Result<Option<gsm::Address>, String> {
837 intent
838 .get(field)
839 .filter(|v| !v.is_null())
840 .map(|v| gsm::Address::from_json(v, field))
841 .transpose()
842 };
843 let mut destination = reference("destination_reference")?;
844 let mut origination = reference("origination_reference")?;
845 if op.context == 19 {
846 if destination.is_none() {
847 destination = argument
848 .get("msisdn")
849 .filter(|v| !v.is_null())
850 .map(|v| gsm::Address::from_json(v, "msisdn"))
851 .transpose()?;
852 }
853 if origination.is_none() {
854 origination = route
855 .local
856 .global_title
857 .as_ref()
858 .map(|g| gsm::Address::international(&g.digits));
859 }
860 }
861 if destination.is_some() || origination.is_some() {
862 user_information.push(tcap::map_open_user_information(
863 destination.as_ref(),
864 origination.as_ref(),
865 )?);
866 }
867 (
868 Family::Map,
869 map::acn(op.context, version),
870 vec![(op.code, Some(arg))],
871 )
872 } else {
873 let phase = intent.get("cap_phase").and_then(Value::as_u64).unwrap_or(2);
874 let acn = cap::context_for_phase(phase)
875 .ok_or("cap_phase must be 2, 3 or 4")?
876 .to_vec();
877 (Family::Cap, acn, cap_invokes(intent)?)
878 };
879 let id = self.allocate();
880 let mut d = Dialogue {
881 id,
882 remote_tid: None,
883 acn: Some(acn.clone()),
884 family,
885 aare_pending: false,
886 route,
887 last_activity: now,
888 peer_invokes: vec![],
889 own_invokes: vec![],
890 next_invoke_id: 1,
891 awaiting_answer: false,
892 depth,
893 };
894 let comps = invokes(&mut d, components)?;
895 let m = TcMessage {
896 kind: TcKind::Begin,
897 otid: Some(d.tid()),
898 dtid: None,
899 dialogue: Some(DialoguePdu::Request {
900 acn,
901 user_information,
902 }),
903 components: comps,
904 p_abort_cause: None,
905 };
906 let out = send_tcap(
907 &d.route,
908 &m,
909 format!("TC-BEGIN {} dialogue {id}", family.name()),
910 )?;
911 self.dialogues.insert(id, d);
912 Ok((id, out))
913 }
914}
915
916fn accept(acn: Vec<u64>) -> DialoguePdu {
917 DialoguePdu::Response {
918 acn,
919 result: 0,
920 diagnostic_provider: false,
921 diagnostic: tcap::DIAG_USER_NULL,
922 }
923}
924
925fn user_abort(d: &Dialogue, diagnostic: u8) -> TcMessage {
926 let dialogue = match (&d.acn, d.aare_pending) {
929 (Some(acn), true) => Some(DialoguePdu::Response {
930 acn: acn.clone(),
931 result: 1,
932 diagnostic_provider: false,
933 diagnostic,
934 }),
935 (Some(_), false) => Some(DialoguePdu::Abort {
936 source: 0,
937 user_information: vec![],
938 }),
939 (None, _) => None,
940 };
941 TcMessage {
942 kind: TcKind::Abort,
943 otid: None,
944 dtid: d.remote_tid.clone(),
945 dialogue,
946 components: vec![],
947 p_abort_cause: None,
950 }
951}
952
953fn p_abort_unknown(route: &Route, otid: &[u8], dtid: &[u8]) -> Inbound {
954 let reply = TcMessage::p_abort(otid, tcap::P_ABORT_UNRECOGNIZED_TRANSACTION_ID);
955 match send_tcap(route, &reply, "TC-P-ABORT unrecognizedTransactionID".into()) {
956 Ok(o) => Inbound::Handled {
957 replies: vec![o],
958 note: format!(
959 "decision=refused_transaction: TC-CONTINUE for transaction {} which no dialogue \
960 here owns",
961 ber::hex(dtid)
962 ),
963 },
964 Err(e) => Inbound::Handled {
965 replies: vec![],
966 note: e,
967 },
968 }
969}
970
971fn refuse_undecodable(data: &[u8], route: &Route, cause: u8, e: ber::BerError) -> Inbound {
972 let otid = ber::shallow_children(data).and_then(|kids| {
974 kids.into_iter()
975 .find(|(t, _)| t.same(ber::CLASS_APPLICATION, 8))
976 .map(|(_, v)| v.to_vec())
977 .filter(|v| (1..=4).contains(&v.len()))
978 });
979 let note = format!(
980 "decision=refused_malformed: TCAP did not decode ({e}); P-Abort cause {}",
981 tcap::p_abort_name(cause)
982 );
983 match otid {
984 Some(otid) => match send_tcap(
985 route,
986 &TcMessage::p_abort(&otid, cause),
987 format!("TC-P-ABORT {}", tcap::p_abort_name(cause)),
988 ) {
989 Ok(o) => Inbound::Handled {
990 replies: vec![o],
991 note,
992 },
993 Err(e) => Inbound::Handled {
994 replies: vec![],
995 note: e,
996 },
997 },
998 None => Inbound::Handled {
999 replies: vec![],
1000 note: format!("{note}; no transaction id to answer, discarded"),
1001 },
1002 }
1003}
1004
1005fn on_scmg(ud: &Unitdata, route: &Route) -> Inbound {
1006 let m = match Scmg::decode(&ud.data) {
1007 Ok(m) => m,
1008 Err(e) => {
1009 return Inbound::Handled {
1010 replies: vec![],
1011 note: format!("SCMG message discarded: {e}"),
1012 }
1013 }
1014 };
1015 if m.format != sccp::SCMG_SST {
1016 return Inbound::Handled {
1017 replies: vec![],
1018 note: format!(
1019 "SCMG {} for SSN {} at PC {} noted",
1020 m.name(),
1021 m.affected_ssn,
1022 m.affected_pc
1023 ),
1024 };
1025 }
1026 let ssa = Scmg {
1028 format: sccp::SCMG_SSA,
1029 ..m
1030 };
1031 let mut scmg_route = route.clone();
1032 scmg_route.message_type = sccp::MSG_UDT;
1033 scmg_route.protocol_class = 0;
1034 match sccp::encode_udt(0, false, &ud.calling, &ud.called, &ssa.encode()) {
1035 Ok(b) => Inbound::Handled {
1036 replies: vec![wrap(&scmg_route, b, "SCMG SSA".into())],
1037 note: format!("SCMG SST for SSN {} answered SSA in Rust", m.affected_ssn),
1038 },
1039 Err(e) => Inbound::Handled {
1040 replies: vec![],
1041 note: e,
1042 },
1043 }
1044}
1045
1046fn describe_components(d: &mut Dialogue, comps: &[Component]) -> (Vec<Value>, Vec<Component>) {
1049 let mut out = Vec::new();
1050 let mut rejects = Vec::new();
1051 for c in comps {
1052 match c {
1053 Component::Invoke {
1054 invoke_id,
1055 linked_id,
1056 opcode,
1057 parameter,
1058 } => {
1059 let code = opcode.local();
1060 let decoded = match (d.family, code) {
1061 (Family::Map, Some(code)) => match map::operation_by_code(code) {
1062 Some(op) => map::decode_argument(code, parameter.as_ref())
1063 .map(|a| (op.name.to_string(), a)),
1064 None => Err(format!("MAP operation {code} is not implemented")),
1065 },
1066 (Family::Cap, Some(code)) => match cap::operation_name(code) {
1067 Some(name) => cap::decode_argument(code, parameter.as_ref())
1068 .map(|a| (name.to_string(), a)),
1069 None => Err(format!("CAP operation {code} is not implemented")),
1070 },
1071 (_, None) => Err("global operation codes are not implemented".into()),
1072 };
1073 match decoded {
1074 Ok((name, argument)) => {
1075 d.peer_invokes.push((*invoke_id, code.unwrap_or(-1)));
1076 out.push(json!({
1077 "component": "invoke",
1078 "invoke_id": invoke_id,
1079 "linked_id": linked_id,
1080 "operation": name,
1081 "opcode": code,
1082 "argument": argument,
1083 }));
1084 }
1085 Err(e) => {
1086 let unknown = e.contains("not implemented");
1087 rejects.push(Component::Reject {
1088 invoke_id: Some(*invoke_id),
1089 problem_type: tcap::PROBLEM_INVOKE,
1090 problem: if unknown {
1091 tcap::INVOKE_UNRECOGNIZED_OPERATION
1092 } else {
1093 tcap::INVOKE_MISTYPED_PARAMETER
1094 },
1095 });
1096 out.push(json!({
1097 "component": "invoke",
1098 "invoke_id": invoke_id,
1099 "opcode": code,
1100 "rejected": e,
1101 }));
1102 }
1103 }
1104 }
1105 Component::ReturnResult {
1106 last,
1107 invoke_id,
1108 opcode,
1109 parameter,
1110 } => {
1111 let own = d.own_invokes.iter().position(|(i, _)| i == invoke_id);
1112 let code = own
1113 .map(|p| d.own_invokes[p].1)
1114 .or_else(|| opcode.as_ref().and_then(Code::local));
1115 if *last {
1116 if let Some(p) = own {
1117 d.own_invokes.remove(p);
1118 }
1119 }
1120 let result = match (d.family, code) {
1121 (Family::Map, Some(code)) => map::decode_result(code, parameter.as_ref())
1122 .unwrap_or_else(|e| json!({ "error": e })),
1123 _ => json!({}),
1124 };
1125 out.push(json!({
1126 "component": c.kind_name(),
1127 "invoke_id": invoke_id,
1128 "matched_own_invoke": own.is_some(),
1129 "operation": code.and_then(|c| op_name(d.family, c)),
1130 "result": result,
1131 }));
1132 }
1133 Component::ReturnError {
1134 invoke_id,
1135 error,
1136 parameter,
1137 } => {
1138 let own = d.own_invokes.iter().position(|(i, _)| i == invoke_id);
1139 let op = own.map(|p| d.own_invokes.remove(p).1);
1140 let code = error.local().unwrap_or(-1);
1141 out.push(json!({
1142 "component": "return_error",
1143 "invoke_id": invoke_id,
1144 "matched_own_invoke": op.is_some(),
1145 "operation": op.and_then(|c| op_name(d.family, c)),
1146 "error": match d.family {
1147 Family::Map => map::error_name(code),
1148 Family::Cap => cap::error_name(code),
1149 },
1150 "error_code": code,
1151 "parameter": match d.family {
1152 Family::Map => map::decode_error_parameter(code, parameter.as_ref()),
1153 Family::Cap => Value::Null,
1154 },
1155 }));
1156 }
1157 Component::Reject {
1158 invoke_id,
1159 problem_type,
1160 problem,
1161 } => {
1162 if let Some(i) = invoke_id {
1163 d.own_invokes.retain(|(x, _)| x != i);
1164 }
1165 out.push(json!({
1166 "component": "reject",
1167 "invoke_id": invoke_id,
1168 "problem": tcap::reject_problem_name(*problem_type, *problem),
1169 }));
1170 }
1171 }
1172 }
1173 (out, rejects)
1174}
1175
1176fn op_name(family: Family, code: i64) -> Option<String> {
1177 match family {
1178 Family::Map => map::operation_by_code(code).map(|o| o.name.to_string()),
1179 Family::Cap => cap::operation_name(code).map(str::to_string),
1180 }
1181}
1182
1183fn needs_answer(d: &Dialogue, components: &[Value]) -> bool {
1184 match d.family {
1185 Family::Map => !d.peer_invokes.is_empty(),
1186 Family::Cap => components.iter().any(|c| {
1187 c["component"] == "invoke"
1188 && (c["opcode"] == cap::OP_INITIAL_DP
1189 || (c["opcode"] == cap::OP_EVENT_REPORT_BCSM
1190 && c["argument"]["message_type"] == "request"))
1191 }),
1192 }
1193}
1194
1195fn event_data(d: &Dialogue, m: &TcMessage, components: Vec<Value>) -> Value {
1196 let context = d.acn.as_ref().map(|a| {
1197 let (name, version) = map::context_of(a)
1198 .or_else(|| cap::context_of(a))
1199 .unwrap_or(("unknown", 0));
1200 json!({"name": name, "version": version, "oid": ber::oid_string(a)})
1201 });
1202 let mut v = json!({
1203 "dialogue_id": d.id,
1204 "tcap": m.kind.name(),
1205 "family": d.family.name(),
1206 "application_context": context,
1207 "dialogue_portion": m.dialogue.as_ref().map(DialoguePdu::to_json),
1208 "components": components,
1209 "awaiting_answer_to": d.peer_invokes.iter().map(|(i, _)| *i).collect::<Vec<_>>(),
1210 "route": d.route.to_json(),
1211 "peer_transaction_id": d.remote_tid.as_deref().map(ber::hex),
1212 });
1213 if m.kind == TcKind::Abort {
1214 v["abort"] = json!({
1215 "p_abort_cause": m.p_abort_cause.map(tcap::p_abort_name),
1216 "user_abort": m.dialogue.as_ref().map(DialoguePdu::to_json),
1217 });
1218 }
1219 v
1220}
1221
1222fn invokes(d: &mut Dialogue, list: Vec<(i64, Option<Vec<u8>>)>) -> Result<Vec<Component>, String> {
1224 let mut out = Vec::new();
1225 for (code, arg) in list {
1226 let id = d.take_invoke_id();
1227 let parameter = arg
1228 .map(|b| ber::decode(&b).map_err(|e| e.to_string()))
1229 .transpose()?;
1230 d.own_invokes.push((id, code));
1231 out.push(Component::Invoke {
1232 invoke_id: id,
1233 linked_id: None,
1234 opcode: Code::Local(code),
1235 parameter,
1236 });
1237 }
1238 Ok(out)
1239}
1240
1241type Invokes = Vec<(i64, Option<Vec<u8>>)>;
1243
1244fn cap_invokes(intent: &Value) -> Result<Invokes, String> {
1245 let ops = intent
1246 .get("operations")
1247 .and_then(Value::as_array)
1248 .ok_or("operations is required (a list)")?;
1249 if ops.is_empty() || ops.len() > tcap::MAX_COMPONENTS {
1250 return Err(format!(
1251 "operations must hold 1..{} entries",
1252 tcap::MAX_COMPONENTS
1253 ));
1254 }
1255 ops.iter()
1256 .enumerate()
1257 .map(|(i, op)| {
1258 let name = op["operation"]
1259 .as_str()
1260 .ok_or_else(|| format!("operations[{i}].operation is required"))?;
1261 let code = cap::operation_code(name).ok_or_else(|| {
1262 format!(
1263 "operations[{i}].operation {name:?} is not one of {}",
1264 cap::OPERATIONS
1265 .iter()
1266 .map(|(n, _)| *n)
1267 .collect::<Vec<_>>()
1268 .join(", ")
1269 )
1270 })?;
1271 Ok((code, cap::encode_argument(code, op)?))
1272 })
1273 .collect()
1274}
1275
1276fn continue_dialogue(
1279 d: &mut Dialogue,
1280 kind: &str,
1281 intent: &Value,
1282 depth: u32,
1283 now: Instant,
1284) -> Result<(Outgoing, bool), String> {
1285 let pick_invoke = |d: &Dialogue| -> Result<(i64, i64), String> {
1286 match intent.get("invoke_id").and_then(Value::as_i64) {
1287 Some(i) => d
1288 .peer_invokes
1289 .iter()
1290 .find(|(x, _)| *x == i)
1291 .copied()
1292 .ok_or_else(|| {
1293 format!("invoke {i} in dialogue {} is not awaiting an answer", d.id)
1294 }),
1295 None => d
1296 .peer_invokes
1297 .first()
1298 .copied()
1299 .ok_or_else(|| format!("dialogue {} has no invoke awaiting an answer", d.id)),
1300 }
1301 };
1302 let end_default;
1303 let mut components = Vec::new();
1304 let mut message_kind = None;
1305 match kind {
1306 "send_map_result" => {
1307 if d.family != Family::Map {
1308 return Err(format!("dialogue {} is CAP, not MAP", d.id));
1309 }
1310 let (inv, code) = pick_invoke(d)?;
1311 let result = map::encode_result(code, intent.get("result").unwrap_or(&json!({})))?;
1312 let parameter = result
1313 .map(|b| ber::decode(&b).map_err(|e| e.to_string()))
1314 .transpose()?;
1315 d.peer_invokes.retain(|(x, _)| *x != inv);
1316 components.push(Component::ReturnResult {
1317 last: true,
1318 invoke_id: inv,
1319 opcode: parameter.as_ref().map(|_| Code::Local(code)),
1320 parameter,
1321 });
1322 end_default = d.peer_invokes.is_empty();
1323 }
1324 "send_map_error" => {
1325 let (inv, _) = pick_invoke(d)?;
1326 let code = match d.family {
1327 Family::Map => map::error_code(intent.get("error").unwrap_or(&Value::Null))?,
1328 Family::Cap => {
1329 let e = intent.get("error").unwrap_or(&Value::Null);
1330 e.as_i64()
1331 .or_else(|| {
1332 cap::CAP_ERRORS
1333 .iter()
1334 .find(|(n, _)| Some(*n) == e.as_str())
1335 .map(|(_, c)| *c)
1336 })
1337 .ok_or("error must be a CAP error name or code")?
1338 }
1339 };
1340 let parameter = match d.family {
1341 Family::Map => map::encode_error_parameter(code, intent)?
1342 .map(|b| ber::decode(&b).map_err(|e| e.to_string()))
1343 .transpose()?,
1344 Family::Cap => None,
1345 };
1346 d.peer_invokes.retain(|(x, _)| *x != inv);
1347 components.push(Component::ReturnError {
1348 invoke_id: inv,
1349 error: Code::Local(code),
1350 parameter,
1351 });
1352 end_default = d.peer_invokes.is_empty();
1353 }
1354 "send_map_request" => {
1355 if d.family != Family::Map {
1356 return Err(format!("dialogue {} is CAP, not MAP", d.id));
1357 }
1358 let op = intent["operation"]
1359 .as_str()
1360 .and_then(map::operation_by_name)
1361 .ok_or_else(|| format!("operation must be one of {}", map::operation_names()))?;
1362 let arg =
1363 map::encode_argument(op.code, intent.get("argument").unwrap_or(&Value::Null))?;
1364 components = invokes(d, vec![(op.code, Some(arg))])?;
1365 end_default = false;
1366 }
1367 "send_cap_operations" => {
1368 if d.family != Family::Cap {
1369 return Err(format!("dialogue {} is MAP, not CAP", d.id));
1370 }
1371 let list = cap_invokes(intent)?;
1372 let arms = list
1374 .iter()
1375 .any(|(c, _)| *c == cap::OP_REQUEST_REPORT_BCSM_EVENT);
1376 components = invokes(d, list)?;
1377 d.own_invokes.clear();
1380 d.peer_invokes.clear();
1381 end_default = !arms;
1382 }
1383 "send_tcap_end" => {
1384 message_kind = Some(TcKind::End);
1385 end_default = true;
1386 }
1387 "send_tcap_abort" => {
1388 let diag = match intent.get("reason").and_then(Value::as_str) {
1389 Some("application_context_not_supported") => tcap::DIAG_USER_ACN_NOT_SUPPORTED,
1390 _ => tcap::DIAG_USER_NO_REASON_GIVEN,
1391 };
1392 let m = user_abort(d, diag);
1393 let out = send_tcap(&d.route, &m, format!("TC-U-ABORT dialogue {}", d.id))?;
1394 return Ok((out, false));
1395 }
1396 other => return Err(format!("{other} is not an SS7 dialogue action")),
1397 }
1398 let end = intent
1399 .get("end_dialogue")
1400 .and_then(Value::as_bool)
1401 .unwrap_or(end_default);
1402 let Some(remote) = d.remote_tid.clone() else {
1403 return Err(format!(
1404 "dialogue {} has not been answered by the peer yet; wait for its TC-CONTINUE",
1405 d.id
1406 ));
1407 };
1408 let kind = message_kind.unwrap_or(if end { TcKind::End } else { TcKind::Continue });
1409 let m = TcMessage {
1410 kind,
1411 otid: if kind == TcKind::Continue {
1412 Some(d.tid())
1413 } else {
1414 None
1415 },
1416 dtid: Some(remote),
1417 dialogue: d.acn.clone().filter(|_| d.aare_pending).map(accept),
1418 components,
1419 p_abort_cause: None,
1420 };
1421 let out = send_tcap(
1422 &d.route,
1423 &m,
1424 format!("TC-{} dialogue {}", kind.name().to_uppercase(), d.id),
1425 )?;
1426 d.aare_pending = false;
1427 d.awaiting_answer = false;
1428 d.last_activity = now;
1429 d.depth = depth;
1430 Ok((out, kind == TcKind::Continue))
1431}
1432
1433pub const INTENTS: &[&str] = &[
1435 "send_map_request",
1436 "send_map_result",
1437 "send_map_error",
1438 "send_cap_operations",
1439 "send_tcap_end",
1440 "send_tcap_abort",
1441];
1442
1443pub fn check_intent(intent: &Value) -> Result<(), String> {
1446 let kind = intent["type"].as_str().unwrap_or_default();
1447 let needs_dialogue = !matches!(kind, "send_map_request" | "send_cap_operations");
1448 if needs_dialogue
1449 && !intent
1450 .get("dialogue_id")
1451 .map(|v| v.is_u64())
1452 .unwrap_or(false)
1453 {
1454 return Err(format!("{kind} needs the dialogue_id from the event"));
1455 }
1456 match kind {
1457 "send_map_request" => {
1458 let op = intent["operation"]
1459 .as_str()
1460 .and_then(map::operation_by_name)
1461 .ok_or_else(|| format!("operation must be one of {}", map::operation_names()))?;
1462 map::encode_argument(op.code, intent.get("argument").unwrap_or(&Value::Null))?;
1463 }
1464 "send_map_result" => {
1465 if let Some(op) = intent
1466 .get("operation")
1467 .and_then(Value::as_str)
1468 .and_then(map::operation_by_name)
1469 {
1470 map::encode_result(op.code, intent.get("result").unwrap_or(&json!({})))?;
1471 }
1472 }
1473 "send_map_error" => {
1474 if intent.get("error").is_none() {
1475 return Err(
1476 "error is required (a MAP error name such as unknownSubscriber)".into(),
1477 );
1478 }
1479 }
1480 "send_cap_operations" => {
1481 cap_invokes(intent)?;
1482 }
1483 "send_tcap_end" | "send_tcap_abort" => {}
1484 other => return Err(format!("{other} is not an SS7 dialogue action")),
1485 }
1486 Ok(())
1487}