Skip to main content

netget/server/diameter/
codec.rs

1//! Bounded selected RFC6733 TCP messages and RFC7155 stateless NASREQ.
2use anyhow::{bail, ensure, Context, Result};
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use std::{
6    net::{IpAddr, Ipv4Addr, Ipv6Addr},
7    time::Duration,
8};
9use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
10
11pub const MAX_FRAME_BYTES: usize = 16 * 1024;
12pub const MAX_AVPS: usize = 64;
13pub const DEFAULT_IO_SECONDS: u64 = 90;
14pub const DEFAULT_HANDLER_SECONDS: u64 = 30;
15pub const DEFAULT_WATCHDOG_SECONDS: u64 = 30;
16pub const DEFAULT_LLM_FALLBACK: bool = false;
17pub const CER: u32 = 257;
18pub const AA: u32 = 265;
19pub const DWR: u32 = 280;
20pub const DPR: u32 = 282;
21pub const SESSION: u32 = 263;
22pub const HOST: u32 = 264;
23pub const REALM: u32 = 296;
24pub const DEST_REALM: u32 = 283;
25pub const DEST_HOST: u32 = 293;
26pub const AUTH_APP: u32 = 258;
27pub const ACCT_APP: u32 = 259;
28pub const VENDOR_APP: u32 = 260;
29pub const AUTH_TYPE: u32 = 274;
30pub const AUTH_STATE: u32 = 277;
31pub const RESULT: u32 = 268;
32pub const USER: u32 = 1;
33pub const PASSWORD: u32 = 2;
34pub const HOST_IP: u32 = 257;
35pub const VENDOR: u32 = 266;
36pub const PRODUCT: u32 = 269;
37pub const ORIGIN_STATE: u32 = 278;
38pub const SECURITY: u32 = 299;
39pub const SUPPORTED_VENDOR: u32 = 265;
40pub const FIRMWARE: u32 = 267;
41pub const DISCONNECT_CAUSE: u32 = 273;
42pub const ERROR_MESSAGE: u32 = 281;
43pub const FAILED_AVP: u32 = 279;
44pub const NAS_ID: u32 = 32;
45pub const NAS_PORT: u32 = 5;
46pub const SERVICE: u32 = 6;
47pub const REPLY_MESSAGE: u32 = 18;
48pub const FILTER_ID: u32 = 11;
49pub const SESSION_TIMEOUT: u32 = 27;
50pub const MAX_PASSWORD_BYTES: usize = 128;
51
52#[derive(Clone, Debug, PartialEq, Eq)]
53pub struct Avp {
54    pub code: u32,
55    pub flags: u8,
56    pub vendor: Option<u32>,
57    pub data: Vec<u8>,
58}
59impl Avp {
60    pub fn bytes(code: u32, data: impl Into<Vec<u8>>) -> Self {
61        Self {
62            code,
63            flags: if [PRODUCT, FIRMWARE, ERROR_MESSAGE].contains(&code) {
64                0
65            } else {
66                0x40
67            },
68            vendor: None,
69            data: data.into(),
70        }
71    }
72    pub fn number(code: u32, value: u32) -> Self {
73        Self::bytes(code, value.to_be_bytes())
74    }
75    pub fn text(code: u32, value: &str) -> Self {
76        Self::bytes(code, value.as_bytes())
77    }
78    pub fn integer(&self) -> Result<u32> {
79        ensure!(self.data.len() == 4, "Diameter integer AVP length");
80        Ok(u32::from_be_bytes(self.data[..].try_into()?))
81    }
82    pub fn string(&self, max: usize) -> Result<String> {
83        ensure!(self.data.len() <= max, "Diameter string AVP bound");
84        let s = std::str::from_utf8(&self.data).context("Diameter UTF8 AVP")?;
85        ensure!(!s.contains('\0'), "Diameter NUL excluded");
86        Ok(s.into())
87    }
88    fn encode(&self, out: &mut Vec<u8>) -> Result<()> {
89        ensure!(
90            self.flags & 0x1f == 0 && (self.flags & 0x80 != 0) == self.vendor.is_some(),
91            "Diameter AVP flags"
92        );
93        let length = 8 + self.vendor.is_some() as usize * 4 + self.data.len();
94        ensure!(length <= MAX_FRAME_BYTES, "Diameter AVP size");
95        ensure!(
96            out.len() + ((length + 3) & !3) <= MAX_FRAME_BYTES,
97            "Diameter aggregate message size before copy"
98        );
99        out.extend(self.code.to_be_bytes());
100        out.push(self.flags);
101        put24(out, length as u32);
102        if let Some(vendor) = self.vendor {
103            out.extend(vendor.to_be_bytes());
104        }
105        out.extend(&self.data);
106        while !out.len().is_multiple_of(4) {
107            out.push(0);
108        }
109        Ok(())
110    }
111}
112#[derive(Clone, Debug, PartialEq, Eq)]
113pub struct Packet {
114    pub flags: u8,
115    pub command: u32,
116    pub application: u32,
117    pub hop: u32,
118    pub end: u32,
119    pub avps: Vec<Avp>,
120}
121impl Packet {
122    pub fn request(command: u32, application: u32) -> Result<Self> {
123        let mut ids = [0; 8];
124        rand::RngCore::try_fill_bytes(&mut rand::rngs::OsRng, &mut ids)
125            .map_err(|_| anyhow::anyhow!("Diameter identifier source unavailable"))?;
126        Ok(Self {
127            flags: 0x80 | if command == AA { 0x40 } else { 0 },
128            command,
129            application,
130            hop: u32::from_be_bytes(ids[..4].try_into()?),
131            end: u32::from_be_bytes(ids[4..].try_into()?),
132            avps: vec![],
133        })
134    }
135    pub fn is_request(&self) -> bool {
136        self.flags & 0x80 != 0
137    }
138    pub fn answer(&self, result: u32) -> Self {
139        Self {
140            flags: self.flags & 0x40
141                | if (3000..4000).contains(&result) {
142                    0x20
143                } else {
144                    0
145                },
146            command: self.command,
147            application: self.application,
148            hop: self.hop,
149            end: self.end,
150            avps: vec![Avp::number(RESULT, result)],
151        }
152    }
153    pub fn matches(&self, request: &Self) -> bool {
154        !self.is_request()
155            && self.command == request.command
156            && self.application == request.application
157            && self.hop == request.hop
158            && self.end == request.end
159            && (self.flags & 0x40) == (request.flags & 0x40)
160    }
161    pub fn one(&self, code: u32) -> Result<&Avp> {
162        let mut v = self
163            .avps
164            .iter()
165            .filter(|a| a.code == code && a.vendor.is_none());
166        let first = v.next().context("Diameter required AVP missing")?;
167        ensure!(v.next().is_none(), "Diameter singleton AVP repeated");
168        Ok(first)
169    }
170    pub fn optional(&self, code: u32) -> Result<Option<&Avp>> {
171        let mut v = self
172            .avps
173            .iter()
174            .filter(|a| a.code == code && a.vendor.is_none());
175        let first = v.next();
176        ensure!(v.next().is_none(), "Diameter singleton AVP repeated");
177        Ok(first)
178    }
179    pub fn num(&self, code: u32) -> Result<u32> {
180        self.one(code)?.integer()
181    }
182    pub fn text(&self, code: u32, max: usize) -> Result<String> {
183        self.one(code)?.string(max)
184    }
185    pub fn origin(&mut self, identity: &Identity) {
186        self.avps.push(Avp::text(HOST, &identity.host));
187        self.avps.push(Avp::text(REALM, &identity.realm));
188    }
189    pub fn encode(&self) -> Result<Vec<u8>> {
190        ensure!(
191            self.command <= 0xffffff
192                && self.flags & 15 == 0
193                && !(self.is_request() && self.flags & 0x20 != 0)
194                && !(!self.is_request() && self.flags & 0x10 != 0)
195                && self.avps.len() <= MAX_AVPS,
196            "Diameter header/AVP bounds"
197        );
198        let mut out = vec![1, 0, 0, 0, self.flags];
199        put24(&mut out, self.command);
200        out.extend(self.application.to_be_bytes());
201        out.extend(self.hop.to_be_bytes());
202        out.extend(self.end.to_be_bytes());
203        for avp in &self.avps {
204            avp.encode(&mut out)?;
205        }
206        ensure!(out.len() <= MAX_FRAME_BYTES, "Diameter message size");
207        let length = out.len() as u32;
208        out[1..4].copy_from_slice(&length.to_be_bytes()[1..]);
209        Ok(out)
210    }
211    pub fn decode(data: &[u8]) -> Result<Self> {
212        ensure!(
213            data.len() >= 20
214                && data.len() <= MAX_FRAME_BYTES
215                && data[0] == 1
216                && get24(&data[1..4]) as usize == data.len()
217                && data.len().is_multiple_of(4),
218            "Diameter frame header/size"
219        );
220        let flags = data[4] & 0xf0;
221        ensure!(
222            flags & 15 == 0
223                && !(flags & 0x80 != 0 && flags & 0x20 != 0)
224                && !(flags & 0x80 == 0 && flags & 0x10 != 0),
225            "Diameter header flags"
226        );
227        let mut avps = vec![];
228        let mut offset = 20;
229        while offset < data.len() {
230            ensure!(
231                data.len() - offset >= 8 && avps.len() < MAX_AVPS,
232                "Diameter AVP header/count"
233            );
234            let code = u32::from_be_bytes(data[offset..offset + 4].try_into()?);
235            let flags = data[offset + 4] & 0xe0;
236            ensure!(flags & 0x1f == 0, "Diameter AVP reserved flags");
237            let length = get24(&data[offset + 5..offset + 8]) as usize;
238            let head = if flags & 0x80 != 0 { 12 } else { 8 };
239            let padded = (length + 3) & !3;
240            ensure!(
241                length >= head && offset + padded <= data.len(),
242                "Diameter AVP length/padding"
243            );
244            let vendor = if head == 12 {
245                Some(u32::from_be_bytes(
246                    data[offset + 8..offset + 12].try_into()?,
247                ))
248            } else {
249                None
250            };
251            avps.push(Avp {
252                code,
253                flags,
254                vendor,
255                data: data[offset + head..offset + length].to_vec(),
256            });
257            offset += padded;
258        }
259        Ok(Self {
260            flags,
261            command: get24(&data[5..8]),
262            application: u32::from_be_bytes(data[8..12].try_into()?),
263            hop: u32::from_be_bytes(data[12..16].try_into()?),
264            end: u32::from_be_bytes(data[16..20].try_into()?),
265            avps,
266        })
267    }
268    pub fn unsupported_mandatory(&self, allowed: &[u32]) -> Option<&Avp> {
269        self.avps
270            .iter()
271            .find(|a| a.flags & 0x40 != 0 && (a.vendor.is_some() || !allowed.contains(&a.code)))
272    }
273}
274fn put24(out: &mut Vec<u8>, v: u32) {
275    out.extend(&v.to_be_bytes()[1..]);
276}
277fn get24(v: &[u8]) -> u32 {
278    u32::from_be_bytes([0, v[0], v[1], v[2]])
279}
280pub async fn read_packet<R: AsyncRead + Unpin>(r: &mut R, deadline: Duration) -> Result<Packet> {
281    tokio::time::timeout(deadline, async {
282        let mut h = [0; 20];
283        r.read_exact(&mut h).await?;
284        let size = get24(&h[1..4]) as usize;
285        ensure!(
286            h[0] == 1 && (20..=MAX_FRAME_BYTES).contains(&size) && size.is_multiple_of(4),
287            "Diameter length/version before allocation"
288        );
289        let mut bytes = vec![0; size];
290        bytes[..20].copy_from_slice(&h);
291        r.read_exact(&mut bytes[20..]).await?;
292        Packet::decode(&bytes)
293    })
294    .await
295    .context("Diameter complete-frame deadline")?
296}
297pub async fn write_packet<W: AsyncWrite + Unpin>(w: &mut W, p: &Packet) -> Result<usize> {
298    let b = p.encode()?;
299    tokio::time::timeout(Duration::from_secs(10), w.write_all(&b))
300        .await
301        .context("Diameter write deadline")??;
302    Ok(b.len())
303}
304pub fn within_json_budget(v: &Value) -> bool {
305    crate::utils::json_budget::within_budget(v, MAX_FRAME_BYTES, 4096, 16)
306}
307pub fn owned_json(v: Value) -> Result<Value> {
308    if !within_json_budget(&v) {
309        crate::utils::json_budget::drop_iteratively(v);
310        bail!("Diameter JSON size/node/depth budget");
311    }
312    Ok(v)
313}
314pub fn timeout(value: Option<u64>, default: u64) -> Result<Duration> {
315    let v = value.unwrap_or(default);
316    ensure!((1..=300).contains(&v), "Diameter timeout1..300seconds");
317    Ok(Duration::from_secs(v))
318}
319#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
320#[serde(deny_unknown_fields)]
321pub struct Identity {
322    pub host: String,
323    pub realm: String,
324}
325pub fn identity_text(v: &str) -> Result<()> {
326    ensure!(
327        !v.is_empty()
328            && v.len() <= 255
329            && v.is_ascii()
330            && v.bytes()
331                .all(|b| b.is_ascii_alphanumeric() || b == b'.' || b == b'-'),
332        "Diameter selected ASCII identity"
333    );
334    Ok(())
335}
336impl Identity {
337    pub fn validate(&self) -> Result<()> {
338        identity_text(&self.host)?;
339        identity_text(&self.realm)
340    }
341    pub fn from_packet(p: &Packet) -> Result<Self> {
342        let id = Self {
343            host: p.text(HOST, 255)?,
344            realm: p.text(REALM, 255)?,
345        };
346        id.validate()?;
347        Ok(id)
348    }
349    pub fn same_peer(&self, p: &Packet) -> Result<()> {
350        ensure!(
351            {
352                let other = Self::from_packet(p)?;
353                self.host.eq_ignore_ascii_case(&other.host)
354                    && self.realm.eq_ignore_ascii_case(&other.realm)
355            },
356            "Diameter peer identity changed"
357        );
358        Ok(())
359    }
360}
361pub const BASE_ALLOWED: &[u32] = &[
362    HOST,
363    REALM,
364    HOST_IP,
365    VENDOR,
366    PRODUCT,
367    AUTH_APP,
368    ACCT_APP,
369    VENDOR_APP,
370    ORIGIN_STATE,
371    SECURITY,
372    SUPPORTED_VENDOR,
373    FIRMWARE,
374    RESULT,
375    ERROR_MESSAGE,
376    FAILED_AVP,
377    DISCONNECT_CAUSE,
378];
379pub fn capabilities(p: &Packet) -> Result<Identity> {
380    ensure!(
381        p.command == CER && p.application == 0 && p.flags & 0x40 == 0,
382        "Diameter capabilities header"
383    );
384    ensure!(
385        p.unsupported_mandatory(BASE_ALLOWED).is_none(),
386        "Diameter unsupported capability AVP"
387    );
388    let id = Identity::from_packet(p)?;
389    p.num(VENDOR)?;
390    ensure!(
391        !p.text(PRODUCT, 255)?.is_empty(),
392        "Diameter product required"
393    );
394    let ips: Vec<_> = p
395        .avps
396        .iter()
397        .filter(|a| a.code == HOST_IP && a.vendor.is_none())
398        .collect();
399    ensure!(
400        !ips.is_empty() && ips.len() <= 8,
401        "Diameter capability address count"
402    );
403    for ip in ips {
404        decode_address(&ip.data)?;
405    }
406    ensure!(
407        p.avps
408            .iter()
409            .filter(|a| a.code == AUTH_APP && a.vendor.is_none())
410            .any(|a| a.integer().ok() == Some(1)),
411        "Diameter NASREQ not advertised"
412    );
413    for a in p
414        .avps
415        .iter()
416        .filter(|a| a.code == SECURITY && a.vendor.is_none())
417    {
418        ensure!(a.integer()? == 0, "Diameter inband TLS excluded");
419    }
420    for avp in p.avps.iter().filter(|a| a.vendor.is_none()) {
421        match avp.code {
422            AUTH_APP | ACCT_APP | SUPPORTED_VENDOR | FIRMWARE | ORIGIN_STATE => {
423                avp.integer()?;
424            }
425            VENDOR_APP => {
426                // Base capability advertisement, not a supported vendor AAA
427                // application or policy group. Parse only its flat typed IDs.
428                let grouped = decode_capability_group(&avp.data)?;
429                grouped.num(VENDOR)?;
430                let auth = grouped.optional(AUTH_APP)?.map(Avp::integer).transpose()?;
431                let acct = grouped.optional(ACCT_APP)?.map(Avp::integer).transpose()?;
432                ensure!(
433                    auth.is_some() ^ acct.is_some(),
434                    "Diameter capability group app type"
435                );
436            }
437            _ => {}
438        }
439    }
440    if !p.is_request() {
441        ensure!(
442            p.num(RESULT)? == 2001 && p.flags & 0x20 == 0,
443            "Diameter capability rejected"
444        );
445    }
446    Ok(id)
447}
448pub fn decode_capability_group(data: &[u8]) -> Result<Packet> {
449    ensure!(
450        data.len() + 20 <= MAX_FRAME_BYTES,
451        "Diameter capability group bound"
452    );
453    let mut frame = vec![1, 0, 0, 0];
454    frame.resize(20, 0);
455    frame.extend(data);
456    let size = frame.len() as u32;
457    frame[1..4].copy_from_slice(&size.to_be_bytes()[1..]);
458    let group = Packet::decode(&frame)?;
459    ensure!(
460        group.avps.len() <= 3
461            && group
462                .avps
463                .iter()
464                .all(|a| a.vendor.is_none() && [VENDOR, AUTH_APP, ACCT_APP].contains(&a.code)),
465        "Diameter flat capability group"
466    );
467    Ok(group)
468}
469pub fn capability_fields(p: &mut Packet, id: &Identity, ip: IpAddr) {
470    p.origin(id);
471    p.avps.push(Avp::bytes(HOST_IP, encode_address(ip)));
472    p.avps.push(Avp::number(VENDOR, 0));
473    p.avps
474        .push(Avp::text(PRODUCT, "NetGet experimental NASREQ"));
475    p.avps.push(Avp::number(AUTH_APP, 1));
476}
477fn encode_address(ip: IpAddr) -> Vec<u8> {
478    match ip {
479        IpAddr::V4(a) => [&[0, 1][..], &a.octets()].concat(),
480        IpAddr::V6(a) => [&[0, 2][..], &a.octets()].concat(),
481    }
482}
483pub fn decode_address(v: &[u8]) -> Result<IpAddr> {
484    match v {
485        [0, 1, rest @ ..] if rest.len() == 4 => {
486            Ok(Ipv4Addr::from(<[u8; 4]>::try_from(rest)?).into())
487        }
488        [0, 2, rest @ ..] if rest.len() == 16 => {
489            Ok(Ipv6Addr::from(<[u8; 16]>::try_from(rest)?).into())
490        }
491        _ => bail!("Diameter selected IP address AVP"),
492    }
493}
494fn default_auth_type() -> u32 {
495    3
496}
497#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
498#[serde(deny_unknown_fields)]
499pub struct Request {
500    pub username: String,
501    #[serde(default, skip_serializing_if = "Option::is_none")]
502    pub password: Option<String>,
503    #[serde(default = "default_auth_type")]
504    pub auth_request_type: u32,
505    #[serde(default, skip_serializing_if = "Option::is_none")]
506    pub nas_identifier: Option<String>,
507    #[serde(default, skip_serializing_if = "Option::is_none")]
508    pub nas_port: Option<u32>,
509}
510impl Request {
511    pub fn validate(&self) -> Result<()> {
512        ensure!(
513            !self.username.is_empty()
514                && self.username.len() <= 255
515                && !self.username.contains('\0'),
516            "Diameter username UTF8 bound"
517        );
518        ensure!(
519            (1..=3).contains(&self.auth_request_type),
520            "Diameter Auth-Request-Type1..3"
521        );
522        ensure!(
523            (self.auth_request_type == 2 && self.password.is_none())
524                || (self.auth_request_type != 2 && self.password.is_some()),
525            "Diameter selected PAP password required for authentication only"
526        );
527        if let Some(v) = &self.password {
528            ensure!(
529                v.len() <= MAX_PASSWORD_BYTES && !v.contains('\0'),
530                "Diameter PAP UTF8 password bound"
531            );
532        }
533        if let Some(v) = &self.nas_identifier {
534            ensure!(
535                v.len() <= 255 && !v.contains('\0'),
536                "Diameter NAS-Identifier bound"
537            );
538        }
539        Ok(())
540    }
541    pub fn credential_free(&self) -> Value {
542        serde_json::json!({"username":self.username,"auth_request_type":self.auth_request_type,"nas_identifier":self.nas_identifier,"nas_port":self.nas_port})
543    }
544    pub fn packet(&self, id: &Identity, destination: &Identity, session: &str) -> Result<Packet> {
545        self.validate()?;
546        session_text(session)?;
547        let mut p = Packet::request(AA, 1)?;
548        p.avps.push(Avp::text(SESSION, session));
549        p.origin(id);
550        p.avps.push(Avp::text(DEST_REALM, &destination.realm));
551        p.avps.push(Avp::text(DEST_HOST, &destination.host));
552        p.avps.push(Avp::number(AUTH_APP, 1));
553        p.avps.push(Avp::number(AUTH_TYPE, self.auth_request_type));
554        p.avps.push(Avp::number(AUTH_STATE, 1));
555        p.avps.push(Avp::text(USER, &self.username));
556        if let Some(v) = &self.password {
557            p.avps.push(Avp::text(PASSWORD, v));
558        }
559        if let Some(v) = &self.nas_identifier {
560            p.avps.push(Avp::text(NAS_ID, v));
561        }
562        if let Some(v) = self.nas_port {
563            p.avps.push(Avp::number(NAS_PORT, v));
564        }
565        Ok(p)
566    }
567    pub fn from_packet(p: &Packet, id: &Identity, peer: &Identity) -> Result<(Self, String)> {
568        ensure!(
569            p.is_request() && p.command == AA && p.application == 1 && p.flags & 0x40 != 0,
570            "Diameter NASREQ request header"
571        );
572        peer.same_peer(p)?;
573        ensure!(
574            p.avps
575                .first()
576                .is_some_and(|a| a.code == SESSION && a.vendor.is_none()),
577            "Diameter Session-Id fixed first AVP"
578        );
579        ensure!(
580            p.text(DEST_REALM, 255)?.eq_ignore_ascii_case(&id.realm),
581            "Diameter destination realm"
582        );
583        if let Some(a) = p.optional(DEST_HOST)? {
584            ensure!(
585                a.string(255)?.eq_ignore_ascii_case(&id.host),
586                "Diameter destination host"
587            );
588        }
589        ensure!(
590            p.num(AUTH_APP)? == 1 && p.num(AUTH_STATE)? == 1,
591            "Diameter stateless NASREQ required"
592        );
593        let session = p.text(SESSION, 512)?;
594        session_text(&session)?;
595        let r = Self {
596            username: p.text(USER, 255)?,
597            password: p
598                .optional(PASSWORD)?
599                .map(|a| a.string(MAX_PASSWORD_BYTES))
600                .transpose()?,
601            auth_request_type: p.num(AUTH_TYPE)?,
602            nas_identifier: p.optional(NAS_ID)?.map(|a| a.string(255)).transpose()?,
603            nas_port: p.optional(NAS_PORT)?.map(Avp::integer).transpose()?,
604        };
605        r.validate()?;
606        Ok((r, session))
607    }
608}
609pub const REQUEST_ALLOWED: &[u32] = &[
610    SESSION,
611    HOST,
612    REALM,
613    DEST_HOST,
614    DEST_REALM,
615    AUTH_APP,
616    AUTH_TYPE,
617    AUTH_STATE,
618    USER,
619    PASSWORD,
620    NAS_ID,
621    NAS_PORT,
622    ORIGIN_STATE,
623];
624pub const ANSWER_ALLOWED: &[u32] = &[
625    SESSION,
626    HOST,
627    REALM,
628    AUTH_APP,
629    AUTH_TYPE,
630    AUTH_STATE,
631    RESULT,
632    USER,
633    SERVICE,
634    REPLY_MESSAGE,
635    FILTER_ID,
636    SESSION_TIMEOUT,
637    ERROR_MESSAGE,
638    FAILED_AVP,
639    ORIGIN_STATE,
640];
641pub fn session_text(v: &str) -> Result<()> {
642    ensure!(
643        !v.is_empty() && v.len() <= 512 && !v.contains('\0'),
644        "Diameter Session-Id bound"
645    );
646    Ok(())
647}
648#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
649#[serde(rename_all = "snake_case")]
650pub enum Verdict {
651    Accept,
652    Reject,
653    Error,
654}
655impl Default for Verdict {
656    fn default() -> Self {
657        Self::Reject
658    }
659}
660impl Verdict {
661    pub fn code(self) -> u32 {
662        match self {
663            Self::Accept => 2001,
664            Self::Reject => 4001,
665            Self::Error => 5012,
666        }
667    }
668}
669#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
670#[serde(deny_unknown_fields)]
671pub struct Reply {
672    #[serde(default)]
673    pub verdict: Verdict,
674    #[serde(default)]
675    pub reply_messages: Vec<String>,
676    #[serde(default)]
677    pub filter_ids: Vec<String>,
678    #[serde(default, skip_serializing_if = "Option::is_none")]
679    pub service_type: Option<u32>,
680    #[serde(default, skip_serializing_if = "Option::is_none")]
681    pub session_timeout: Option<u32>,
682}
683impl Reply {
684    pub fn validate(&self) -> Result<()> {
685        ensure!(
686            self.reply_messages.len() <= 8 && self.filter_ids.len() <= 8,
687            "Diameter reply AVP count"
688        );
689        for s in self.reply_messages.iter().chain(&self.filter_ids) {
690            ensure!(
691                s.len() <= 1024 && !s.contains('\0'),
692                "Diameter reply UTF8 bound"
693            );
694        }
695        if let Some(v) = self.service_type {
696            ensure!((1..=19).contains(&v), "Diameter selected Service-Type1..19");
697        }
698        Ok(())
699    }
700    pub fn packet(&self, request: &Packet, id: &Identity) -> Result<Packet> {
701        self.validate()?;
702        let mut a = request.answer(self.verdict.code());
703        a.origin(id);
704        a.avps
705            .insert(0, Avp::text(SESSION, &request.text(SESSION, 512)?));
706        a.avps.push(Avp::number(AUTH_APP, 1));
707        a.avps.push(Avp::number(AUTH_TYPE, request.num(AUTH_TYPE)?));
708        a.avps.push(Avp::number(AUTH_STATE, 1));
709        for v in &self.reply_messages {
710            a.avps.push(Avp::text(REPLY_MESSAGE, v));
711        }
712        for v in &self.filter_ids {
713            a.avps.push(Avp::text(FILTER_ID, v));
714        }
715        if let Some(v) = self.service_type {
716            a.avps.push(Avp::number(SERVICE, v));
717        }
718        if let Some(v) = self.session_timeout {
719            a.avps.push(Avp::number(SESSION_TIMEOUT, v));
720        }
721        a.encode()?;
722        Ok(a)
723    }
724}
725#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
726pub struct Response {
727    pub result_code: u32,
728    pub accepted: bool,
729    pub stateless: bool,
730    pub reply_messages: Vec<String>,
731    pub filter_ids: Vec<String>,
732    pub service_type: Option<u32>,
733    pub session_timeout: Option<u32>,
734}
735impl Response {
736    pub fn from_packet(p: &Packet, request: &Packet, peer: &Identity) -> Result<Self> {
737        ensure!(p.matches(request), "Diameter answer correlation");
738        peer.same_peer(p)?;
739        let result = p.num(RESULT)?;
740        ensure!((1000..6000).contains(&result), "Diameter Result-Code class");
741        ensure!(
742            (p.flags & 0x20 != 0) == (3000..4000).contains(&result),
743            "Diameter error flag/result category"
744        );
745        ensure!(
746            p.unsupported_mandatory(ANSWER_ALLOWED).is_none(),
747            "Diameter unhandled mandatory answer AVP"
748        );
749        let successful = result == 2001;
750        ensure!(
751            !successful || p.optional(FAILED_AVP)?.is_none(),
752            "Diameter success cannot report a failed AVP"
753        );
754        if p.optional(SESSION)?.is_some() {
755            ensure!(
756                p.avps
757                    .first()
758                    .is_some_and(|a| a.code == SESSION && a.vendor.is_none()),
759                "Diameter answer Session-Id fixed first AVP"
760            );
761        }
762        // Protocol error answers (E-bit) use the RFC6733 generic grammar. Other
763        // answers must meet the NASREQ grammar even when they deny the request.
764        if p.flags & 0x20 == 0 {
765            ensure!(
766                p.text(SESSION, 512)? == request.text(SESSION, 512)?
767                    && p.num(AUTH_APP)? == 1
768                    && p.num(AUTH_TYPE)? == request.num(AUTH_TYPE)?,
769                "Diameter NASREQ answer fields"
770            );
771        }
772        let stateless = p.optional(AUTH_STATE)?.map(Avp::integer).transpose()? == Some(1);
773        ensure!(
774            !successful || stateless,
775            "Diameter successful answer must agree to stateless scope"
776        );
777        let strings = |code| -> Result<Vec<String>> {
778            let values: Vec<_> = p
779                .avps
780                .iter()
781                .filter(|a| a.code == code && a.vendor.is_none())
782                .collect();
783            ensure!(values.len() <= 8, "Diameter answer repeated AVP count");
784            values.into_iter().map(|a| a.string(1024)).collect()
785        };
786        let service_type = p.optional(SERVICE)?.map(Avp::integer).transpose()?;
787        if let Some(v) = service_type {
788            ensure!((1..=19).contains(&v), "Diameter unsupported Service-Type");
789        }
790        let session_timeout = p.optional(SESSION_TIMEOUT)?.map(Avp::integer).transpose()?;
791        Ok(Self {
792            result_code: result,
793            accepted: successful && stateless,
794            stateless,
795            reply_messages: strings(REPLY_MESSAGE)?,
796            filter_ids: strings(FILTER_ID)?,
797            service_type,
798            session_timeout,
799        })
800    }
801}
802pub fn error_answer(
803    request: &Packet,
804    id: &Identity,
805    result: u32,
806    failed: Option<&Avp>,
807) -> Result<Packet> {
808    let mut p = request.answer(result);
809    p.origin(id);
810    if request.command == AA {
811        if let Ok(v) = request.text(SESSION, 512) {
812            p.avps.insert(0, Avp::text(SESSION, &v));
813        }
814        if !(3000..4000).contains(&result) {
815            p.avps.push(Avp::number(AUTH_APP, 1));
816            if let Ok(v) = request.num(AUTH_TYPE) {
817                p.avps.push(Avp::number(AUTH_TYPE, v));
818            }
819            p.avps.push(Avp::number(AUTH_STATE, 1));
820        }
821    }
822    if let Some(avp) = failed {
823        let mut data = vec![];
824        avp.encode(&mut data)?;
825        p.avps.push(Avp::bytes(FAILED_AVP, data));
826    }
827    p.encode()?;
828    Ok(p)
829}