Skip to main content

netget/server/m3ua/
map.rs

1//! MAP (3GPP TS 29.002) operations NetGet implements: their application contexts, operation
2//! codes, and the mapping between each argument/result and the structured JSON the model reads
3//! and writes. The model never sees BER: an `anyTimeInterrogation` arrives as
4//! `{"subscriber": {"msisdn": {...}}, "requested_info": [...]}` and its answer leaves as
5//! `{"subscriber_state": "assumedIdle", "location_information": {...}}`, encoded here.
6//!
7//! Implemented (MAP v3 syntax unless noted):
8//!
9//! | Operation | Code | Context | Typical answering node |
10//! |---|---|---|---|
11//! | updateLocation | 2 | networkLocUpContext v3 | HLR |
12//! | sendRoutingInfo | 22 | locationInfoRetrievalContext v3 | HLR |
13//! | mt-forwardSM | 44 | shortMsgMTRelayContext v3 | MSC |
14//! | sendRoutingInfoForSM | 45 | shortMsgGatewayContext v3 | HLR |
15//! | mo-forwardSM | 46 | shortMsgMORelayContext v3 | SMSC |
16//! | processUnstructuredSS-Request | 59 | networkUnstructuredSsContext v2 | HLR / USSD gateway |
17//! | unstructuredSS-Request | 60 | networkUnstructuredSsContext v2 | MSC (towards the handset) |
18//! | unstructuredSS-Notify | 61 | networkUnstructuredSsContext v2 | MSC |
19//! | anyTimeInterrogation | 71 | anyTimeEnquiryContext v3 | HLR |
20//!
21//! Every argument is decoded field by field with the tags TS 29.002 gives them (the module uses
22//! IMPLICIT TAGS; CHOICEs are explicitly tagged). Extension containers and fields not listed
23//! are reported by tag in `uninterpreted` rather than dropped silently.
24
25use super::ber::{self, Tag, Tlv};
26use super::gsm::{self, Address};
27use serde_json::{json, Map, Value};
28
29pub type MapResult<T> = Result<T, String>;
30
31/// `{0 4 0 0 1 0 <ac> <version>}`.
32pub fn acn(ac: u64, version: u64) -> Vec<u64> {
33    vec![0, 4, 0, 0, 1, 0, ac, version]
34}
35
36/// MAP application contexts NetGet names.
37pub const CONTEXTS: &[(&str, u64)] = &[
38    ("networkLocUpContext", 1),
39    ("locationInfoRetrievalContext", 5),
40    ("infoRetrievalContext", 14),
41    ("subscriberDataMngtContext", 16),
42    ("networkUnstructuredSsContext", 19),
43    ("shortMsgGatewayContext", 20),
44    ("shortMsgMORelayContext", 21),
45    ("shortMsgMTRelayContext", 25),
46    ("anyTimeEnquiryContext", 29),
47];
48
49/// If `oid` is a MAP application context, its name and version.
50pub fn context_of(oid: &[u64]) -> Option<(&'static str, u64)> {
51    if oid.len() != 8 || oid[..6] != [0, 4, 0, 0, 1, 0] {
52        return None;
53    }
54    // Only the contexts NetGet names: CAMEL v2's {0 4 0 0 1 0 50 1} shares this prefix, and an
55    // unknown context must be refused rather than read as MAP.
56    CONTEXTS
57        .iter()
58        .find(|(_, c)| *c == oid[6])
59        .map(|(n, _)| (*n, oid[7]))
60}
61
62/// One implemented operation.
63pub struct Operation {
64    pub name: &'static str,
65    pub code: i64,
66    pub context: u64,
67    pub version: u64,
68    /// Whether the operation is answered by a result (class 1); the notify has none.
69    pub has_result: bool,
70}
71
72pub const OPERATIONS: &[Operation] = &[
73    Operation {
74        name: "updateLocation",
75        code: 2,
76        context: 1,
77        version: 3,
78        has_result: true,
79    },
80    Operation {
81        name: "sendRoutingInfo",
82        code: 22,
83        context: 5,
84        version: 3,
85        has_result: true,
86    },
87    Operation {
88        name: "mt-forwardSM",
89        code: 44,
90        context: 25,
91        version: 3,
92        has_result: true,
93    },
94    Operation {
95        name: "sendRoutingInfoForSM",
96        code: 45,
97        context: 20,
98        version: 3,
99        has_result: true,
100    },
101    Operation {
102        name: "mo-forwardSM",
103        code: 46,
104        context: 21,
105        version: 3,
106        has_result: true,
107    },
108    Operation {
109        name: "processUnstructuredSS-Request",
110        code: 59,
111        context: 19,
112        version: 2,
113        has_result: true,
114    },
115    Operation {
116        name: "unstructuredSS-Request",
117        code: 60,
118        context: 19,
119        version: 2,
120        has_result: true,
121    },
122    Operation {
123        name: "unstructuredSS-Notify",
124        code: 61,
125        context: 19,
126        version: 2,
127        has_result: true,
128    },
129    Operation {
130        name: "anyTimeInterrogation",
131        code: 71,
132        context: 29,
133        version: 3,
134        has_result: true,
135    },
136];
137
138pub fn operation_by_code(code: i64) -> Option<&'static Operation> {
139    OPERATIONS.iter().find(|o| o.code == code)
140}
141
142pub fn operation_by_name(name: &str) -> Option<&'static Operation> {
143    let norm = |s: &str| s.replace(['-', '_'], "").to_ascii_lowercase();
144    OPERATIONS.iter().find(|o| norm(o.name) == norm(name))
145}
146
147pub fn operation_names() -> String {
148    OPERATIONS
149        .iter()
150        .map(|o| o.name)
151        .collect::<Vec<_>>()
152        .join(", ")
153}
154
155/// MAP error codes (TS 29.002 section 17.6.6) by name.
156pub const ERRORS: &[(&str, i64)] = &[
157    ("unknownSubscriber", 1),
158    ("unknownMSC", 3),
159    ("unidentifiedSubscriber", 5),
160    ("absentSubscriberSM", 6),
161    ("unknownEquipment", 7),
162    ("roamingNotAllowed", 8),
163    ("illegalSubscriber", 9),
164    ("bearerServiceNotProvisioned", 10),
165    ("teleserviceNotProvisioned", 11),
166    ("illegalEquipment", 12),
167    ("callBarred", 13),
168    ("forwardingViolation", 14),
169    ("cug-Reject", 15),
170    ("illegalSS-Operation", 16),
171    ("ss-ErrorStatus", 17),
172    ("ss-NotAvailable", 18),
173    ("ss-SubscriptionViolation", 19),
174    ("ss-Incompatibility", 20),
175    ("facilityNotSupported", 21),
176    ("absentSubscriber", 27),
177    ("subscriberBusyForMT-SMS", 31),
178    ("sm-DeliveryFailure", 32),
179    ("messageWaitingListFull", 33),
180    ("systemFailure", 34),
181    ("dataMissing", 35),
182    ("unexpectedDataValue", 36),
183    ("noRoamingNumberAvailable", 39),
184    ("busySubscriber", 45),
185    ("noSubscriberReply", 46),
186    ("ati-NotAllowed", 49),
187    ("resourceLimitation", 51),
188    ("unknownAlphabet", 71),
189    ("ussd-Busy", 72),
190];
191
192pub const ERROR_SYSTEM_FAILURE: i64 = 34;
193
194pub fn error_name(code: i64) -> String {
195    ERRORS
196        .iter()
197        .find(|(_, c)| *c == code)
198        .map(|(n, _)| n.to_string())
199        .unwrap_or_else(|| format!("error{code}"))
200}
201
202/// An error by name (case and punctuation insensitive) or by code.
203pub fn error_code(v: &Value) -> MapResult<i64> {
204    if let Some(n) = v.as_i64() {
205        if ERRORS.iter().any(|(_, c)| *c == n) {
206            return Ok(n);
207        }
208        return Err(format!("MAP error code {n} is not one NetGet names"));
209    }
210    let s = v.as_str().ok_or("error must be a MAP error name or code")?;
211    let norm = |x: &str| x.replace(['-', '_'], "").to_ascii_lowercase();
212    ERRORS
213        .iter()
214        .find(|(n, _)| norm(n) == norm(s))
215        .map(|(_, c)| *c)
216        .ok_or_else(|| {
217            format!(
218                "{s:?} is not a MAP error; use one of {}",
219                ERRORS
220                    .iter()
221                    .map(|(n, _)| *n)
222                    .collect::<Vec<_>>()
223                    .join(", ")
224            )
225        })
226}
227
228// ---------------------------------------------------------------------------
229// Small helpers
230// ---------------------------------------------------------------------------
231
232fn addr(t: &Tlv) -> Value {
233    Address::decode(&t.value)
234        .map(|a| a.to_json())
235        .unwrap_or_else(|e| json!({ "error": e }))
236}
237
238fn imsi(t: &Tlv) -> Value {
239    json!(gsm::tbcd_decode(&t.value))
240}
241
242fn address_tlv(tag: Tag, v: &Value, field: &str, max_digits: usize) -> MapResult<Vec<u8>> {
243    let a = Address::from_json(v, field)?;
244    Ok(ber::tlv(tag, &a.encode(max_digits)?))
245}
246
247fn required<'a>(v: &'a Value, field: &str) -> MapResult<&'a Value> {
248    v.get(field)
249        .filter(|x| !x.is_null())
250        .ok_or_else(|| format!("{field} is required"))
251}
252
253fn imsi_tlv(tag: Tag, v: &Value, field: &str) -> MapResult<Vec<u8>> {
254    let s = v
255        .as_str()
256        .ok_or_else(|| format!("{field} must be a digit string"))?;
257    Ok(ber::tlv(tag, &gsm::imsi_encode(s)?))
258}
259
260fn seq_children(t: &Tlv, what: &str) -> MapResult<Vec<Tlv>> {
261    if t.tag != Tag::SEQUENCE && !(t.tag.constructed) {
262        return Err(format!("{what} is {}, expected a SEQUENCE", t.tag.label()));
263    }
264    Ok(t.children.clone())
265}
266
267/// Tags present that the mapping below did not consume.
268fn leftovers(children: &[Tlv], used: &[Tag]) -> Vec<String> {
269    children
270        .iter()
271        .filter(|c| !used.contains(&c.tag))
272        .map(|c| c.tag.label())
273        .collect()
274}
275
276fn ctx(n: u32) -> Tag {
277    Tag::context(n, false)
278}
279
280fn ctxc(n: u32) -> Tag {
281    Tag::context(n, true)
282}
283
284// ---------------------------------------------------------------------------
285// Arguments
286// ---------------------------------------------------------------------------
287
288/// Decode an Invoke's argument for `code`. `mobile_originated` matters only for forwardSM's
289/// TPDU direction.
290pub fn decode_argument(code: i64, p: Option<&Tlv>) -> MapResult<Value> {
291    let Some(p) = p else {
292        return Err("the operation's argument is missing".into());
293    };
294    let kids = seq_children(p, "argument")?;
295    let find = |tag: Tag| kids.iter().find(|c| c.tag == tag);
296    let mut out = Map::new();
297    let used: Vec<Tag> = match code {
298        45 => {
299            out.insert(
300                "msisdn".into(),
301                find(ctx(0)).map(addr).ok_or("msisdn [0] missing")?,
302            );
303            out.insert(
304                "sm_rp_pri".into(),
305                json!(find(ctx(1))
306                    .map(|b| b.as_bool())
307                    .transpose()
308                    .map_err(|e| e.0)?),
309            );
310            out.insert(
311                "service_centre_address".into(),
312                find(ctx(2))
313                    .map(addr)
314                    .ok_or("serviceCentreAddress [2] missing")?,
315            );
316            out.insert(
317                "gprs_support_indicator".into(),
318                json!(find(ctx(7)).is_some()),
319            );
320            vec![ctx(0), ctx(1), ctx(2), ctx(7)]
321        }
322        71 => {
323            let ident = find(ctxc(0)).ok_or("subscriberIdentity [0] missing")?;
324            let sub = match ident.children.first() {
325                Some(c) if c.tag == ctx(0) => json!({"imsi": imsi(c)}),
326                Some(c) if c.tag == ctx(1) => json!({"msisdn": addr(c)}),
327                _ => return Err("subscriberIdentity is neither imsi [0] nor msisdn [1]".into()),
328            };
329            out.insert("subscriber".into(), sub);
330            let mut requested = Vec::new();
331            if let Some(ri) = find(ctxc(1)) {
332                for (n, name) in [
333                    (0, "location_information"),
334                    (1, "subscriber_state"),
335                    (3, "current_location"),
336                    (5, "ms_classmark"),
337                    (6, "imei"),
338                    (7, "mnp_requested_info"),
339                ] {
340                    if ri.children.iter().any(|c| c.tag.is_context(n)) {
341                        requested.push(json!(name));
342                    }
343                }
344            }
345            out.insert("requested_info".into(), json!(requested));
346            out.insert(
347                "gsm_scf_address".into(),
348                find(ctx(3)).map(addr).unwrap_or(Value::Null),
349            );
350            vec![ctxc(0), ctxc(1), ctx(3)]
351        }
352        59..=61 => {
353            let (dcs, text) = ussd_fields(&kids)?;
354            out.insert("ussd_data_coding_scheme".into(), json!(dcs));
355            out.insert("ussd_text".into(), text);
356            out.insert(
357                "msisdn".into(),
358                find(ctx(0)).map(addr).unwrap_or(Value::Null),
359            );
360            vec![Tag::OCTET_STRING, ctx(0)]
361        }
362        44 | 46 => {
363            let da = kids.first().ok_or("sm-RP-DA missing")?;
364            let oa = kids.get(1).ok_or("sm-RP-OA missing")?;
365            let ui = kids
366                .get(2)
367                .filter(|u| u.tag == Tag::OCTET_STRING)
368                .ok_or("sm-RP-UI missing")?;
369            out.insert("sm_rp_da".into(), sm_rp_da(da));
370            out.insert("sm_rp_oa".into(), sm_rp_oa(oa));
371            out.insert(
372                "sms".into(),
373                gsm::sms_tpdu_decode(&ui.value, code == 46)
374                    .unwrap_or_else(|e| json!({ "error": e })),
375            );
376            if code == 44 {
377                out.insert(
378                    "more_messages_to_send".into(),
379                    json!(kids.iter().skip(3).any(|c| c.tag == Tag::NULL)),
380                );
381            } else if let Some(i) = kids.iter().skip(3).find(|c| c.tag == Tag::OCTET_STRING) {
382                out.insert("imsi".into(), imsi(i));
383            }
384            let mut u = vec![da.tag, oa.tag, Tag::OCTET_STRING, Tag::NULL];
385            u.dedup();
386            u
387        }
388        22 => {
389            out.insert(
390                "msisdn".into(),
391                find(ctx(0)).map(addr).ok_or("msisdn [0] missing")?,
392            );
393            out.insert(
394                "interrogation_type".into(),
395                json!(match find(ctx(3))
396                    .map(|t| t.as_i64())
397                    .transpose()
398                    .map_err(|e| e.0)?
399                {
400                    Some(0) => "basicCall",
401                    Some(1) => "forwarding",
402                    _ => "unknown",
403                }),
404            );
405            out.insert(
406                "gmsc_or_gsm_scf_address".into(),
407                find(ctx(6)).map(addr).unwrap_or(Value::Null),
408            );
409            out.insert("or_interrogation".into(), json!(find(ctx(4)).is_some()));
410            vec![ctx(0), ctx(3), ctx(6), ctx(4)]
411        }
412        2 => {
413            out.insert(
414                "imsi".into(),
415                find(Tag::OCTET_STRING).map(imsi).ok_or("imsi missing")?,
416            );
417            out.insert(
418                "msc_number".into(),
419                find(ctx(1)).map(addr).ok_or("msc-Number [1] missing")?,
420            );
421            out.insert(
422                "vlr_number".into(),
423                kids.iter()
424                    .filter(|c| c.tag == Tag::OCTET_STRING)
425                    .nth(1)
426                    .map(addr)
427                    .ok_or("vlr-Number missing")?,
428            );
429            out.insert(
430                "lmsi".into(),
431                find(ctx(10))
432                    .map(|l| json!(ber::hex(&l.value)))
433                    .unwrap_or(Value::Null),
434            );
435            vec![Tag::OCTET_STRING, ctx(1), ctx(10)]
436        }
437        other => return Err(format!("operation {other} is not implemented")),
438    };
439    out.insert("uninterpreted".into(), json!(leftovers(&kids, &used)));
440    Ok(Value::Object(out))
441}
442
443fn ussd_fields(kids: &[Tlv]) -> MapResult<(u8, Value)> {
444    let mut octets = kids.iter().filter(|c| c.tag == Tag::OCTET_STRING);
445    let dcs = octets
446        .next()
447        .and_then(|d| d.value.first().copied())
448        .ok_or("ussd-DataCodingScheme missing")?;
449    let s = octets.next().ok_or("ussd-String missing")?;
450    if s.value.len() > gsm::MAX_USSD_OCTETS {
451        return Err(format!(
452            "ussd-String of {} octets (at most {})",
453            s.value.len(),
454            gsm::MAX_USSD_OCTETS
455        ));
456    }
457    Ok((dcs, json!(gsm::ussd_decode(dcs, &s.value))))
458}
459
460fn sm_rp_da(t: &Tlv) -> Value {
461    match t.tag.number {
462        0 => json!({"imsi": imsi(t)}),
463        1 => json!({"lmsi": ber::hex(&t.value)}),
464        4 => json!({"service_centre_address": addr(t)}),
465        5 => json!({"none": true}),
466        n => json!({"unknown_choice": n}),
467    }
468}
469
470fn sm_rp_oa(t: &Tlv) -> Value {
471    match t.tag.number {
472        2 => json!({"msisdn": addr(t)}),
473        4 => json!({"service_centre_address": addr(t)}),
474        5 => json!({"none": true}),
475        n => json!({"unknown_choice": n}),
476    }
477}
478
479/// Encode an Invoke's argument from the model's fields.
480pub fn encode_argument(code: i64, v: &Value) -> MapResult<Vec<u8>> {
481    let parts: Vec<Vec<u8>> = match code {
482        45 => vec![
483            address_tlv(
484                ctx(0),
485                required(v, "msisdn")?,
486                "msisdn",
487                gsm::MAX_ISDN_ADDRESS_DIGITS,
488            )?,
489            ber::tlv(
490                ctx(1),
491                &[
492                    if v.get("sm_rp_pri").and_then(Value::as_bool).unwrap_or(true) {
493                        0xff
494                    } else {
495                        0
496                    },
497                ],
498            ),
499            address_tlv(
500                ctx(2),
501                required(v, "service_centre_address")?,
502                "service_centre_address",
503                gsm::MAX_ADDRESS_DIGITS,
504            )?,
505        ],
506        71 => {
507            let sub = required(v, "subscriber")?;
508            let ident = if let Some(i) = sub.get("imsi") {
509                imsi_tlv(ctx(0), i, "subscriber.imsi")?
510            } else if let Some(m) = sub.get("msisdn") {
511                address_tlv(ctx(1), m, "subscriber.msisdn", gsm::MAX_ISDN_ADDRESS_DIGITS)?
512            } else {
513                return Err("subscriber must be {\"msisdn\": ...} or {\"imsi\": ...}".into());
514            };
515            let wanted: Vec<String> = match v.get("requested_info") {
516                Some(Value::Array(a)) => a
517                    .iter()
518                    .filter_map(|x| x.as_str().map(str::to_string))
519                    .collect(),
520                _ => vec!["location_information".into(), "subscriber_state".into()],
521            };
522            let mut ri = Vec::new();
523            for (n, name) in [
524                (0u32, "location_information"),
525                (1, "subscriber_state"),
526                (3, "current_location"),
527                (5, "ms_classmark"),
528                (6, "imei"),
529            ] {
530                if wanted.iter().any(|w| w == name) {
531                    ri.push(ber::tlv(ctx(n), &[]));
532                }
533            }
534            if ri.is_empty() {
535                return Err("requested_info names nothing NetGet can request".into());
536            }
537            vec![
538                ber::seq(ctxc(0), &[ident]),
539                ber::seq(ctxc(1), &ri),
540                address_tlv(
541                    ctx(3),
542                    required(v, "gsm_scf_address")?,
543                    "gsm_scf_address",
544                    gsm::MAX_ISDN_ADDRESS_DIGITS,
545                )?,
546            ]
547        }
548        59..=61 => {
549            let text = v
550                .get("ussd_text")
551                .or_else(|| v.get("text"))
552                .and_then(Value::as_str)
553                .ok_or("ussd_text is required")?;
554            let (dcs, octets) = gsm::ussd_encode(text)?;
555            let mut p = vec![
556                ber::tlv(Tag::OCTET_STRING, &[dcs]),
557                ber::tlv(Tag::OCTET_STRING, &octets),
558            ];
559            if let Some(m) = v.get("msisdn").filter(|m| !m.is_null()) {
560                p.push(address_tlv(
561                    ctx(0),
562                    m,
563                    "msisdn",
564                    gsm::MAX_ISDN_ADDRESS_DIGITS,
565                )?);
566            }
567            p
568        }
569        46 => {
570            let da = required(v, "sm_rp_da")?;
571            let oa = required(v, "sm_rp_oa")?;
572            vec![
573                encode_sm_rp_da(da)?,
574                encode_sm_rp_oa(oa)?,
575                ber::tlv(
576                    Tag::OCTET_STRING,
577                    &gsm::sms_submit_encode(required(v, "sms")?)?,
578                ),
579            ]
580        }
581        44 => {
582            let mut p = vec![
583                encode_sm_rp_da(required(v, "sm_rp_da")?)?,
584                encode_sm_rp_oa(required(v, "sm_rp_oa")?)?,
585                ber::tlv(
586                    Tag::OCTET_STRING,
587                    &gsm::sms_deliver_encode(required(v, "sms")?)?,
588                ),
589            ];
590            if v.get("more_messages_to_send").and_then(Value::as_bool) == Some(true) {
591                p.push(ber::tlv(Tag::NULL, &[]));
592            }
593            p
594        }
595        22 => {
596            let it = match v.get("interrogation_type").and_then(Value::as_str) {
597                None | Some("basicCall") => 0,
598                Some("forwarding") => 1,
599                Some(other) => {
600                    return Err(format!(
601                        "interrogation_type {other:?} is not basicCall or forwarding"
602                    ))
603                }
604            };
605            vec![
606                address_tlv(
607                    ctx(0),
608                    required(v, "msisdn")?,
609                    "msisdn",
610                    gsm::MAX_ISDN_ADDRESS_DIGITS,
611                )?,
612                ber::integer(ctx(3), it),
613                address_tlv(
614                    ctx(6),
615                    required(v, "gmsc_or_gsm_scf_address")?,
616                    "gmsc_or_gsm_scf_address",
617                    gsm::MAX_ISDN_ADDRESS_DIGITS,
618                )?,
619            ]
620        }
621        2 => vec![
622            imsi_tlv(Tag::OCTET_STRING, required(v, "imsi")?, "imsi")?,
623            address_tlv(
624                ctx(1),
625                required(v, "msc_number")?,
626                "msc_number",
627                gsm::MAX_ISDN_ADDRESS_DIGITS,
628            )?,
629            address_tlv(
630                Tag::OCTET_STRING,
631                required(v, "vlr_number")?,
632                "vlr_number",
633                gsm::MAX_ISDN_ADDRESS_DIGITS,
634            )?,
635        ],
636        other => return Err(format!("operation {other} is not implemented")),
637    };
638    Ok(ber::seq(Tag::SEQUENCE, &parts))
639}
640
641fn encode_sm_rp_da(v: &Value) -> MapResult<Vec<u8>> {
642    if let Some(i) = v.get("imsi") {
643        imsi_tlv(ctx(0), i, "sm_rp_da.imsi")
644    } else if let Some(l) = v.get("lmsi").and_then(Value::as_str) {
645        let b = ber::unhex(l).map_err(|e| e.0)?;
646        if b.len() != 4 {
647            return Err("sm_rp_da.lmsi must be 4 octets (8 hex digits)".into());
648        }
649        Ok(ber::tlv(ctx(1), &b))
650    } else if let Some(s) = v.get("service_centre_address") {
651        address_tlv(
652            ctx(4),
653            s,
654            "sm_rp_da.service_centre_address",
655            gsm::MAX_ADDRESS_DIGITS,
656        )
657    } else {
658        Err("sm_rp_da must be {\"imsi\"}, {\"lmsi\"} or {\"service_centre_address\"}".into())
659    }
660}
661
662fn encode_sm_rp_oa(v: &Value) -> MapResult<Vec<u8>> {
663    if let Some(m) = v.get("msisdn") {
664        address_tlv(ctx(2), m, "sm_rp_oa.msisdn", gsm::MAX_ISDN_ADDRESS_DIGITS)
665    } else if let Some(s) = v.get("service_centre_address") {
666        address_tlv(
667            ctx(4),
668            s,
669            "sm_rp_oa.service_centre_address",
670            gsm::MAX_ADDRESS_DIGITS,
671        )
672    } else {
673        Err("sm_rp_oa must be {\"msisdn\"} or {\"service_centre_address\"}".into())
674    }
675}
676
677// ---------------------------------------------------------------------------
678// Results
679// ---------------------------------------------------------------------------
680
681pub const SUBSCRIBER_STATES: &[&str] = &[
682    "assumedIdle",
683    "camelBusy",
684    "notProvidedFromVLR",
685    "netDetNotReachable",
686];
687
688pub const NOT_REACHABLE_REASONS: &[&str] = &[
689    "msPurged",
690    "imsiDetached",
691    "restrictedArea",
692    "notRegistered",
693];
694
695/// Encode a result from the model's fields. `None` for an operation answered with an empty
696/// ReturnResultLast.
697pub fn encode_result(code: i64, v: &Value) -> MapResult<Option<Vec<u8>>> {
698    Ok(Some(match code {
699        45 => {
700            let mut loc = vec![address_tlv(
701                ctx(1),
702                required(v, "network_node_number")?,
703                "network_node_number",
704                gsm::MAX_ISDN_ADDRESS_DIGITS,
705            )?];
706            if let Some(l) = v.get("lmsi").and_then(Value::as_str) {
707                let b = ber::unhex(l).map_err(|e| e.0)?;
708                if b.len() != 4 {
709                    return Err("lmsi must be 4 octets (8 hex digits)".into());
710                }
711                loc.push(ber::tlv(Tag::OCTET_STRING, &b));
712            }
713            if v.get("gprs_node_indicator").and_then(Value::as_bool) == Some(true) {
714                loc.push(ber::tlv(ctx(5), &[]));
715            }
716            ber::seq(
717                Tag::SEQUENCE,
718                &[
719                    imsi_tlv(Tag::OCTET_STRING, required(v, "imsi")?, "imsi")?,
720                    ber::seq(ctxc(0), &loc),
721                ],
722            )
723        }
724        71 => {
725            let mut info = Vec::new();
726            if let Some(li) = v.get("location_information").filter(|x| !x.is_null()) {
727                let mut l = Vec::new();
728                if let Some(age) = li
729                    .get("age_of_location_information")
730                    .and_then(Value::as_i64)
731                {
732                    if !(0..=32767).contains(&age) {
733                        return Err("age_of_location_information must be 0..32767".into());
734                    }
735                    l.push(ber::integer(Tag::INTEGER, age));
736                }
737                if let Some(vlr) = li.get("vlr_number").filter(|x| !x.is_null()) {
738                    l.push(address_tlv(
739                        ctx(1),
740                        vlr,
741                        "vlr_number",
742                        gsm::MAX_ISDN_ADDRESS_DIGITS,
743                    )?);
744                }
745                if let Some(cgi) = li.get("cell_global_id").filter(|x| !x.is_null()) {
746                    l.push(ber::seq(
747                        ctxc(3),
748                        &[ber::tlv(ctx(0), &gsm::cgi_encode(cgi)?)],
749                    ));
750                }
751                info.push(ber::seq(ctxc(0), &l));
752            }
753            if let Some(state) = v.get("subscriber_state").and_then(Value::as_str) {
754                let inner = match state {
755                    "assumedIdle" => ber::tlv(ctx(0), &[]),
756                    "camelBusy" => ber::tlv(ctx(1), &[]),
757                    "notProvidedFromVLR" => ber::tlv(ctx(2), &[]),
758                    "netDetNotReachable" => {
759                        let reason = v
760                            .get("not_reachable_reason")
761                            .and_then(Value::as_str)
762                            .unwrap_or("imsiDetached");
763                        let r = NOT_REACHABLE_REASONS
764                            .iter()
765                            .position(|n| *n == reason)
766                            .ok_or_else(|| {
767                                format!(
768                                    "not_reachable_reason must be one of {}",
769                                    NOT_REACHABLE_REASONS.join(", ")
770                                )
771                            })?;
772                        ber::integer(Tag::ENUMERATED, r as i64)
773                    }
774                    other => {
775                        return Err(format!(
776                            "subscriber_state {other:?} must be one of {}",
777                            SUBSCRIBER_STATES.join(", ")
778                        ))
779                    }
780                };
781                info.push(ber::seq(ctxc(1), &[inner]));
782            }
783            if let Some(imei) = v.get("imei").and_then(Value::as_str) {
784                if imei.len() != 15 && imei.len() != 16 {
785                    return Err("imei must be 15 or 16 digits".into());
786                }
787                info.push(ber::tlv(ctx(5), &gsm::tbcd_encode(imei)?));
788            }
789            ber::seq(Tag::SEQUENCE, &[ber::seq(Tag::SEQUENCE, &info)])
790        }
791        59 | 60 => {
792            let text = v
793                .get("ussd_text")
794                .or_else(|| v.get("text"))
795                .and_then(Value::as_str)
796                .ok_or("ussd_text is required")?;
797            let (dcs, octets) = gsm::ussd_encode(text)?;
798            ber::seq(
799                Tag::SEQUENCE,
800                &[
801                    ber::tlv(Tag::OCTET_STRING, &[dcs]),
802                    ber::tlv(Tag::OCTET_STRING, &octets),
803                ],
804            )
805        }
806        61 | 44 | 46 => return Ok(None),
807        22 => {
808            let mut p = Vec::new();
809            if let Some(i) = v.get("imsi").filter(|x| !x.is_null()) {
810                p.push(imsi_tlv(ctx(9), i, "imsi")?);
811            }
812            p.push(address_tlv(
813                Tag::OCTET_STRING,
814                required(v, "roaming_number")?,
815                "roaming_number",
816                gsm::MAX_ISDN_ADDRESS_DIGITS,
817            )?);
818            ber::seq(ctxc(3), &p)
819        }
820        2 => ber::seq(
821            Tag::SEQUENCE,
822            &[address_tlv(
823                Tag::OCTET_STRING,
824                required(v, "hlr_number")?,
825                "hlr_number",
826                gsm::MAX_ISDN_ADDRESS_DIGITS,
827            )?],
828        ),
829        other => return Err(format!("operation {other} is not implemented")),
830    }))
831}
832
833/// Decode a ReturnResult's parameter for `code`.
834pub fn decode_result(code: i64, p: Option<&Tlv>) -> MapResult<Value> {
835    let Some(p) = p else {
836        return Ok(json!({}));
837    };
838    let kids = p.children.clone();
839    let find = |tag: Tag| kids.iter().find(|c| c.tag == tag);
840    Ok(match code {
841        45 => {
842            let loc = find(ctxc(0));
843            json!({
844                "imsi": find(Tag::OCTET_STRING).map(imsi),
845                "network_node_number": loc.and_then(|l| l.ctx(1)).map(addr),
846                "lmsi": loc.and_then(|l| l.univ(ber::UNIV_OCTET_STRING)).map(|x| ber::hex(&x.value)),
847                "gprs_node_indicator": loc.map(|l| l.ctx(5).is_some()).unwrap_or(false),
848            })
849        }
850        71 => {
851            let info = kids.first();
852            let li = info.and_then(|i| i.ctx(0));
853            let state = info.and_then(|i| i.ctx(1)).and_then(|s| s.children.first());
854            json!({
855                "location_information": li.map(|l| json!({
856                    "age_of_location_information": l.univ(ber::UNIV_INTEGER).and_then(|a| a.as_i64().ok()),
857                    "vlr_number": l.ctx(1).map(addr),
858                    "cell_global_id": l.ctx(3).and_then(|c| c.children.first()).map(|c| gsm::cgi_decode(&c.value)),
859                })),
860                "subscriber_state": state.map(|s| match (s.tag.class, s.tag.number) {
861                    (ber::CLASS_CONTEXT, 0) => json!("assumedIdle"),
862                    (ber::CLASS_CONTEXT, 1) => json!("camelBusy"),
863                    (ber::CLASS_CONTEXT, 2) => json!("notProvidedFromVLR"),
864                    _ => json!(format!("netDetNotReachable:{}", s.as_i64().ok()
865                        .and_then(|r| NOT_REACHABLE_REASONS.get(r as usize).copied())
866                        .unwrap_or("unknown"))),
867                }),
868                "imei": info.and_then(|i| i.ctx(5)).map(|x| gsm::tbcd_decode(&x.value)),
869            })
870        }
871        59 | 60 => {
872            let (dcs, text) = ussd_fields(&kids)?;
873            json!({"ussd_data_coding_scheme": dcs, "ussd_text": text})
874        }
875        44 | 46 => json!({
876            "sm_rp_ui_present": find(Tag::OCTET_STRING).is_some(),
877        }),
878        22 => json!({
879            "imsi": find(ctx(9)).map(imsi),
880            "roaming_number": find(Tag::OCTET_STRING).map(addr),
881            "uninterpreted": leftovers(&kids, &[ctx(9), Tag::OCTET_STRING]),
882        }),
883        2 => json!({
884            "hlr_number": find(Tag::OCTET_STRING).map(addr),
885        }),
886        _ => json!({"uninterpreted": p.child_labels()}),
887    })
888}
889
890/// SM-DeliveryFailureCause values.
891pub const SM_DELIVERY_FAILURE_CAUSES: &[&str] = &[
892    "memoryCapacityExceeded",
893    "equipmentProtocolError",
894    "equipmentNotSM-Equipped",
895    "unknownServiceCentre",
896    "sc-Congestion",
897    "invalidSME-Address",
898    "subscriberNotSC-Subscriber",
899];
900
901/// An error's parameter. Only sm-DeliveryFailure's is mandatory; absentSubscriberSM takes an
902/// optional diagnostic. Every other error is sent without one (each is OPTIONAL in TS 29.002).
903pub fn encode_error_parameter(code: i64, v: &Value) -> MapResult<Option<Vec<u8>>> {
904    match code {
905        32 => {
906            let cause = v
907                .get("delivery_failure_cause")
908                .and_then(Value::as_str)
909                .unwrap_or("memoryCapacityExceeded");
910            let i = SM_DELIVERY_FAILURE_CAUSES
911                .iter()
912                .position(|c| *c == cause)
913                .ok_or_else(|| {
914                    format!(
915                        "delivery_failure_cause must be one of {}",
916                        SM_DELIVERY_FAILURE_CAUSES.join(", ")
917                    )
918                })?;
919            Ok(Some(ber::seq(
920                Tag::SEQUENCE,
921                &[ber::integer(Tag::ENUMERATED, i as i64)],
922            )))
923        }
924        6 => match v
925            .get("absent_subscriber_diagnostic")
926            .and_then(Value::as_i64)
927        {
928            Some(d) if (0..=255).contains(&d) => Ok(Some(ber::seq(
929                Tag::SEQUENCE,
930                &[ber::integer(Tag::INTEGER, d)],
931            ))),
932            Some(_) => Err("absent_subscriber_diagnostic must be 0..255".into()),
933            None => Ok(None),
934        },
935        _ => Ok(None),
936    }
937}
938
939pub fn decode_error_parameter(code: i64, p: Option<&Tlv>) -> Value {
940    let Some(p) = p else {
941        return Value::Null;
942    };
943    match code {
944        32 => json!({
945            "delivery_failure_cause": p.univ(ber::UNIV_ENUMERATED)
946                .and_then(|e| e.as_i64().ok())
947                .and_then(|i| SM_DELIVERY_FAILURE_CAUSES.get(i as usize).copied()),
948        }),
949        6 => json!({
950            "absent_subscriber_diagnostic": p.univ(ber::UNIV_INTEGER).and_then(|e| e.as_i64().ok()),
951        }),
952        _ => json!({"present": p.tag.label()}),
953    }
954}