use crate::error::PfcpError;
use crate::ie::{Ie, IeType};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HeaderType {
HttpHeaderField = 0,
HttpUrlUri = 1,
}
impl HeaderType {
fn from_u8(value: u8) -> Result<Self, PfcpError> {
match value {
0 => Ok(HeaderType::HttpHeaderField),
1 => Ok(HeaderType::HttpUrlUri),
_ => Err(PfcpError::invalid_value(
"Header Type",
value.to_string(),
"must be 0 (HttpHeaderField) or 1 (HttpUrlUri)",
)),
}
}
fn to_u8(self) -> u8 {
self as u8
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HeaderEnrichment {
pub header_type: HeaderType,
pub name: String, pub value: String, }
impl HeaderEnrichment {
pub fn new(header_type: HeaderType, name: String, value: String) -> Self {
HeaderEnrichment {
header_type,
name,
value,
}
}
pub fn http_header(name: String, value: String) -> Self {
HeaderEnrichment {
header_type: HeaderType::HttpHeaderField,
name,
value,
}
}
pub fn http_url(prefix: String, suffix: String) -> Self {
HeaderEnrichment {
header_type: HeaderType::HttpUrlUri,
name: prefix,
value: suffix,
}
}
pub fn marshal(&self) -> Vec<u8> {
let mut data = Vec::new();
data.push(self.header_type.to_u8());
data.push(0);
let name_bytes = self.name.as_bytes();
if name_bytes.len() > 255 {
data.push(255);
data.extend_from_slice(&name_bytes[..255]);
} else {
data.push(name_bytes.len() as u8);
data.extend_from_slice(name_bytes);
}
let value_bytes = self.value.as_bytes();
if value_bytes.len() > 255 {
data.push(255);
data.extend_from_slice(&value_bytes[..255]);
} else {
data.push(value_bytes.len() as u8);
data.extend_from_slice(value_bytes);
}
data
}
pub fn unmarshal(payload: &[u8]) -> Result<Self, PfcpError> {
if payload.len() < 4 {
return Err(PfcpError::invalid_length(
"Header Enrichment",
IeType::HeaderEnrichment,
4,
payload.len(),
));
}
let mut offset = 0;
let header_type = HeaderType::from_u8(payload[offset])?;
offset += 1;
offset += 1;
let name_len = payload[offset] as usize;
offset += 1;
if offset + name_len > payload.len() {
return Err(PfcpError::invalid_length(
"Header Enrichment (name)",
IeType::HeaderEnrichment,
offset + name_len,
payload.len(),
));
}
let name =
String::from_utf8(payload[offset..offset + name_len].to_vec()).map_err(|_| {
PfcpError::invalid_value("Header Enrichment name", "bytes", "not valid UTF-8")
})?;
offset += name_len;
if offset >= payload.len() {
return Err(PfcpError::invalid_length(
"Header Enrichment (value length)",
IeType::HeaderEnrichment,
offset + 1,
payload.len(),
));
}
let value_len = payload[offset] as usize;
offset += 1;
if offset + value_len > payload.len() {
return Err(PfcpError::invalid_length(
"Header Enrichment (value)",
IeType::HeaderEnrichment,
offset + value_len,
payload.len(),
));
}
let value =
String::from_utf8(payload[offset..offset + value_len].to_vec()).map_err(|_| {
PfcpError::invalid_value("Header Enrichment value", "bytes", "not valid UTF-8")
})?;
Ok(HeaderEnrichment {
header_type,
name,
value,
})
}
pub fn to_ie(&self) -> Ie {
Ie::new(IeType::HeaderEnrichment, self.marshal())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_header_enrichment_http_header() {
let enrichment =
HeaderEnrichment::http_header("X-Custom-Header".to_string(), "value123".to_string());
assert_eq!(enrichment.header_type, HeaderType::HttpHeaderField);
assert_eq!(enrichment.name, "X-Custom-Header");
assert_eq!(enrichment.value, "value123");
let marshaled = enrichment.marshal();
let unmarshaled = HeaderEnrichment::unmarshal(&marshaled).unwrap();
assert_eq!(enrichment, unmarshaled);
}
#[test]
fn test_header_enrichment_http_url() {
let enrichment = HeaderEnrichment::http_url(
"https://example.com/path".to_string(),
"?param=value".to_string(),
);
assert_eq!(enrichment.header_type, HeaderType::HttpUrlUri);
assert_eq!(enrichment.name, "https://example.com/path");
assert_eq!(enrichment.value, "?param=value");
let marshaled = enrichment.marshal();
let unmarshaled = HeaderEnrichment::unmarshal(&marshaled).unwrap();
assert_eq!(enrichment, unmarshaled);
}
#[test]
fn test_header_enrichment_empty_strings() {
let enrichment =
HeaderEnrichment::new(HeaderType::HttpHeaderField, String::new(), String::new());
let marshaled = enrichment.marshal();
let unmarshaled = HeaderEnrichment::unmarshal(&marshaled).unwrap();
assert_eq!(enrichment, unmarshaled);
}
#[test]
fn test_header_enrichment_unicode() {
let enrichment =
HeaderEnrichment::http_header("X-Unicode-Header".to_string(), "值123".to_string());
let marshaled = enrichment.marshal();
let unmarshaled = HeaderEnrichment::unmarshal(&marshaled).unwrap();
assert_eq!(enrichment, unmarshaled);
}
#[test]
fn test_header_enrichment_to_ie() {
let enrichment =
HeaderEnrichment::http_header("X-Test".to_string(), "test-value".to_string());
let ie = enrichment.to_ie();
assert_eq!(ie.ie_type, IeType::HeaderEnrichment);
let unmarshaled = HeaderEnrichment::unmarshal(&ie.payload).unwrap();
assert_eq!(enrichment, unmarshaled);
}
#[test]
fn test_header_type_conversion() {
assert_eq!(HeaderType::from_u8(0).unwrap(), HeaderType::HttpHeaderField);
assert_eq!(HeaderType::from_u8(1).unwrap(), HeaderType::HttpUrlUri);
assert!(HeaderType::from_u8(2).is_err());
}
#[test]
fn test_header_enrichment_unmarshal_invalid_utf8() {
let mut data = vec![0, 0, 3]; data.extend_from_slice(&[0xFF, 0xFE, 0xFD]); data.push(0);
let result = HeaderEnrichment::unmarshal(&data);
assert!(result.is_err());
}
}