syslog_rs/formatters/
syslog_5424.rs

1/*-
2 * syslog-rs - a syslog client translated from libc to rust
3 * 
4 * Copyright 2025 Aleksandr Morozov
5 * 
6 * The syslog-rs crate can be redistributed and/or modified
7 * under the terms of either of the following licenses:
8 *
9 *   1. the Mozilla Public License Version 2.0 (the “MPL”) OR
10 *
11 *   2. The MIT License (MIT)
12 *                     
13 *   3. EUROPEAN UNION PUBLIC LICENCE v. 1.2 EUPL © the European Union 2007, 2016
14 */
15
16use std::{borrow::Cow, fmt, net::IpAddr, num::NonZero, time::Duration};
17
18use chrono::{Local, SecondsFormat};
19
20use crate::
21{
22    check_printable, 
23    error::SyRes, 
24    escape_chars, 
25    map_error, 
26    portable, 
27    throw_error, 
28    truncate, 
29    truncate_n, 
30    Priority, 
31    ATSIGN, 
32    CBRACE, 
33    EQSIGN, 
34    IANA_PRIV_ENT_NUM, 
35    NILVALUE, 
36    OBRACE, 
37    QCHAR, 
38    WSPACE
39};
40
41use super::{SyslogFormatted, SyslogFormatter};
42
43/// A trait for the unification of the all Structured Data IDs (IANA-registered SD-IDs).
44pub trait SdIdIntrf: fmt::Display
45{
46    /// SD-ID identifier.
47    const SD_ID_TEXT: &'static str;
48
49    /// Formats the internals.
50    fn format_msg(&self) -> String;
51}
52
53
54/// RFC:
55/// > The SD-ID "timeQuality" MAY be used by the originator to describe its
56/// > notion of system time.  This SD-ID SHOULD be written if the
57/// > originator is not properly synchronized with a reliable external time
58/// > source or if it does not know whether its time zone information is
59/// > correct.  The main use of this structured data element is to provide
60/// > some information on the level of trust it has in the TIMESTAMP
61/// > described in Section 6.2.3.  All parameters are OPTIONAL.
62/// 
63/// RFC: 
64/// > The "tzKnown" parameter indicates whether the originator knows its
65/// > time zone.  If it does, the value "1" MUST be used.  If the time zone
66/// > information is in doubt, the value "0" MUST be used.  If the
67/// > originator knows its time zone but decides to emit time in UTC, the
68/// > value "1" MUST be used (because the time zone is known).
69/// tz_known
70/// 
71/// RFC:
72/// > The "isSynced" parameter indicates whether the originator is
73/// > synchronized to a reliable external time source, e.g., via NTP.  If
74/// > the originator is time synchronized, the value "1" MUST be used.  If
75/// > not, the value "0" MUST be used.
76/// is_synced
77/// 
78/// RFC: 
79/// > The "syncAccuracy" parameter indicates how accurate the originator
80/// > thinks its time synchronization is.  It is an integer describing the
81/// > maximum number of microseconds that its clock may be off between
82/// > synchronization intervals.
83/// > If the value "0" is used for "isSynced", this parameter MUST NOT be
84/// > specified.  If the value "1" is used for "isSynced" but the
85/// > "syncAccuracy" parameter is absent, a collector or relay can assume
86/// > that the time information provided is accurate enough to be
87/// > considered correct.  The "syncAccuracy" parameter MUST be written
88/// > only if the originator actually has knowledge of the reliability of
89/// > the external time source.
90/// sync_acc
91#[derive(Debug, Clone, PartialEq, Eq)]
92pub struct SdIdTimeQuality(String);
93
94impl fmt::Display for SdIdTimeQuality
95{
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result 
97    {
98        write!(f, "{} {}", Self::SD_ID_TEXT, self.0)
99    }
100}
101
102impl SdIdIntrf for SdIdTimeQuality
103{
104    const SD_ID_TEXT: &'static str = "timeQuality";
105
106    fn format_msg(&self) -> String 
107    {
108        return self.0.clone();
109    }
110}
111
112impl SdIdTimeQuality
113{
114    pub 
115    fn new(tz_known: bool, is_synced: bool, sync_acc: Option<u64>) -> Self
116    {
117        if let Some(sa) = sync_acc
118        {
119            let msg = 
120                [
121                    Self::SD_ID_TEXT, WSPACE,
122                    "tzKnown", "=", QCHAR, tz_known.to_string().as_str(), QCHAR, WSPACE,
123                    "isSynced", "=", QCHAR, is_synced.to_string().as_str(), QCHAR, WSPACE,
124                    "syncAccuracy", "=", QCHAR, sa.to_string().as_str(), QCHAR,
125                ]
126                .concat();
127
128            return Self(msg);
129        }
130        else
131        {
132            let msg = 
133                [
134                    Self::SD_ID_TEXT, WSPACE,
135                    "tzKnown", "=", QCHAR, tz_known.to_string().as_str(), QCHAR, WSPACE,
136                    "isSynced", "=", QCHAR, is_synced.to_string().as_str(), QCHAR,
137                ]
138                .concat();
139
140            return Self(msg);
141        }
142    }
143}
144
145/// RFC:
146/// > The SD-ID "origin" MAY be used to indicate the origin of a syslog
147/// > message.  The following parameters can be used.  All parameters are
148/// > OPTIONAL.
149/// > 
150/// > Specifying any of these parameters is primarily an aid to log
151/// > analyzers and similar applications.
152/// 
153/// RFC:
154/// > The "ip" parameter denotes an IP address that the originator knows it
155/// > had at the time of originating the message.  It MUST contain the
156/// > textual representation of an IP address as outlined in Section 6.2.4.
157/// > 
158/// > This parameter can be used to provide identifying information in
159/// > addition to what is present in the HOSTNAME field.  It might be
160/// > especially useful if the host's IP address is included in the message
161/// > while the HOSTNAME field still contains the FQDN.  It is also useful
162/// > for describing all IP addresses of a multihomed host.
163/// > 
164/// > If an originator has multiple IP addresses, it MAY either list one of
165/// > its IP addresses in the "ip" parameter or it MAY include multiple
166/// > "ip" parameters in a single "origin" structured data element.
167/// ip
168/// 
169/// Some sh$t which grandpas from IANA invented and grabbed.
170/// enterprise_id
171/// 
172/// RFC:
173/// > The "software" parameter uniquely identifies the software that
174/// > generated the message.  If it is used, "enterpriseId" SHOULD also be
175/// > specified, so that a specific vendor's software can be identified.
176/// > The "software" parameter is not the same as the APP-NAME header
177/// > field.
178/// > The "software" parameter is a string.  It MUST NOT be longer than 48
179/// > characters.
180/// software
181/// 
182/// RFC: 
183/// > The "swVersion" parameter uniquely identifies the version of the
184/// > software that generated the message.  If it is used, the "software"
185/// > and "enterpriseId" parameters SHOULD be provided, too.
186/// > 
187/// > The "swVersion" parameter is a string.  It MUST NOT be longer than 32
188/// > characters.
189/// sw_version
190#[derive(Debug, Clone, PartialEq, Eq)]
191pub struct SdIdOrigin(String);
192
193impl fmt::Display for SdIdOrigin
194{
195    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result 
196    {
197        write!(f, "{} {}", Self::SD_ID_TEXT, self.0)
198    }
199}
200
201impl SdIdIntrf for SdIdOrigin
202{
203    const SD_ID_TEXT: &'static str = "origin";
204
205    fn format_msg(&self) -> String
206    {
207        return self.0.clone();
208    }
209}
210
211impl SdIdOrigin
212{
213    pub 
214    fn new(ips: Vec<IpAddr>, enterprise_id: Option<&str>, software: Option<&str>, sw_version: Option<&str>) -> Self
215    {
216        let mut items: Vec<String> = 
217            Vec::with_capacity(ips.len() + 1 + enterprise_id.map_or(0, |_| 1) + 
218                software.map_or(0, |_| 1) + sw_version.map_or(0, |_| 1));
219
220        items.push(Self::SD_ID_TEXT.to_string());
221
222        ips.iter().for_each(|ip| items.push(["ip=", QCHAR, ip.to_string().as_str(), QCHAR].concat()));
223
224        if let Some(ent_id) = enterprise_id
225        {
226            items.push(["enterpriseId=", QCHAR, ent_id, QCHAR].concat());
227
228            if let Some(softw) = software
229            {
230                items.push(["software=", QCHAR, softw, QCHAR].concat());
231
232                if let Some(sw_ver) = sw_version
233                {
234                    items.push(["swVersion=", QCHAR, sw_ver, QCHAR].concat());
235                }
236                else
237                {
238                    items.push(["swVersion=", QCHAR, "upspecified", QCHAR].concat());
239                }
240            }
241            else
242            {
243                items.push(["software=", QCHAR, "upspecified", QCHAR].concat());
244            }
245        }
246
247        let ret = Self(items.join(WSPACE));
248
249        return ret;
250    }
251}
252
253
254/// RFC:
255/// > The SD-ID "meta" MAY be used to provide meta-information about the
256/// > message.  The following parameters can be used.  All parameters are
257/// > OPTIONAL.  If the "meta" SD-ID is used, at least one parameter SHOULD
258/// > be specified.
259/// 
260/// RFC: 
261/// > The "sequenceId" parameter tracks the sequence in which the
262/// > originator submits messages to the syslog transport for sending.  It
263/// > is an integer that MUST be set to 1 when the syslog function is
264/// > started and MUST be increased with every message up to a maximum
265/// > value of 2147483647.  If that value is reached, the next message MUST
266/// > be sent with a sequenceId of 1.
267/// sequenceId
268/// 
269/// RFC: 
270/// > The "sysUpTime" parameter MAY be used to include the SNMP "sysUpTime"
271/// > parameter in the message.  Its syntax and semantics are as defined in
272/// > [RFC3418].
273/// sysUpTime
274/// 
275/// RFC:
276/// > The "language" parameter MAY be specified by the originator to convey
277/// > information about the natural language used inside MSG.  If it is
278/// > specified, it MUST contain a language identifier as defined in BCP 47
279/// > [RFC4646].
280/// language
281#[derive(Debug, Clone, PartialEq, Eq)]
282pub struct SdIdMeta(String);
283
284impl fmt::Display for SdIdMeta
285{
286    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result 
287    {
288        write!(f, "{} {}", Self::SD_ID_TEXT, self.0)
289    }
290}
291
292impl SdIdIntrf for SdIdMeta
293{
294    const SD_ID_TEXT: &'static str = "meta";
295
296    fn format_msg(&self) -> String
297    {
298        return self.0.clone();
299    }
300}
301
302impl SdIdMeta
303{
304    /// Creates new instance. This includes the uptime request and sequence number.
305    /// 
306    /// This message can not be cached as it 
307    pub 
308    fn new(language: &str, msg: &FormatRfc5424) -> Self
309    {
310        let uptime = portable::get_uptime().unwrap_or(Duration::from_secs(0));
311
312       
313        
314        let msg = 
315            [
316                Self::SD_ID_TEXT, WSPACE,
317                "sequenceId", "=", QCHAR, msg.seq.to_string().as_str(), QCHAR, WSPACE,
318                "sysUpTime", "=", QCHAR, uptime.as_secs().to_string().as_str(), QCHAR, WSPACE,
319                "language", "=", QCHAR, language, QCHAR,
320            ]
321            .concat();
322
323        return Self(msg);
324    }
325
326    #[cfg(test)]
327    pub 
328    fn new_test(language: &str, uptime: u64, msg: &FormatRfc5424) -> Self
329    {
330        let msg = 
331            [
332                Self::SD_ID_TEXT, WSPACE,
333                "sequenceId", "=", QCHAR, msg.seq.to_string().as_str(), QCHAR, WSPACE,
334                "sysUpTime", "=", QCHAR, uptime.to_string().as_str(), QCHAR, WSPACE,
335                "language", "=", QCHAR, language, QCHAR,
336            ]
337            .concat();
338
339        return Self(msg);
340    }
341}
342
343/// A RFC5424 SD-ELEMENT of the STRUCTURED-DATA.
344/// 
345/// i.e [exampleSDID@32473 iut="3" eventSource="Application" eventID="1011"]
346/// 
347/// RFC:
348/// > SD-IDs are case-sensitive and uniquely identify the type and purpose
349/// > of the SD-ELEMENT.  The same SD-ID MUST NOT exist more than once in a
350/// > message.
351/// > 
352/// > There are two formats for SD-ID names:
353/// > 
354/// > * - Names that do not contain an at-sign ("@", ABNF %d64) are reserved
355/// > to be assigned by IETF
356/// > 
357/// > * - Anyone can define additional SD-IDs using names in the format
358/// > name@<private enterprise number>, e.g., "ourSDID@32473".  The
359/// > format of the part preceding the at-sign is not specified;
360/// sd_id
361/// 
362/// RFC:
363/// > Each SD-PARAM consists of a name, referred to as PARAM-NAME, and a
364/// > value, referred to as PARAM-VALUE. PARAM-NAME is case-sensitive.
365/// > To support international characters, the PARAM-VALUE field MUST be
366/// > encoded using UTF-8.
367/// vals
368#[derive(Debug, Clone)]
369pub struct FormatRfc5424Sd
370{
371    // SD-ID offset 0, rest - data.
372    vals: Vec<Cow<'static, str>>
373}
374
375impl fmt::Display for FormatRfc5424Sd
376{
377    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result 
378    {
379        write!(f, "{}", self.format_sd())
380    }
381}
382
383impl FormatRfc5424Sd
384{
385    /// Creates the second type of the SD-ID in format of
386    /// name@<private enterprise number>, e.g., "ourSDID@32473". 
387    /// 
388    /// RFC5424:
389    /// ```text
390    /// The format of the part preceding the at-sign is not specified;
391    /// however, these names MUST be printable US-ASCII strings, and MUST
392    /// NOT contain an at-sign ('@', ABNF %d64), an equal-sign ('=', ABNF
393    /// %d61), a closing brace (']', ABNF %d93), a quote-character ('"',
394    /// ABNF %d34), whitespace, or control characters.  The part following
395    /// the at-sign MUST be a private enterprise number as specified in
396    /// Section 7.2.2.  Please note that throughout this document the
397    /// value of 32473 is used for all private enterprise numbers.  This
398    /// value has been reserved by IANA to be used as an example number in
399    /// documentation.  Implementors will need to use their own private
400    /// enterprise number for the enterpriseId parameter, and when
401    /// creating locally extensible SD-ID names.
402    /// ```
403    /// 
404    /// # Arguments
405    /// 
406    /// * `sd_id` - a sd_id which is stored with static lifetime.
407    /// 
408    /// * `vals_cnt` - amount of the values to be added.
409    /// 
410    /// # Returns
411    /// 
412    /// A [Result] in form of [SyRes] is returned.
413    pub 
414    fn new(sd_id: &'static str, vals_cnt: usize) -> SyRes<Self>
415    {
416        if vals_cnt == 0
417        {
418            throw_error!("params should present.");
419        }
420
421        if sd_id.contains(ATSIGN) == false
422        {
423            throw_error!("SD-ID should contain @, otherwise see pre-defined ID's.");
424        }
425
426        let (name, ent_num) = 
427            sd_id
428                .split_once(ATSIGN)
429                .ok_or_else(||
430                    map_error!("incorrect SD-ID format")
431                )?;
432
433        check_printable(name)?;
434
435        let ent_num: u64 = 
436            ent_num
437                .parse()
438                .map_err(|e|
439                    map_error!("incorrect SD-ID entrprise number format, {}", e)
440                )?;
441
442        if ent_num == IANA_PRIV_ENT_NUM
443        {
444            throw_error!("SD-ID entrprise number can not be '{}' because it is private", ent_num);
445        }
446
447        let mut vals: Vec<Cow<'static, str>> = Vec::with_capacity(vals_cnt*3 + 2);
448        vals.push(Cow::Borrowed(sd_id));
449
450        return Ok(Self{ vals: vals });
451    }
452
453    /// Inserts the SD-PARAM into list. 
454    /// An SD-PARAM MAY be repeated multiple times inside an SD-ELEMENT.
455    pub 
456    fn push(&mut self, key: Cow<'static, str>, val: Cow<'static, str>) -> SyRes<()>
457    {
458        check_printable(&key)?;
459
460        let escp_val = escape_chars(val);
461        self.vals.push( Cow::Owned([WSPACE, key.as_ref(), EQSIGN, QCHAR, escp_val.as_ref(), QCHAR].concat()));
462
463        return Ok(());
464    }
465
466    fn format_sd(&self) -> String
467    {        
468        return self.vals.concat();
469    }
470
471    #[inline]
472    fn get_sd_id(&self) -> &str
473    {
474        return self.vals[0].as_ref();
475    }
476}
477
478
479/// The "sequenceId" parameter tracks the sequence in which the
480/// originator submits messages to the syslog transport for sending.
481#[cfg(target_has_atomic = "64")]
482static SEQUENCE_ID: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
483
484/// The "sequenceId" parameter tracks the sequence in which the
485/// originator submits messages to the syslog transport for sending.
486#[cfg(not(target_has_atomic = "64"))]
487static SEQUENCE_ID: std::sync::Mutex<u32> = std::sync::Mutex::new(0);
488
489/// A RFC5424 formatter which formats the syslog message according to the
490/// RFC5424 requirments so it could be received by the syslog server.
491/// 
492/// # Usage
493/// 
494/// ```ignore
495/// let mut m = FormatRfc5424::from("test123");
496///
497/// m.add_msg_id_str("ATX41").unwrap();
498///
499/// let v = SdIdMeta::new_test("jp", 12345, &m);
500///
501/// m.add_internal(&v).unwrap();
502/// ```
503#[derive(Debug, Clone)]
504pub struct FormatRfc5424
505{
506    /// A sequence number of the message
507    seq: NonZero<u32>,
508
509    /// A payload message.
510    msg: Cow<'static, str>,
511
512    /// RFC: The MSGID SHOULD identify the type of message.  For example, a
513    /// firewall might use the MSGID "TCPIN" for incoming TCP traffic and the
514    /// MSGID "TCPOUT" for outgoing TCP traffic.  Messages with the same
515    /// MSGID should reflect events of the same semantics.  The MSGID itself
516    /// is a string without further semantics.  It is intended for filtering
517    //// messages on a relay or collector.
518    msg_id: Cow<'static, str>,
519
520    /// All STRUCTURED-DATA added to the current message.
521    st_data: Vec<(Cow<'static, str>, String)>,
522}
523
524impl FormatRfc5424
525{
526    /// Creates a new Rfc5424 template with provided `seq_num`. The sequence is not
527    /// verified!
528    /// 
529    /// # Arguments 
530    /// 
531    /// * `value` - [String] a log message.
532    /// 
533    /// * `seq_num` - [u32] a sequence number.
534    /// 
535    /// # Returns
536    /// 
537    /// A template is retuened.
538    pub 
539    fn new(value: String, seq_num: NonZero<u32>) -> FormatRfc5424 
540    {
541        return 
542            Self
543            {
544                seq: seq_num,
545                msg: Cow::Owned(value),
546                msg_id: Cow::Borrowed(NILVALUE),
547                st_data: Vec::new(),
548            };
549    }
550
551    /// Generates a [NonZero] sequence number of the message. When reaching [u32::MAX] 
552    /// starts from 1. Using atomics.
553    #[cfg(target_has_atomic = "64")]
554    fn generate_sequence_num() -> NonZero<u32>
555    {
556        use std::sync::atomic::{Ordering};
557
558        let mut last = SEQUENCE_ID.load(Ordering::Relaxed);
559        
560        loop 
561        {
562            let seq_id = 
563                match last.checked_add(1) 
564                {
565                    Some(id) => id,
566                    None =>
567                    {
568                        1
569                    }
570                };
571
572            match SEQUENCE_ID.compare_exchange_weak(last, seq_id, Ordering::Relaxed, Ordering::Relaxed) 
573            {
574                Ok(_) => 
575                    return NonZero::new(seq_id).unwrap(),
576                Err(id) => 
577                    last = id,
578            }
579        }
580    }
581
582    /// Generates a [NonZero] sequence number of the message. When reaching [u32::MAX] 
583    /// starts from 1. Using mutex.
584    #[cfg(not(target_has_atomic = "64"))]
585    fn generate_sequence_num() -> NonZero<u32>
586    {
587        let mut seq_id_cnt = 
588            SEQUENCE_ID.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
589
590        let seq_id = 
591            match seq_id_cnt.checked_add(1) 
592            {
593                Some(id) => id,
594                None =>
595                {
596                    1
597                }
598            };
599
600        *seq_id_cnt = seq_id;
601        drop(seq_id_cnt);
602
603        return NonZero::new(seq_id).unwrap();
604    }
605
606    #[cfg(test)]
607    #[cfg(target_has_atomic = "64")]
608    fn reset_seq_counter()
609    {
610        SEQUENCE_ID.store(0, std::sync::atomic::Ordering::Relaxed);
611    }
612
613    #[cfg(test)]
614    #[cfg(not(target_has_atomic = "64"))]
615    fn reset_seq_counter()
616    {
617        *SEQUENCE_ID.lock().unwrap() = 0;
618    }
619
620    /// Adds the MSGID which identifies the type of the message. Make sure that
621    /// it does not contain any non ASCII characters or non-printable chars. Normally
622    /// it should consist from letters and digits.
623    #[inline]
624    pub 
625    fn add_msg_id_str(&mut self, msg_id: &'static str) -> SyRes<()>
626    {
627        check_printable(msg_id)?;
628
629        self.msg_id = Cow::Borrowed(msg_id);
630
631        return Ok(());
632    }
633
634    /// Adds the MSGID which identifies the type of the message. Make sure that
635    /// it does not contain any non ASCII characters or non-printable chars. Normally
636    /// it should consist from letters and digits.
637    #[inline]
638    pub 
639    fn add_msg_id(&mut self, msg_id: impl Into<String>) -> SyRes<()>
640    {
641        let msgid = msg_id.into();
642
643        check_printable(&msgid)?;
644
645        self.msg_id = Cow::Owned(msgid);
646
647        return Ok(());
648    }
649
650    /// Inserts the instance of the [FormatRfc5424Sd] to the list. This is a user defined
651    /// formatted message.
652    #[inline]
653    pub
654    fn add_st_data(&mut self, st_data: FormatRfc5424Sd) -> SyRes<()>
655    {
656        if self.st_data.iter().any(|(v, _)| v.as_ref() == st_data.get_sd_id()) == true
657        {
658            throw_error!("SD-ID already exists on the list: '{}'", st_data);
659        }
660
661        let items = st_data.format_sd();
662
663        self.st_data.push((Cow::Owned(st_data.get_sd_id().to_string()), items));
664
665        return Ok(());
666    }
667
668    /// Inserts the reserved type of the formatted message. The reserved types can be found 
669    /// above. [] [] []
670    #[inline]
671    pub 
672    fn add_reserved<I: SdIdIntrf>(&mut self, item: &I) -> SyRes<()>
673    {
674        if self.st_data.iter().any(|(v, _)| v.as_ref() == I::SD_ID_TEXT) == true
675        {
676            throw_error!("SD-ID already exists on the list: '{}'", I::SD_ID_TEXT);
677        }
678
679        let items = item.format_msg();
680
681        self.st_data.push((Cow::Borrowed(I::SD_ID_TEXT), items));
682
683        return Ok(());
684    }
685
686    #[inline]
687    fn format(&self) -> String
688    {
689        let ret = 
690            self
691                .st_data
692                .iter()
693                .map(|(_, d)| [OBRACE, d.as_str(), CBRACE].concat())
694                .collect::<Vec<String>>()
695                .concat();
696
697        return ret;
698    }
699
700
701}
702
703unsafe impl Send for FormatRfc5424 {}
704
705impl From<String> for FormatRfc5424
706{
707    fn from(value: String) -> FormatRfc5424 
708    {
709        return 
710            Self
711            {
712                seq: Self::generate_sequence_num(),
713                msg: Cow::Owned(value),
714                msg_id: Cow::Borrowed(NILVALUE),
715                st_data: Vec::new(),
716            };
717    }
718}
719
720impl From<&'static str> for FormatRfc5424
721{
722    fn from(value: &'static str) -> FormatRfc5424
723    {
724        return 
725            Self
726            {
727                seq: Self::generate_sequence_num(),
728                msg: Cow::Borrowed(value),
729                msg_id: Cow::Borrowed(NILVALUE),
730                st_data: Vec::new(),
731            };
732    }
733}
734
735
736impl SyslogFormatter for FormatRfc5424
737{
738    fn vsyslog1_format(&self, max_msg_size: usize, pri: Priority, progname: &str, pid: &str) -> SyslogFormatted
739    {
740        // get timedate
741        let timedate = 
742            Local::now().to_rfc3339_opts(SecondsFormat::Secs, false);
743
744		let hostname: String =
745            nix::unistd::gethostname()
746                .map_or(
747                    NILVALUE.into(), 
748                    |r| r.to_str().map_or(NILVALUE, |v| v).to_string()
749                );
750
751        // message based on RFC 3164
752        let msg_pri = 
753            [
754                "<", pri.bits().to_string().as_str(), ">1"
755            ]
756            .concat();
757
758        let msg_st_data = 
759            if self.st_data.is_empty() == false
760            {
761                Some(self.format())
762            }
763            else
764            {
765                None
766                //Cow::Borrowed(NILVALUE)
767            };
768
769		// message based on RFC 5424
770        let msg_pkt = 
771            [
772                // PRI 
773                //Cow::Owned(msg_pri),
774                // timedate
775                WSPACE, timedate.as_str(), 
776                // hostname
777                WSPACE, hostname.as_str(),
778                // appname
779                WSPACE, progname, 
780                // PID
781                WSPACE, pid,
782                // message ID
783                WSPACE, &self.msg_id,
784                // structured data
785                WSPACE, msg_st_data.as_ref().map_or(NILVALUE, |f| f.as_str()), 
786                // msg
787                WSPACE, 
788                /*b"\xEF\xBB\xBF",*/
789            ]
790            .concat();
791
792        // calculate the left message space
793        let msg_space_left = max_msg_size - msg_pkt.as_bytes().len();
794
795        let msg_payload = truncate_n(&self.msg, msg_space_left);
796
797        let msg_payload_final = 
798            if msg_payload.ends_with("\n") == true
799            {
800                truncate(msg_payload)
801            }
802            else
803            {
804                msg_payload
805            };
806
807        // set payload
808		let msg_payload= [msg_pkt.as_str(), msg_payload_final].concat();
809
810        return 
811            SyslogFormatted
812            { 
813                msg_header: Some(msg_pri), 
814                msg_payload: msg_payload, 
815                full_msg: None
816            };
817    }
818}
819
820#[cfg(test)]
821mod tests
822{
823    use std::{borrow::Cow, time::Instant};
824
825    use crate::{formatters::{syslog_5424::{FormatRfc5424Sd, SdIdMeta}, FormatRfc5424, SyslogFormatter}, Priority};
826
827    #[test]
828    fn test_params1()
829    {
830        FormatRfc5424::reset_seq_counter();
831
832        let mut m = FormatRfc5424::from("test123");
833
834        m.add_msg_id_str("ATX41").unwrap();
835
836        let mut sd= FormatRfc5424Sd::new("testItem@3424", 2).unwrap();
837
838        sd.push(Cow::Borrowed("testKey"), Cow::Borrowed("testVal")).unwrap();
839        sd.push(Cow::Borrowed("testKey"), Cow::Borrowed("testVal")).unwrap();
840
841        m.add_st_data(sd).unwrap();
842
843        let fmtf = m.format();
844
845        assert_eq!(fmtf.as_str(), "[testItem@3424 testKey=\"testVal\" testKey=\"testVal\"]");
846
847        let mut res = 
848            m.vsyslog1_format(1024, Priority::LOG_CRIT,  "test", "1234");
849
850        let cres = res.get_full_msg();
851
852        println!("{}", cres);
853
854        return;
855    }
856
857    #[test]
858    fn test_params2()
859    {
860        FormatRfc5424::reset_seq_counter();
861
862        let mut m = FormatRfc5424::from("test123");
863
864        m.add_msg_id_str("ATX41").unwrap();
865
866        let mut sd= FormatRfc5424Sd::new("exampleSDID@32474", 3).unwrap();
867
868        sd.push(Cow::Borrowed("iut"), Cow::Borrowed("3")).unwrap();
869        sd.push(Cow::Borrowed("eventSource"), Cow::Borrowed("Application")).unwrap();
870        sd.push(Cow::Borrowed("eventID"), Cow::Borrowed("1011")).unwrap();
871
872        m.add_st_data(sd).unwrap();
873
874        let mut sd= FormatRfc5424Sd::new("examplePriority@32474", 1).unwrap();
875
876        sd.push(Cow::Borrowed("class"), Cow::Borrowed("high")).unwrap();
877        m.add_st_data(sd).unwrap();
878
879        let s = Instant::now();
880
881        let fmtf = m.format();
882
883        let e = s.elapsed();
884
885        println!("took: {:?}", e);
886
887        println!("{}", fmtf);
888
889        assert_eq!(fmtf.as_str(), "[exampleSDID@32474 iut=\"3\" eventSource=\"Application\" eventID=\"1011\"][examplePriority@32474 class=\"high\"]");
890
891
892        let mut res = 
893            m.vsyslog1_format(1024, Priority::LOG_CRIT, "test", "1234");
894
895        let cres = res.get_full_msg();
896
897        println!("{}", cres);
898
899        return;
900    }
901
902    #[test]
903    fn test_params3()
904    {
905        fn test_p3(assert_tx: &str)
906        {
907            let mut m = FormatRfc5424::from("test123");
908
909            m.add_msg_id_str("ATX41").unwrap();
910
911            let v = SdIdMeta::new_test("jp", 12345, &m);
912
913            m.add_reserved(&v).unwrap();
914
915            let fmtf = m.format();
916
917            assert_eq!(fmtf.as_str(), assert_tx);
918
919            let mut res = 
920                m.vsyslog1_format( 1024, Priority::LOG_CRIT, "test", "1234");
921
922            let cres = res.get_full_msg();
923
924            println!("{}", cres);
925        }
926
927        FormatRfc5424::reset_seq_counter();
928
929        test_p3("[meta sequenceId=\"1\" sysUpTime=\"12345\" language=\"jp\"]");
930        test_p3("[meta sequenceId=\"2\" sysUpTime=\"12345\" language=\"jp\"]");
931        
932
933        return;
934    }
935
936    #[test]
937    fn test_fail_params1()
938    {
939        let mut m = FormatRfc5424::from("test123");
940
941        m.add_msg_id_str("ATX41").unwrap();
942
943        let sd= FormatRfc5424Sd::new("exampleSDID", 1);
944
945        assert_eq!(sd.is_err(), true);
946
947        let sd= FormatRfc5424Sd::new("exampleSDID@", 1);
948
949        assert_eq!(sd.is_err(), true);
950
951        let sd= FormatRfc5424Sd::new("@23", 1);
952
953        assert_eq!(sd.is_err(), true);
954
955        let sd= FormatRfc5424Sd::new("dfe@23", 0);
956
957        assert_eq!(sd.is_err(), true);
958    }
959}
960