1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
//! Linked URR ID IE implementation.
//!
//! Per 3GPP TS 29.244 Section 8.2.83, the Linked URR ID IE is used to
//! indicate one or more URRs to which the current URR is linked.
use crate::error::PfcpError;
use crate::ie::{Ie, IeType};
/// Linked URR ID Information Element.
///
/// Used to link Usage Reporting Rules together. When a URR is linked to
/// another URR, usage reports from one may trigger actions on the other.
///
/// # Structure (per 3GPP TS 29.244)
/// ```text
/// +-----------+-----------+-----------+-----------+
/// | Linked URR ID (32 bits) |
/// +-----------+-----------+-----------+-----------+
/// ```
///
/// # Example
/// ```
/// use rs_pfcp::ie::linked_urr_id::LinkedUrrId;
///
/// let linked_urr = LinkedUrrId::new(42);
/// let marshaled = linked_urr.marshal();
/// let unmarshaled = LinkedUrrId::unmarshal(&marshaled).unwrap();
/// assert_eq!(linked_urr, unmarshaled);
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct LinkedUrrId {
/// The URR ID that this URR is linked to
pub id: u32,
}
impl LinkedUrrId {
/// Creates a new Linked URR ID.
///
/// # Arguments
/// * `id` - The URR ID to link to (u32)
///
/// # Example
/// ```
/// use rs_pfcp::ie::linked_urr_id::LinkedUrrId;
///
/// let linked_urr = LinkedUrrId::new(1);
/// assert_eq!(linked_urr.id, 1);
/// ```
pub fn new(id: u32) -> Self {
LinkedUrrId { id }
}
/// Marshals the Linked URR ID into a byte vector.
///
/// # Returns
/// A 4-byte vector in big-endian (network byte order).
pub fn marshal(&self) -> Vec<u8> {
self.id.to_be_bytes().to_vec()
}
/// Unmarshals a byte slice into a Linked URR ID.
///
/// # Arguments
/// * `payload` - Byte slice containing the Linked URR ID
///
/// # Returns
/// A `LinkedUrrId` instance or an error if unmarshaling fails.
///
/// # Errors
/// Returns `PfcpError` if:
/// - The buffer is too short (< 4 bytes)
///
/// # Example
/// ```
/// use rs_pfcp::ie::linked_urr_id::LinkedUrrId;
///
/// let data = vec![0x00, 0x00, 0x00, 0x2A]; // 42 in big-endian
/// let linked_urr = LinkedUrrId::unmarshal(&data).unwrap();
/// assert_eq!(linked_urr.id, 42);
/// ```
pub fn unmarshal(payload: &[u8]) -> Result<Self, PfcpError> {
if payload.len() < 4 {
return Err(PfcpError::invalid_length(
"Linked URR ID",
IeType::LinkedUrrId,
4,
payload.len(),
));
}
Ok(LinkedUrrId {
id: u32::from_be_bytes([payload[0], payload[1], payload[2], payload[3]]),
})
}
/// Wraps the Linked URR ID in a generic IE.
///
/// # Returns
/// An `Ie` with type `LinkedUrrId` and the marshaled payload.
pub fn to_ie(&self) -> Ie {
Ie::new(IeType::LinkedUrrId, self.marshal())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_linked_urr_id_new() {
let linked_urr = LinkedUrrId::new(42);
assert_eq!(linked_urr.id, 42);
}
#[test]
fn test_linked_urr_id_marshal_unmarshal() {
let linked_urr = LinkedUrrId::new(0x12345678);
let marshaled = linked_urr.marshal();
assert_eq!(marshaled, vec![0x12, 0x34, 0x56, 0x78]);
let unmarshaled = LinkedUrrId::unmarshal(&marshaled).unwrap();
assert_eq!(unmarshaled.id, 0x12345678);
}
#[test]
fn test_linked_urr_id_unmarshal_empty() {
let result = LinkedUrrId::unmarshal(&[]);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(matches!(err, PfcpError::InvalidLength { .. }));
assert!(err.to_string().contains("Linked URR ID"));
assert!(err.to_string().contains("4"));
assert!(err.to_string().contains("0"));
}
#[test]
fn test_linked_urr_id_unmarshal_too_short() {
// Test with 1, 2, and 3 bytes
for len in 1..4 {
let data = vec![0xFF; len];
let result = LinkedUrrId::unmarshal(&data);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(matches!(err, PfcpError::InvalidLength { .. }));
assert!(err.to_string().contains("Linked URR ID"));
assert!(err.to_string().contains("4"));
assert!(err.to_string().contains(&len.to_string()));
}
}
#[test]
fn test_linked_urr_id_round_trip() {
let test_ids = vec![0, 1, 0xFFFFFFFF, 0x12345678, 0xABCDEF00];
for id in test_ids {
let linked_urr = LinkedUrrId::new(id);
let marshaled = linked_urr.marshal();
let unmarshaled = LinkedUrrId::unmarshal(&marshaled).unwrap();
assert_eq!(unmarshaled.id, id);
}
}
#[test]
fn test_linked_urr_id_to_ie() {
let linked_urr = LinkedUrrId::new(0x11223344);
let ie = linked_urr.to_ie();
assert_eq!(ie.ie_type, IeType::LinkedUrrId);
assert_eq!(ie.payload, vec![0x11, 0x22, 0x33, 0x44]);
let unmarshaled = LinkedUrrId::unmarshal(&ie.payload).unwrap();
assert_eq!(unmarshaled.id, 0x11223344);
}
#[test]
fn test_linked_urr_id_edge_cases() {
// Zero value
let linked_urr = LinkedUrrId::new(0);
let marshaled = linked_urr.marshal();
let unmarshaled = LinkedUrrId::unmarshal(&marshaled).unwrap();
assert_eq!(unmarshaled.id, 0);
// Max value (u32::MAX = 4294967295)
let linked_urr = LinkedUrrId::new(u32::MAX);
let marshaled = linked_urr.marshal();
let unmarshaled = LinkedUrrId::unmarshal(&marshaled).unwrap();
assert_eq!(unmarshaled.id, u32::MAX);
}
#[test]
fn test_linked_urr_id_copy_trait() {
let linked_urr1 = LinkedUrrId::new(100);
let linked_urr2 = linked_urr1; // Should copy, not move
assert_eq!(linked_urr1.id, linked_urr2.id);
assert_eq!(linked_urr1, linked_urr2);
}
}