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