use std::{borrow::Cow, fmt, net::IpAddr, num::NonZero, time::Duration};
use chrono::{Local, SecondsFormat};
use crate::
{
ATSIGN, CBRACE, EQSIGN, IANA_PRIV_ENT_NUM, NILVALUE, OBRACE, Priority, QCHAR, SyslogMsgPriFac, WSPACE, check_printable, error::SyRes, escape_chars, map_error, portable, throw_error, truncate, truncate_n
};
use super::{SyslogFormatted, SyslogFormatter};
pub trait SdIdIntrf: fmt::Display
{
const SD_ID_TEXT: &'static str;
fn format_msg(&self) -> String;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SdIdTimeQuality(String);
impl fmt::Display for SdIdTimeQuality
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
write!(f, "{} {}", Self::SD_ID_TEXT, self.0)
}
}
impl SdIdIntrf for SdIdTimeQuality
{
const SD_ID_TEXT: &'static str = "timeQuality";
fn format_msg(&self) -> String
{
return self.0.clone();
}
}
impl SdIdTimeQuality
{
pub
fn new(tz_known: bool, is_synced: bool, sync_acc: Option<u64>) -> Self
{
if let Some(sa) = sync_acc
{
let msg =
[
Self::SD_ID_TEXT, WSPACE,
"tzKnown", "=", QCHAR, tz_known.to_string().as_str(), QCHAR, WSPACE,
"isSynced", "=", QCHAR, is_synced.to_string().as_str(), QCHAR, WSPACE,
"syncAccuracy", "=", QCHAR, sa.to_string().as_str(), QCHAR,
]
.concat();
return Self(msg);
}
else
{
let msg =
[
Self::SD_ID_TEXT, WSPACE,
"tzKnown", "=", QCHAR, tz_known.to_string().as_str(), QCHAR, WSPACE,
"isSynced", "=", QCHAR, is_synced.to_string().as_str(), QCHAR,
]
.concat();
return Self(msg);
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SdIdOrigin(String);
impl fmt::Display for SdIdOrigin
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
write!(f, "{} {}", Self::SD_ID_TEXT, self.0)
}
}
impl SdIdIntrf for SdIdOrigin
{
const SD_ID_TEXT: &'static str = "origin";
fn format_msg(&self) -> String
{
return self.0.clone();
}
}
impl SdIdOrigin
{
pub
fn new(ips: Vec<IpAddr>, enterprise_id: Option<&str>, software: Option<&str>, sw_version: Option<&str>) -> Self
{
let mut items: Vec<String> =
Vec::with_capacity(ips.len() + 1 + enterprise_id.map_or(0, |_| 1) +
software.map_or(0, |_| 1) + sw_version.map_or(0, |_| 1));
items.push(Self::SD_ID_TEXT.to_string());
ips.iter().for_each(|ip| items.push(["ip=", QCHAR, ip.to_string().as_str(), QCHAR].concat()));
if let Some(ent_id) = enterprise_id
{
items.push(["enterpriseId=", QCHAR, ent_id, QCHAR].concat());
if let Some(softw) = software
{
items.push(["software=", QCHAR, softw, QCHAR].concat());
if let Some(sw_ver) = sw_version
{
items.push(["swVersion=", QCHAR, sw_ver, QCHAR].concat());
}
else
{
items.push(["swVersion=", QCHAR, "upspecified", QCHAR].concat());
}
}
else
{
items.push(["software=", QCHAR, "upspecified", QCHAR].concat());
}
}
let ret = Self(items.join(WSPACE));
return ret;
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SdIdMeta(String);
impl fmt::Display for SdIdMeta
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
write!(f, "{} {}", Self::SD_ID_TEXT, self.0)
}
}
impl SdIdIntrf for SdIdMeta
{
const SD_ID_TEXT: &'static str = "meta";
fn format_msg(&self) -> String
{
return self.0.clone();
}
}
impl SdIdMeta
{
pub
fn new(language: &str, msg: &FormatRfc5424) -> Self
{
let uptime = portable::get_uptime().unwrap_or(Duration::from_secs(0));
let msg =
[
Self::SD_ID_TEXT, WSPACE,
"sequenceId", "=", QCHAR, msg.seq.to_string().as_str(), QCHAR, WSPACE,
"sysUpTime", "=", QCHAR, uptime.as_secs().to_string().as_str(), QCHAR, WSPACE,
"language", "=", QCHAR, language, QCHAR,
]
.concat();
return Self(msg);
}
#[cfg(test)]
pub
fn new_test(language: &str, uptime: u64, msg: &FormatRfc5424) -> Self
{
let msg =
[
Self::SD_ID_TEXT, WSPACE,
"sequenceId", "=", QCHAR, msg.seq.to_string().as_str(), QCHAR, WSPACE,
"sysUpTime", "=", QCHAR, uptime.to_string().as_str(), QCHAR, WSPACE,
"language", "=", QCHAR, language, QCHAR,
]
.concat();
return Self(msg);
}
}
#[derive(Debug, Clone)]
pub struct FormatRfc5424Sd
{
vals: Vec<Cow<'static, str>>
}
impl fmt::Display for FormatRfc5424Sd
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
write!(f, "{}", self.format_sd())
}
}
impl FormatRfc5424Sd
{
pub
fn new(sd_id: &'static str, vals_cnt: usize) -> SyRes<Self>
{
if vals_cnt == 0
{
throw_error!("params should present.");
}
if sd_id.contains(ATSIGN) == false
{
throw_error!("SD-ID should contain @, otherwise see pre-defined ID's.");
}
let (name, ent_num) =
sd_id
.split_once(ATSIGN)
.ok_or_else(||
map_error!("incorrect SD-ID format")
)?;
check_printable(name)?;
let ent_num: u64 =
ent_num
.parse()
.map_err(|e|
map_error!("incorrect SD-ID entrprise number format, {}", e)
)?;
if ent_num == IANA_PRIV_ENT_NUM
{
throw_error!("SD-ID entrprise number can not be '{}' because it is private", ent_num);
}
let mut vals: Vec<Cow<'static, str>> = Vec::with_capacity(vals_cnt*3 + 2);
vals.push(Cow::Borrowed(sd_id));
return Ok(Self{ vals: vals });
}
pub
fn push(&mut self, key: Cow<'static, str>, val: Cow<'static, str>) -> SyRes<()>
{
check_printable(&key)?;
let escp_val = escape_chars(val);
self.vals.push( Cow::Owned([WSPACE, key.as_ref(), EQSIGN, QCHAR, escp_val.as_ref(), QCHAR].concat()));
return Ok(());
}
fn format_sd(&self) -> String
{
return self.vals.concat();
}
#[inline]
fn get_sd_id(&self) -> &str
{
return self.vals[0].as_ref();
}
}
#[cfg(target_has_atomic = "64")]
static SEQUENCE_ID: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
#[cfg(not(target_has_atomic = "64"))]
static SEQUENCE_ID: std::sync::Mutex<u32> = std::sync::Mutex::new(0);
#[derive(Debug, Clone)]
pub struct FormatRfc5424
{
seq: NonZero<u32>,
msg: Cow<'static, str>,
msg_id: Cow<'static, str>,
st_data: Vec<(Cow<'static, str>, String)>,
}
impl FormatRfc5424
{
pub
fn new(value: String, seq_num: NonZero<u32>) -> FormatRfc5424
{
return
Self
{
seq: seq_num,
msg: Cow::Owned(value),
msg_id: Cow::Borrowed(NILVALUE),
st_data: Vec::new(),
};
}
#[cfg(target_has_atomic = "64")]
fn generate_sequence_num() -> NonZero<u32>
{
use std::sync::atomic::{Ordering};
let mut last = SEQUENCE_ID.load(Ordering::Relaxed);
loop
{
let seq_id =
match last.checked_add(1)
{
Some(id) => id,
None =>
{
1
}
};
match SEQUENCE_ID.compare_exchange_weak(last, seq_id, Ordering::Relaxed, Ordering::Relaxed)
{
Ok(_) =>
return NonZero::new(seq_id).unwrap(),
Err(id) =>
last = id,
}
}
}
#[cfg(not(target_has_atomic = "64"))]
fn generate_sequence_num() -> NonZero<u32>
{
let mut seq_id_cnt =
SEQUENCE_ID.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
let seq_id =
match seq_id_cnt.checked_add(1)
{
Some(id) => id,
None =>
{
1
}
};
*seq_id_cnt = seq_id;
drop(seq_id_cnt);
return NonZero::new(seq_id).unwrap();
}
#[cfg(test)]
#[cfg(target_has_atomic = "64")]
fn reset_seq_counter()
{
SEQUENCE_ID.store(0, std::sync::atomic::Ordering::Relaxed);
}
#[cfg(test)]
#[cfg(not(target_has_atomic = "64"))]
fn reset_seq_counter()
{
*SEQUENCE_ID.lock().unwrap() = 0;
}
#[inline]
pub
fn add_msg_id_str(&mut self, msg_id: &'static str) -> SyRes<()>
{
check_printable(msg_id)?;
self.msg_id = Cow::Borrowed(msg_id);
return Ok(());
}
#[inline]
pub
fn add_msg_id(&mut self, msg_id: impl Into<String>) -> SyRes<()>
{
let msgid = msg_id.into();
check_printable(&msgid)?;
self.msg_id = Cow::Owned(msgid);
return Ok(());
}
#[inline]
pub
fn add_st_data(&mut self, st_data: FormatRfc5424Sd) -> SyRes<()>
{
if self.st_data.iter().any(|(v, _)| v.as_ref() == st_data.get_sd_id()) == true
{
throw_error!("SD-ID already exists on the list: '{}'", st_data);
}
let items = st_data.format_sd();
self.st_data.push((Cow::Owned(st_data.get_sd_id().to_string()), items));
return Ok(());
}
#[inline]
pub
fn add_reserved<I: SdIdIntrf>(&mut self, item: &I) -> SyRes<()>
{
if self.st_data.iter().any(|(v, _)| v.as_ref() == I::SD_ID_TEXT) == true
{
throw_error!("SD-ID already exists on the list: '{}'", I::SD_ID_TEXT);
}
let items = item.format_msg();
self.st_data.push((Cow::Borrowed(I::SD_ID_TEXT), items));
return Ok(());
}
#[inline]
fn format(&self) -> String
{
let ret =
self
.st_data
.iter()
.map(|(_, d)| [OBRACE, d.as_str(), CBRACE].concat())
.collect::<Vec<String>>()
.concat();
return ret;
}
}
unsafe impl Send for FormatRfc5424 {}
impl From<String> for FormatRfc5424
{
fn from(value: String) -> FormatRfc5424
{
return
Self
{
seq: Self::generate_sequence_num(),
msg: Cow::Owned(value),
msg_id: Cow::Borrowed(NILVALUE),
st_data: Vec::new(),
};
}
}
impl From<&'static str> for FormatRfc5424
{
fn from(value: &'static str) -> FormatRfc5424
{
return
Self
{
seq: Self::generate_sequence_num(),
msg: Cow::Borrowed(value),
msg_id: Cow::Borrowed(NILVALUE),
st_data: Vec::new(),
};
}
}
impl SyslogFormatter for FormatRfc5424
{
fn vsyslog1_format(&self, max_msg_size: usize, prifac: SyslogMsgPriFac, progname: &str, pid: Option<&str>) -> SyslogFormatted
{
let timedate =
Local::now().to_rfc3339_opts(SecondsFormat::Secs, false);
let hostname: String =
portable::portable_gethostname().map_or(NILVALUE.into(), |f| f);
let msg_pri =
[
"<", prifac.get_val().to_string().as_str(), ">1"
]
.concat();
let msg_st_data =
if self.st_data.is_empty() == false
{
Some(self.format())
}
else
{
None
};
let msg_pkt =
[
WSPACE, timedate.as_str(),
WSPACE, hostname.as_str(),
WSPACE, progname,
WSPACE, pid.map_or(NILVALUE, |f| f),
WSPACE, &self.msg_id,
WSPACE, msg_st_data.as_ref().map_or(NILVALUE, |f| f.as_str()),
WSPACE,
]
.concat();
let msg_space_left = max_msg_size - msg_pkt.as_bytes().len();
let msg_payload = truncate_n(&self.msg, msg_space_left);
let msg_payload_final =
if msg_payload.ends_with("\n") == true
{
truncate(msg_payload)
}
else
{
msg_payload
};
let msg_payload= [msg_pkt.as_str(), msg_payload_final].concat();
return
SyslogFormatted
{
msg_header: Some(msg_pri),
msg_payload: msg_payload,
full_msg: None
};
}
}
#[cfg(test)]
mod tests
{
use std::{borrow::Cow, time::Instant};
use crate::
{
LogFacility, Priority, SyslogMsgPriFac,
formatters::{FormatRfc5424, SyslogFormatter, syslog_5424::{FormatRfc5424Sd, SdIdMeta}}
};
#[test]
fn test_params1()
{
FormatRfc5424::reset_seq_counter();
let mut m = FormatRfc5424::from("test123");
m.add_msg_id_str("ATX41").unwrap();
let mut sd= FormatRfc5424Sd::new("testItem@3424", 2).unwrap();
sd.push(Cow::Borrowed("testKey"), Cow::Borrowed("testVal")).unwrap();
sd.push(Cow::Borrowed("testKey"), Cow::Borrowed("testVal")).unwrap();
m.add_st_data(sd).unwrap();
let fmtf = m.format();
assert_eq!(fmtf.as_str(), "[testItem@3424 testKey=\"testVal\" testKey=\"testVal\"]");
let mut res =
m.vsyslog1_format(
1024,
SyslogMsgPriFac::set_facility(Priority::LOG_CRIT, LogFacility::LOG_DAEMON),
"test",
Some("1234")
);
let cres = res.get_full_msg();
println!("{}", cres);
return;
}
#[test]
fn test_params2()
{
FormatRfc5424::reset_seq_counter();
let mut m = FormatRfc5424::from("test123");
m.add_msg_id_str("ATX41").unwrap();
let mut sd= FormatRfc5424Sd::new("exampleSDID@32474", 3).unwrap();
sd.push(Cow::Borrowed("iut"), Cow::Borrowed("3")).unwrap();
sd.push(Cow::Borrowed("eventSource"), Cow::Borrowed("Application")).unwrap();
sd.push(Cow::Borrowed("eventID"), Cow::Borrowed("1011")).unwrap();
m.add_st_data(sd).unwrap();
let mut sd= FormatRfc5424Sd::new("examplePriority@32474", 1).unwrap();
sd.push(Cow::Borrowed("class"), Cow::Borrowed("high")).unwrap();
m.add_st_data(sd).unwrap();
let s = Instant::now();
let fmtf = m.format();
let e = s.elapsed();
println!("took: {:?}", e);
println!("{}", fmtf);
assert_eq!(fmtf.as_str(), "[exampleSDID@32474 iut=\"3\" eventSource=\"Application\" eventID=\"1011\"][examplePriority@32474 class=\"high\"]");
let mut res =
m.vsyslog1_format(
1024,
SyslogMsgPriFac::set_facility(Priority::LOG_CRIT, LogFacility::LOG_DAEMON),
"test",
Some("1234")
);
let cres = res.get_full_msg();
println!("{}", cres);
return;
}
#[test]
fn test_params3()
{
fn test_p3(assert_tx: &str)
{
let mut m = FormatRfc5424::from("test123");
m.add_msg_id_str("ATX41").unwrap();
let v = SdIdMeta::new_test("jp", 12345, &m);
m.add_reserved(&v).unwrap();
let fmtf = m.format();
assert_eq!(fmtf.as_str(), assert_tx);
let mut res =
m.vsyslog1_format(
1024,
SyslogMsgPriFac::set_facility(Priority::LOG_CRIT, LogFacility::LOG_DAEMON),
"test",
Some("1234")
);
let cres = res.get_full_msg();
println!("{}", cres);
}
FormatRfc5424::reset_seq_counter();
test_p3("[meta sequenceId=\"1\" sysUpTime=\"12345\" language=\"jp\"]");
test_p3("[meta sequenceId=\"2\" sysUpTime=\"12345\" language=\"jp\"]");
return;
}
#[test]
fn test_fail_params1()
{
let mut m = FormatRfc5424::from("test123");
m.add_msg_id_str("ATX41").unwrap();
let sd= FormatRfc5424Sd::new("exampleSDID", 1);
assert_eq!(sd.is_err(), true);
let sd= FormatRfc5424Sd::new("exampleSDID@", 1);
assert_eq!(sd.is_err(), true);
let sd= FormatRfc5424Sd::new("@23", 1);
assert_eq!(sd.is_err(), true);
let sd= FormatRfc5424Sd::new("dfe@23", 0);
assert_eq!(sd.is_err(), true);
}
}