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