Skip to main content

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