1use anyhow::{bail, ensure, Context, Result};
11use serde_json::{json, Map, Value};
12use std::net::{Ipv4Addr, Ipv6Addr};
13
14pub const PORT: u16 = 8805;
15pub const MAX_DATAGRAM: usize = 65_507;
16pub const MAX_DEPTH: usize = 8;
18pub const MAX_IES: usize = 1024;
20const NTP_UNIX: u64 = 2_208_988_800;
22
23pub const MESSAGES: &[(u8, &str)] = &[
24 (1, "heartbeat_request"),
25 (2, "heartbeat_response"),
26 (3, "pfd_management_request"),
27 (4, "pfd_management_response"),
28 (5, "association_setup_request"),
29 (6, "association_setup_response"),
30 (7, "association_update_request"),
31 (8, "association_update_response"),
32 (9, "association_release_request"),
33 (10, "association_release_response"),
34 (11, "version_not_supported_response"),
35 (12, "node_report_request"),
36 (13, "node_report_response"),
37 (14, "session_set_deletion_request"),
38 (15, "session_set_deletion_response"),
39 (50, "session_establishment_request"),
40 (51, "session_establishment_response"),
41 (52, "session_modification_request"),
42 (53, "session_modification_response"),
43 (54, "session_deletion_request"),
44 (55, "session_deletion_response"),
45 (56, "session_report_request"),
46 (57, "session_report_response"),
47];
48
49pub fn message_name(t: u8) -> String {
50 MESSAGES
51 .iter()
52 .find(|(n, _)| *n == t)
53 .map(|(_, s)| s.to_string())
54 .unwrap_or_else(|| format!("message_{t}"))
55}
56
57pub fn message_type(name: &str) -> Result<u8> {
58 MESSAGES
59 .iter()
60 .find(|(_, s)| *s == name)
61 .map(|(n, _)| *n)
62 .with_context(|| format!("{name:?} is not a PFCP message name"))
63}
64
65pub fn has_seid(t: u8) -> bool {
67 t >= 50
68}
69
70pub fn is_request(t: u8) -> bool {
72 matches!(t, 1 | 3 | 5 | 7 | 9 | 12 | 14 | 16 | 50 | 52 | 54 | 56)
73}
74
75pub const CAUSES: &[(u8, &str)] = &[
76 (1, "request_accepted"),
77 (2, "more_usage_report_to_send"),
78 (64, "request_rejected"),
79 (65, "session_context_not_found"),
80 (66, "mandatory_ie_missing"),
81 (67, "conditional_ie_missing"),
82 (68, "invalid_length"),
83 (69, "mandatory_ie_incorrect"),
84 (70, "invalid_forwarding_policy"),
85 (71, "invalid_f_teid_allocation_option"),
86 (72, "no_established_pfcp_association"),
87 (73, "rule_creation_modification_failure"),
88 (74, "pfcp_entity_in_congestion"),
89 (75, "no_resources_available"),
90 (76, "service_not_supported"),
91 (77, "system_failure"),
92 (78, "redirection_requested"),
93];
94
95pub fn cause_name(c: u8) -> String {
96 CAUSES
97 .iter()
98 .find(|(n, _)| *n == c)
99 .map(|(_, s)| s.to_string())
100 .unwrap_or_else(|| format!("cause_{c}"))
101}
102
103pub fn cause_code(name: &str) -> Result<u8> {
104 CAUSES
105 .iter()
106 .find(|(_, s)| *s == name)
107 .map(|(n, _)| *n)
108 .or_else(|| name.strip_prefix("cause_").and_then(|n| n.parse().ok()))
109 .with_context(|| {
110 format!(
111 "cause {name:?} is not one of {}",
112 CAUSES
113 .iter()
114 .map(|(_, s)| *s)
115 .collect::<Vec<_>>()
116 .join(", ")
117 )
118 })
119}
120
121#[derive(Clone, Copy, PartialEq, Eq, Debug)]
122enum K {
123 Grouped,
124 U8,
125 U16,
126 U32,
127 Cause,
128 NodeId,
129 FSeid,
130 FTeid,
131 UeIp,
132 ApplyAction,
133 Interface,
134 Text,
135 Recovery,
136 OuterHeaderCreation,
137 OuterHeaderRemoval,
138 Hex,
139}
140
141const IES: &[(u16, &str, K)] = &[
142 (1, "create_pdr", K::Grouped),
143 (2, "pdi", K::Grouped),
144 (3, "create_far", K::Grouped),
145 (4, "forwarding_parameters", K::Grouped),
146 (5, "duplicating_parameters", K::Grouped),
147 (6, "create_urr", K::Grouped),
148 (7, "create_qer", K::Grouped),
149 (8, "created_pdr", K::Grouped),
150 (9, "update_pdr", K::Grouped),
151 (10, "update_far", K::Grouped),
152 (11, "update_forwarding_parameters", K::Grouped),
153 (12, "update_bar_session_report_response", K::Grouped),
154 (13, "update_urr", K::Grouped),
155 (14, "update_qer", K::Grouped),
156 (15, "remove_pdr", K::Grouped),
157 (16, "remove_far", K::Grouped),
158 (17, "remove_urr", K::Grouped),
159 (18, "remove_qer", K::Grouped),
160 (19, "cause", K::Cause),
161 (20, "source_interface", K::Interface),
162 (21, "f_teid", K::FTeid),
163 (22, "network_instance", K::Text),
164 (23, "sdf_filter", K::Hex),
165 (24, "application_id", K::Text),
166 (25, "gate_status", K::U8),
167 (26, "mbr", K::Hex),
168 (27, "gbr", K::Hex),
169 (28, "qer_correlation_id", K::U32),
170 (29, "precedence", K::U32),
171 (37, "measurement_method", K::U8),
172 (39, "report_type", K::U8),
173 (40, "offending_ie", K::U16),
174 (42, "destination_interface", K::Interface),
175 (43, "up_function_features", K::Hex),
176 (44, "apply_action", K::ApplyAction),
177 (54, "downlink_data_service_information", K::Hex),
178 (56, "pdr_id", K::U16),
179 (57, "f_seid", K::FSeid),
180 (60, "node_id", K::NodeId),
181 (81, "urr_id", K::U32),
182 (83, "downlink_data_report", K::Grouped),
183 (84, "outer_header_creation", K::OuterHeaderCreation),
184 (85, "create_bar", K::Grouped),
185 (86, "update_bar", K::Grouped),
186 (87, "remove_bar", K::Grouped),
187 (88, "bar_id", K::U8),
188 (89, "cp_function_features", K::Hex),
189 (93, "ue_ip_address", K::UeIp),
190 (95, "outer_header_removal", K::OuterHeaderRemoval),
191 (96, "recovery_time_stamp", K::Recovery),
192 (108, "far_id", K::U32),
193 (109, "qer_id", K::U32),
194 (111, "pdn_type", K::U8),
195 (114, "failed_rule_id", K::Hex),
196 (124, "qfi", K::U8),
197 (155, "network_instance_dnn", K::Text),
198];
199
200fn by_type(t: u16) -> Option<(&'static str, K)> {
201 IES.iter()
202 .find(|(n, _, _)| *n == t)
203 .map(|(_, s, k)| (*s, *k))
204}
205
206fn by_name(name: &str) -> Result<(u16, K)> {
207 if let Some((t, _, k)) = IES.iter().find(|(_, s, _)| *s == name) {
208 return Ok((*t, *k));
209 }
210 if let Some(n) = name.strip_prefix("ie_").and_then(|n| n.parse().ok()) {
211 return Ok((n, K::Hex));
212 }
213 bail!("{name:?} is not a PFCP IE NetGet knows; give unknown ones as ie_<type> with {{\"hex\": ...}}")
214}
215
216const INTERFACES: &[&str] = &[
217 "access",
218 "core",
219 "sgi_lan_n6_lan",
220 "cp_function",
221 "5g_vn_internal",
222];
223const APPLY: &[&str] = &[
224 "DROP", "FORW", "BUFF", "NOCP", "DUPL", "IPMA", "IPMD", "DFRT", "EDRT", "BDPN", "DDPN",
225];
226const OHC: &[&str] = &[
227 "gtpu_udp_ipv4",
228 "gtpu_udp_ipv6",
229 "udp_ipv4",
230 "udp_ipv6",
231 "ipv4",
232 "ipv6",
233 "c_tag",
234 "s_tag",
235];
236
237#[derive(Debug, Clone, PartialEq)]
238pub struct Header {
239 pub message_type: u8,
240 pub seid: Option<u64>,
241 pub sequence: u32,
242}
243
244#[derive(Debug, Clone)]
245pub struct Message {
246 pub header: Header,
247 pub ies: Value,
249}
250
251pub fn parse(b: &[u8]) -> Result<Message> {
253 ensure!(b.len() <= MAX_DATAGRAM, "a {}-byte datagram", b.len());
254 ensure!(
255 b.len() >= 8,
256 "a {}-byte datagram is shorter than a PFCP header",
257 b.len()
258 );
259 let version = b[0] >> 5;
260 ensure!(version == 1, "PFCP version {version}");
261 let s = b[0] & 0x01 != 0;
262 let message_type = b[1];
263 let length = u16::from_be_bytes([b[2], b[3]]) as usize;
264 ensure!(
265 4 + length <= b.len(),
266 "Message Length {length} runs past the datagram"
267 );
268 let body = &b[4..4 + length];
269 let (seid, rest) = if s {
270 ensure!(body.len() >= 12, "a truncated header");
271 (Some(u64::from_be_bytes(body[..8].try_into()?)), &body[8..])
272 } else {
273 ensure!(body.len() >= 4, "a truncated header");
274 (None, body)
275 };
276 let sequence = u32::from_be_bytes([0, rest[0], rest[1], rest[2]]);
277 let mut count = 0;
278 let ies = decode_ies(&rest[4..], 0, &mut count)?;
279 Ok(Message {
280 header: Header {
281 message_type,
282 seid,
283 sequence,
284 },
285 ies,
286 })
287}
288
289fn decode_ies(mut b: &[u8], depth: usize, count: &mut usize) -> Result<Value> {
290 ensure!(
291 depth < MAX_DEPTH,
292 "grouped IEs nest more than {MAX_DEPTH} deep"
293 );
294 let mut out = Map::new();
295 let mut repeated = std::collections::HashSet::new();
296 while !b.is_empty() {
297 ensure!(b.len() >= 4, "a truncated IE header");
298 let t = u16::from_be_bytes([b[0], b[1]]);
299 let len = u16::from_be_bytes([b[2], b[3]]) as usize;
300 ensure!(4 + len <= b.len(), "IE {t} runs past its container");
301 *count += 1;
302 ensure!(*count <= MAX_IES, "more than {MAX_IES} IEs");
303 let v = &b[4..4 + len];
305 let (name, value) = match by_type(t) {
306 Some((name, kind)) if t & 0x8000 == 0 => (
307 name.to_string(),
308 decode_value(kind, v, depth, count).with_context(|| format!("IE {name}"))?,
309 ),
310 _ => (format!("ie_{t}"), json!({"hex": hex::encode(v)})),
311 };
312 match out.get_mut(&name) {
313 None => {
314 out.insert(name, value);
315 }
316 Some(existing) => {
317 if repeated.contains(&name) {
319 if let Value::Array(list) = existing {
320 list.push(value);
321 }
322 } else {
323 let first = existing.take();
324 *existing = Value::Array(vec![first, value]);
325 repeated.insert(name);
326 }
327 }
328 }
329 b = &b[4 + len..];
330 }
331 Ok(Value::Object(out))
332}
333
334fn ip4(b: &[u8]) -> Result<Ipv4Addr> {
335 ensure!(b.len() >= 4, "a truncated IPv4 address");
336 Ok(Ipv4Addr::new(b[0], b[1], b[2], b[3]))
337}
338
339fn ip6(b: &[u8]) -> Result<Ipv6Addr> {
340 ensure!(b.len() >= 16, "a truncated IPv6 address");
341 let a: [u8; 16] = b[..16].try_into()?;
342 Ok(Ipv6Addr::from(a))
343}
344
345fn flags(bits: u32, names: &[&str]) -> Vec<String> {
346 names
347 .iter()
348 .enumerate()
349 .filter(|(i, _)| bits & (1 << i) != 0)
350 .map(|(_, n)| n.to_string())
351 .collect()
352}
353
354fn text(v: &[u8]) -> String {
356 let mut labels = Vec::new();
357 let mut i = 0;
358 while i < v.len() {
359 let n = v[i] as usize;
360 if n == 0
361 || i + 1 + n > v.len()
362 || !v[i + 1..i + 1 + n].iter().all(|c| c.is_ascii_graphic())
363 {
364 return String::from_utf8_lossy(v).into_owned();
365 }
366 labels.push(String::from_utf8_lossy(&v[i + 1..i + 1 + n]).into_owned());
367 i += 1 + n;
368 }
369 if labels.len() > 1 {
370 labels.join(".")
371 } else {
372 String::from_utf8_lossy(v).into_owned()
373 }
374}
375
376fn decode_value(kind: K, v: &[u8], depth: usize, count: &mut usize) -> Result<Value> {
377 let need = |n: usize| -> Result<()> {
378 ensure!(v.len() >= n, "{} bytes where {n} are needed", v.len());
379 Ok(())
380 };
381 Ok(match kind {
382 K::Grouped => decode_ies(v, depth + 1, count)?,
383 K::U8 => {
384 need(1)?;
385 json!(v[0])
386 }
387 K::U16 => {
388 need(2)?;
389 json!(u16::from_be_bytes([v[0], v[1]]))
390 }
391 K::U32 => {
392 need(4)?;
393 json!(u32::from_be_bytes(v[..4].try_into()?))
394 }
395 K::Cause => {
396 need(1)?;
397 json!(cause_name(v[0]))
398 }
399 K::NodeId => {
400 need(1)?;
401 match v[0] & 0x0f {
402 0 => json!({"ipv4": ip4(&v[1..])?.to_string()}),
403 1 => json!({"ipv6": ip6(&v[1..])?.to_string()}),
404 2 => json!({"fqdn": text(&v[1..])}),
405 other => bail!("Node ID type {other}"),
406 }
407 }
408 K::FSeid => {
409 need(9)?;
410 let mut o = json!({"seid": u64::from_be_bytes(v[1..9].try_into()?)});
411 let mut at = 9;
412 if v[0] & 0x02 != 0 {
413 o["ipv4"] = json!(ip4(&v[at..])?.to_string());
414 at += 4;
415 }
416 if v[0] & 0x01 != 0 {
417 o["ipv6"] = json!(ip6(&v[at..])?.to_string());
418 }
419 o
420 }
421 K::FTeid => {
422 need(1)?;
423 let f = v[0];
424 if f & 0x04 != 0 {
425 let mut o = json!({"choose": true, "ipv4": f & 0x01 != 0, "ipv6": f & 0x02 != 0});
427 if f & 0x08 != 0 {
428 need(2)?;
429 o["choose_id"] = json!(v[1]);
430 }
431 o
432 } else {
433 need(5)?;
434 let mut o = json!({"teid": u32::from_be_bytes(v[1..5].try_into()?)});
435 let mut at = 5;
436 if f & 0x01 != 0 {
437 o["ipv4"] = json!(ip4(&v[at..])?.to_string());
438 at += 4;
439 }
440 if f & 0x02 != 0 {
441 o["ipv6"] = json!(ip6(&v[at..])?.to_string());
442 }
443 o
444 }
445 }
446 K::UeIp => {
447 need(1)?;
448 let f = v[0];
449 let mut o = json!({"direction": if f & 0x04 != 0 { "destination" } else { "source" }});
450 let mut at = 1;
451 if f & 0x02 != 0 {
452 o["ipv4"] = json!(ip4(&v[at..])?.to_string());
453 at += 4;
454 }
455 if f & 0x01 != 0 {
456 o["ipv6"] = json!(ip6(&v[at..])?.to_string());
457 }
458 o
459 }
460 K::ApplyAction => {
461 need(1)?;
462 let bits = u32::from(v[0]) | v.get(1).map_or(0, |b| u32::from(*b) << 8);
463 json!(flags(bits, APPLY))
464 }
465 K::Interface => {
466 need(1)?;
467 let i = (v[0] & 0x0f) as usize;
468 json!(INTERFACES
469 .get(i)
470 .map(|s| s.to_string())
471 .unwrap_or_else(|| format!("interface_{i}")))
472 }
473 K::Text => json!(text(v)),
474 K::Recovery => {
475 need(4)?;
476 let ntp = u64::from(u32::from_be_bytes(v[..4].try_into()?));
477 json!(ntp.saturating_sub(NTP_UNIX))
478 }
479 K::OuterHeaderCreation => {
480 need(2)?;
481 let d = v[0];
484 let mut o = json!({"description": flags(u32::from(d), OHC)});
485 let mut at = 2;
486 if d & 0x03 != 0 {
487 need(at + 4)?;
488 o["teid"] = json!(u32::from_be_bytes(v[at..at + 4].try_into()?));
489 at += 4;
490 }
491 if d & 0x15 != 0 {
492 o["ipv4"] = json!(ip4(&v[at..])?.to_string());
493 at += 4;
494 }
495 if d & 0x2a != 0 {
496 o["ipv6"] = json!(ip6(&v[at..])?.to_string());
497 at += 16;
498 }
499 if d & 0x0c != 0 {
500 need(at + 2)?;
501 o["port"] = json!(u16::from_be_bytes([v[at], v[at + 1]]));
502 }
503 o
504 }
505 K::OuterHeaderRemoval => {
506 need(1)?;
507 json!(v[0])
508 }
509 K::Hex => json!({"hex": hex::encode(v)}),
510 })
511}
512
513pub fn encode(message_type: u8, seid: Option<u64>, sequence: u32, ies: &Value) -> Result<Vec<u8>> {
515 let mut body = Vec::new();
516 encode_ies(ies, &mut body, 0)?;
517 let mut out = Vec::with_capacity(16 + body.len());
518 out.push((1 << 5) | u8::from(seid.is_some()));
519 out.push(message_type);
520 let len = body.len() + 4 + if seid.is_some() { 8 } else { 0 };
521 ensure!(
522 len <= u16::MAX as usize,
523 "a {len}-byte message is too long for PFCP"
524 );
525 out.extend_from_slice(&(len as u16).to_be_bytes());
526 if let Some(s) = seid {
527 out.extend_from_slice(&s.to_be_bytes());
528 }
529 out.extend_from_slice(&sequence.to_be_bytes()[1..]);
530 out.push(0);
531 out.extend_from_slice(&body);
532 ensure!(
533 out.len() <= MAX_DATAGRAM,
534 "the message is too large for one datagram"
535 );
536 Ok(out)
537}
538
539fn encode_ies(ies: &Value, out: &mut Vec<u8>, depth: usize) -> Result<()> {
540 ensure!(
541 depth < MAX_DEPTH,
542 "grouped IEs nest more than {MAX_DEPTH} deep"
543 );
544 let map = match ies {
545 Value::Object(m) => m,
546 Value::Null => return Ok(()),
547 _ => bail!("IEs are an object keyed by IE name"),
548 };
549 for (name, value) in map {
550 let (t, kind) = by_name(name)?;
551 let repeated = match (kind, value) {
554 (K::ApplyAction, Value::Array(list)) => list.iter().all(Value::is_array),
555 (_, Value::Array(_)) => true,
556 _ => false,
557 };
558 let items: Vec<&Value> = match value {
559 Value::Array(list) if repeated => list.iter().collect(),
560 other => vec![other],
561 };
562 for item in items {
563 let mut v = Vec::new();
564 encode_value(kind, item, &mut v, depth).with_context(|| format!("IE {name}"))?;
565 ensure!(v.len() <= u16::MAX as usize, "IE {name} is too long");
566 out.extend_from_slice(&t.to_be_bytes());
567 out.extend_from_slice(&(v.len() as u16).to_be_bytes());
568 out.extend_from_slice(&v);
569 }
570 }
571 Ok(())
572}
573
574fn num(v: &Value) -> Result<u64> {
575 v.as_u64().context("a whole number")
576}
577
578fn parse_ip4(v: &Value) -> Result<Ipv4Addr> {
579 v.as_str()
580 .context("an IPv4 address string")?
581 .parse()
582 .context("an IPv4 address")
583}
584
585fn parse_ip6(v: &Value) -> Result<Ipv6Addr> {
586 v.as_str()
587 .context("an IPv6 address string")?
588 .parse()
589 .context("an IPv6 address")
590}
591
592fn flag_bits(v: &Value, names: &[&str]) -> Result<u32> {
593 let list: Vec<&str> = match v {
594 Value::String(s) => vec![s.as_str()],
595 Value::Array(a) => a
596 .iter()
597 .map(|x| x.as_str().context("flag names are strings"))
598 .collect::<Result<_>>()?,
599 _ => bail!("one of {} or a list of them", names.join(", ")),
600 };
601 let mut bits = 0;
602 for f in list {
603 let i = names
604 .iter()
605 .position(|n| n.eq_ignore_ascii_case(f))
606 .with_context(|| format!("{f:?} is not one of {}", names.join(", ")))?;
607 bits |= 1 << i;
608 }
609 Ok(bits)
610}
611
612fn encode_value(kind: K, v: &Value, out: &mut Vec<u8>, depth: usize) -> Result<()> {
613 match kind {
614 K::Grouped => encode_ies(v, out, depth + 1)?,
615 K::U8 => out.push(u8::try_from(num(v)?).context("0-255")?),
616 K::U16 => out.extend_from_slice(&u16::try_from(num(v)?).context("0-65535")?.to_be_bytes()),
617 K::U32 => out.extend_from_slice(
618 &u32::try_from(num(v)?)
619 .context("a 32-bit number")?
620 .to_be_bytes(),
621 ),
622 K::Cause => {
623 let c = match v {
624 Value::String(s) => cause_code(s)?,
625 n => u8::try_from(num(n)?).context("0-255")?,
626 };
627 out.push(c);
628 }
629 K::NodeId => {
630 if let Some(a) = v.get("ipv4") {
631 out.push(0);
632 out.extend_from_slice(&parse_ip4(a)?.octets());
633 } else if let Some(a) = v.get("ipv6") {
634 out.push(1);
635 out.extend_from_slice(&parse_ip6(a)?.octets());
636 } else if let Some(f) = v.get("fqdn").and_then(Value::as_str) {
637 out.push(2);
638 for label in f.split('.').filter(|l| !l.is_empty()) {
639 ensure!(label.len() < 64, "an FQDN label is longer than 63 bytes");
640 out.push(label.len() as u8);
641 out.extend_from_slice(label.as_bytes());
642 }
643 } else {
644 bail!("node_id is {{\"ipv4\": …}}, {{\"ipv6\": …}} or {{\"fqdn\": …}}");
645 }
646 }
647 K::FSeid => {
648 let (v4, v6) = (v.get("ipv4"), v.get("ipv6"));
649 out.push(u8::from(v4.is_some()) << 1 | u8::from(v6.is_some()));
650 out.extend_from_slice(&num(v.get("seid").context("f_seid needs seid")?)?.to_be_bytes());
651 if let Some(a) = v4 {
652 out.extend_from_slice(&parse_ip4(a)?.octets());
653 }
654 if let Some(a) = v6 {
655 out.extend_from_slice(&parse_ip6(a)?.octets());
656 }
657 }
658 K::FTeid => {
659 if v.get("choose").and_then(Value::as_bool) == Some(true) {
660 let v4 = v.get("ipv4").and_then(Value::as_bool).unwrap_or(true);
661 let v6 = v.get("ipv6").and_then(Value::as_bool).unwrap_or(false);
662 let chid = v.get("choose_id").map(num).transpose()?;
663 out.push(0x04 | u8::from(v4) | u8::from(v6) << 1 | u8::from(chid.is_some()) << 3);
664 if let Some(id) = chid {
665 out.push(u8::try_from(id).context("choose_id is 0-255")?);
666 }
667 } else {
668 let (v4, v6) = (v.get("ipv4"), v.get("ipv6"));
669 out.push(u8::from(v4.is_some()) | u8::from(v6.is_some()) << 1);
670 out.extend_from_slice(
671 &u32::try_from(num(v
672 .get("teid")
673 .context("f_teid needs teid (or choose: true)")?)?)?
674 .to_be_bytes(),
675 );
676 if let Some(a) = v4 {
677 out.extend_from_slice(&parse_ip4(a)?.octets());
678 }
679 if let Some(a) = v6 {
680 out.extend_from_slice(&parse_ip6(a)?.octets());
681 }
682 }
683 }
684 K::UeIp => {
685 let (v4, v6) = (v.get("ipv4"), v.get("ipv6"));
686 let dest = v.get("direction").and_then(Value::as_str) == Some("destination");
687 out.push(u8::from(dest) << 2 | u8::from(v4.is_some()) << 1 | u8::from(v6.is_some()));
688 if let Some(a) = v4 {
689 out.extend_from_slice(&parse_ip4(a)?.octets());
690 }
691 if let Some(a) = v6 {
692 out.extend_from_slice(&parse_ip6(a)?.octets());
693 }
694 }
695 K::ApplyAction => {
696 let bits = flag_bits(v, APPLY)?;
697 out.push(bits as u8);
698 if bits > 0xff {
699 out.push((bits >> 8) as u8);
700 }
701 }
702 K::Interface => {
703 let i = match v {
704 Value::String(s) => INTERFACES.iter().position(|n| n == s).with_context(|| {
705 format!("interface {s:?} is not one of {}", INTERFACES.join(", "))
706 })?,
707 n => num(n)? as usize,
708 };
709 out.push(u8::try_from(i).context("an interface number")? & 0x0f);
710 }
711 K::Text => out.extend_from_slice(v.as_str().context("text")?.as_bytes()),
712 K::Recovery => {
713 let unix = num(v)?;
714 out.extend_from_slice(&((unix + NTP_UNIX) as u32).to_be_bytes());
715 }
716 K::OuterHeaderCreation => {
717 let d = flag_bits(
718 v.get("description")
719 .context("outer_header_creation needs description")?,
720 OHC,
721 )? as u8;
722 ensure!(
723 d & 0xc0 == 0,
724 "C-TAG and S-TAG outer headers are not supported"
725 );
726 out.extend_from_slice(&[d, 0]);
727 if d & 0x03 != 0 {
728 out.extend_from_slice(
729 &u32::try_from(num(v.get("teid").context("GTP-U needs teid")?)?)?.to_be_bytes(),
730 );
731 }
732 if d & 0x15 != 0 {
733 out.extend_from_slice(
734 &parse_ip4(v.get("ipv4").context("an IPv4 outer header needs ipv4")?)?.octets(),
735 );
736 }
737 if d & 0x2a != 0 {
738 out.extend_from_slice(
739 &parse_ip6(v.get("ipv6").context("an IPv6 outer header needs ipv6")?)?.octets(),
740 );
741 }
742 if d & 0x0c != 0 {
743 out.extend_from_slice(
744 &u16::try_from(num(v.get("port").context("UDP needs port")?)?)?.to_be_bytes(),
745 );
746 }
747 }
748 K::OuterHeaderRemoval => out.push(u8::try_from(num(v)?).context("0-255")?),
749 K::Hex => {
750 let h = v
751 .get("hex")
752 .and_then(Value::as_str)
753 .or(v.as_str())
754 .context("give this IE as {\"hex\": \"…\"}")?;
755 out.extend(hex::decode(h).context("not hex")?);
756 }
757 }
758 Ok(())
759}
760
761pub fn now_unix() -> u64 {
763 crate::utils::clock::SystemTime::now()
764 .duration_since(crate::utils::clock::UNIX_EPOCH)
765 .map(|d| d.as_secs())
766 .unwrap_or(0)
767}