1use 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 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 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}