1#[derive(Debug, Clone, Eq, PartialEq)]
6pub struct Message {
7 pub ttaaii: String,
9
10 pub cccc: String,
12
13 pub awips_id: Option<String>,
15
16 pub issue: chrono::DateTime<chrono::FixedOffset>,
18
19 pub id: String,
25
26 pub delay_stamp: Option<chrono::DateTime<chrono::FixedOffset>>,
31
32 pub ldm_sequence_number: Option<u32>,
38
39 pub message: String,
41}
42
43impl TryFrom<xmpp_parsers::minidom::Element> for Message {
44 type Error = ();
45
46 fn try_from(value: xmpp_parsers::minidom::Element) -> Result<Self, Self::Error> {
47 xmpp_parsers::message::Message::try_from(value)
48 .ok()
49 .and_then(|msg| Self::try_from(msg).ok())
50 .ok_or(())
51 }
52}
53
54impl TryFrom<xmpp_parsers::message::Message> for Message {
55 type Error = xmpp_parsers::message::Message;
56
57 fn try_from(value: xmpp_parsers::message::Message) -> std::result::Result<Self, Self::Error> {
58 if value.type_ != xmpp_parsers::message::MessageType::Groupchat {
59 return Err(value);
60 }
61
62 let delay_stamp = value
63 .payloads
64 .iter()
65 .find(|p| p.is("delay", "urn:xmpp:delay"))
66 .and_then(|delay| delay.attr("stamp"))
67 .and_then(|v| chrono::DateTime::parse_from_rfc3339(v).ok());
68
69 let oi = if let Some(oi) = value.payloads.iter().find(|p| p.is("x", "nwws-oi")) {
70 oi
71 } else {
72 return Err(value);
73 };
74
75 let message = oi.text();
76
77 let message = if message.matches("\n").count() == message.matches("\n\n").count() * 2 {
80 message.replace("\n\n", "\n")
81 } else {
82 message
83 };
84
85 let (ldm_sequence_number, message) = match {
87 let mut i = message.splitn(3, '\n');
88 (i.next(), i.next().and_then(|s| s.parse().ok()), i.next())
89 } {
90 (Some(""), Some(ldm_sequence_number), Some(rest)) => {
91 (Some(ldm_sequence_number), rest.into())
92 }
93 _ => (None, message),
94 };
95
96 return match (
97 oi.attr("awipsid"),
98 oi.attr("cccc"),
99 oi.attr("id"),
100 oi.attr("issue").map(chrono::DateTime::parse_from_rfc3339),
101 oi.attr("ttaaii"),
102 ) {
103 (Some(awipsid), Some(cccc), Some(id), Some(Ok(issue)), Some(ttaaii)) => Ok(Self {
104 awips_id: Some(awipsid).filter(|s| s.len() > 0).map(|s| s.into()),
105 cccc: cccc.into(),
106 id: id.into(),
107 issue,
108 ttaaii: ttaaii.into(),
109 delay_stamp,
110 ldm_sequence_number,
111 message,
112 }),
113 _ => Err(value),
114 };
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121 use chrono::Timelike;
122
123 fn msg(xml: &str) -> Result<Message, ()> {
124 let element: xmpp_parsers::minidom::Element = xml.parse().unwrap();
125 let msg: xmpp_parsers::message::Message = element.try_into().unwrap();
126
127 Message::try_from(msg).map_err(|_| ())
128 }
129
130 #[test]
131 fn parse_banner() {
132 assert_eq!(
133 msg("<message xmlns=\"jabber:client\" from=\"nwws@conference.nwws-oi.weather.gov\" to=\"w.glynn@nwws-oi.weather.gov/todo\" type=\"groupchat\"><subject>National Weather Wire Service Open Interface</subject><delay xmlns=\"urn:xmpp:delay\" from=\"nwws@conference.nwws-oi.weather.gov\" stamp=\"2015-02-03T20:48:44.222Z\"/></message>"),
134 Err(())
135 );
136 }
137
138 #[test]
139 fn parse_terms() {
140 assert_eq!(
141 msg("<message xmlns=\"jabber:client\" from=\"nwws-oi.weather.gov\" to=\"w.glynn@nwws-oi.weather.gov/uuid/56d00e55-29f5-446a-8e18-0dd6af8e7dcd\"><subject>US Federal Government</subject><body>**WARNING**WARNING**WARNING**WARNING**WARNING**WARNING**WARNING**WARNING**\n\nThis is a United States Federal Government computer system, which may be\naccessed and used only for official Government business by authorized\npersonnel. Unauthorized access or use of this computer system may\nsubject violators to criminal, civil, and/or administrative action.\n\nAll information on this computer system may be intercepted, recorded,\nread, copied, and disclosed by and to authorized personnel for official\npurposes, including criminal investigations. Access or use of this\ncomputer system by any person whether authorized or unauthorized,\nCONSTITUTES CONSENT to these terms.\n\n**WARNING**WARNING**WARNING**WARNING**WARNING**WARNING**WARNING**WARNING**</body></message>"),
142 Err(())
143 );
144 }
145
146 #[test]
147 fn parse_awips() {
148 let timestamp = chrono::DateTime::from_naive_utc_and_offset(
149 chrono::NaiveDate::from_ymd_opt(2022, 2, 4)
150 .unwrap()
151 .and_hms_opt(2, 54, 0)
152 .unwrap(),
153 chrono::FixedOffset::east_opt(0).unwrap(),
154 );
155
156 assert_eq!(
157 msg("<message xmlns=\"jabber:client\" to=\"w.glynn@nwws-oi.weather.gov/uuid/25976f21-a846-4e08-8890-d750a95d96a2\" type=\"groupchat\" from=\"nwws@conference.nwws-oi.weather.gov/nwws-oi\"><body>KLMK issues RRM valid 2022-02-04T02:54:00Z</body><html xmlns=\"http://jabber.org/protocol/xhtml-im\"><body xmlns=\"http://www.w3.org/1999/xhtml\">KLMK issues RRM valid 2022-02-04T02:54:00Z</body></html><x xmlns=\"nwws-oi\" cccc=\"KLMK\" ttaaii=\"SRUS43\" issue=\"2022-02-04T02:54:00Z\" awipsid=\"RRMLMK\" id=\"14425.25117\"><![CDATA[\n\n987\n\nSRUS43 KLMK 040254\n\nRRMLMK\n\n.ER PRSK2 20220203 Z DC202202040254/DUE/DQG/DH17/HGIFE/DIH1/\n\n.E1 15.4/15.6/15.8/16.1/16.5/17.0/17.6/18.1\n\n.E2 18.6/18.8/18.8/18.9/19.2/19.2/19.3/19.3\n\n.E3 19.2/19.2/19.2/19.1/19.0/19.0/18.8/18.7\n\n.E4 18.6/18.4/18.4/18.4/18.4/18.3/18.2/18.1\n\n.E5 18.1/18.0/17.9/17.9/17.9/17.7/17.7/17.6\n\n.E6 17.5/17.6/17.5/17.4/17.3/17.2/17.2/17.0\n\n]]></x><delay xmlns=\"urn:xmpp:delay\" stamp=\"2022-02-04T02:55:11.810Z\" from=\"nwws@conference.nwws-oi.weather.gov/nwws-oi\"/></message>"),
158 Ok(Message {
159 ttaaii: "SRUS43".into(),
160 cccc: "KLMK".into(),
161 awips_id: Some(
162 "RRMLMK".into()
163 ),
164 issue: timestamp,
165 id: "14425.25117".into(),
166 delay_stamp: timestamp.with_minute(55).unwrap().with_second(11).unwrap().with_nanosecond(810_000_000),
167 ldm_sequence_number: Some(987),
168 message: "SRUS43 KLMK 040254\nRRMLMK\n.ER PRSK2 20220203 Z DC202202040254/DUE/DQG/DH17/HGIFE/DIH1/\n.E1 15.4/15.6/15.8/16.1/16.5/17.0/17.6/18.1\n.E2 18.6/18.8/18.8/18.9/19.2/19.2/19.3/19.3\n.E3 19.2/19.2/19.2/19.1/19.0/19.0/18.8/18.7\n.E4 18.6/18.4/18.4/18.4/18.4/18.3/18.2/18.1\n.E5 18.1/18.0/17.9/17.9/17.9/17.7/17.7/17.6\n.E6 17.5/17.6/17.5/17.4/17.3/17.2/17.2/17.0\n".into(),
169 })
170 );
171
172 assert_eq!(
173 msg("<message xmlns=\"jabber:client\" to=\"w.glynn@nwws-oi.weather.gov/uuid/851c737e-ead3-460d-b0a6-6749602fccd9\" type=\"groupchat\" from=\"nwws@conference.nwws-oi.weather.gov/nwws-oi\"><body>PAJK issues RR3 valid 2022-02-04T02:11:00Z</body><html xmlns=\"http://jabber.org/protocol/xhtml-im\"><body xmlns=\"http://www.w3.org/1999/xhtml\">PAJK issues RR3 valid 2022-02-04T02:11:00Z</body></html><x xmlns=\"nwws-oi\" cccc=\"PAJK\" ttaaii=\"SRAK57\" issue=\"2022-02-04T02:11:00Z\" awipsid=\"RR3AJK\" id=\"14425.24041\"><![CDATA[\n\n876\n\nSRAK57 PAJK 040211\n\nRR3AJK\n\nSRAK57 PAJK 040210\n\n\n\n.A NDIA2 220204 Z DH0202/TA 26/TD 27/UD 0/US 0/UG 0/UP 0/PA 29.57\n\n]]></x></message>"),
174 Ok(Message {
175 ttaaii: "SRAK57".into(),
176 cccc: "PAJK".into(),
177 awips_id: Some("RR3AJK".into()),
178 issue: timestamp.with_minute(11).unwrap(),
179 id: "14425.24041".into(),
180 delay_stamp: None,
181 ldm_sequence_number: Some(876),
182 message: "SRAK57 PAJK 040211\nRR3AJK\nSRAK57 PAJK 040210\n\n.A NDIA2 220204 Z DH0202/TA 26/TD 27/UD 0/US 0/UG 0/UP 0/PA 29.57\n".into(),
183 }));
184
185 assert_eq!(
186 msg("<message xmlns=\"jabber:client\" to=\"w.glynn@nwws-oi.weather.gov/uuid/851c737e-ead3-460d-b0a6-6749602fccd9\" type=\"groupchat\" from=\"nwws@conference.nwws-oi.weather.gov/nwws-oi\"><body>KKCI issues CFP valid 2022-02-04T02:00:00Z</body><html xmlns=\"http://jabber.org/protocol/xhtml-im\"><body xmlns=\"http://www.w3.org/1999/xhtml\">KKCI issues CFP valid 2022-02-04T02:00:00Z</body></html><x xmlns=\"nwws-oi\" cccc=\"KKCI\" ttaaii=\"FAUS29\" issue=\"2022-02-04T02:00:00Z\" awipsid=\"CFP03\" id=\"14425.22838\"><![CDATA[\n\n631\n\nFAUS29 KKCI 040200\n\nCFP03 \n\nCCFP 20220204_0200 20220204_0800\n\nCANADA OFF\n\n]]></x></message>"),
187 Ok(Message{
188 ttaaii: "FAUS29".to_string(),
189 cccc: "KKCI".to_string(),
190 awips_id: Some("CFP03".into()),
191 issue: timestamp.with_minute(0).unwrap(),
192 id: "14425.22838".into(),
193 delay_stamp: None,
194 ldm_sequence_number: Some(631),
195 message: "FAUS29 KKCI 040200\nCFP03 \nCCFP 20220204_0200 20220204_0800\nCANADA OFF\n".into()
196 }));
197 }
198
199 #[test]
200 fn parse_test() {
201 assert_eq!(
202 msg("<message xmlns=\"jabber:client\" to=\"w.glynn@nwws-oi.weather.gov/uuid/851c737e-ead3-460d-b0a6-6749602fccd9\" type=\"groupchat\" from=\"nwws@conference.nwws-oi.weather.gov/nwws-oi\"><body>PHEB issues valid 2022-02-04T01:23:00Z</body><html xmlns=\"http://jabber.org/protocol/xhtml-im\"><body xmlns=\"http://www.w3.org/1999/xhtml\">PHEB issues valid 2022-02-04T01:23:00Z</body></html><x xmlns=\"nwws-oi\" cccc=\"PHEB\" ttaaii=\"NTXX98\" issue=\"2022-02-04T01:23:00Z\" awipsid=\"\" id=\"14425.22800\"><![CDATA[\n\n593\n\nNTXX98 PHEB 040123\n\nPTWC REDUNDANT-SIDE TEST FROM IRC\n\nRZRZRZRZRZRZRZRZRZRZRZRZRZRZRZRZRZRZRZRZRZRZ\n\nRZRZRZRZRZRZRZRZRZRZRZRZRZRZRZRZRZRZRZRZRZRZ\n\n]]></x></message>"),
203 Ok(Message {
204 ttaaii: "NTXX98".into(),
205 cccc: "PHEB".into(),
206 awips_id: None,
207 issue: chrono::DateTime::from_naive_utc_and_offset(chrono::NaiveDate::from_ymd_opt(2022, 2, 4).unwrap().and_hms_opt(1, 23, 0).unwrap(), chrono::FixedOffset::east_opt(0).unwrap()),
208 id: "14425.22800".into(),
209 delay_stamp: None,
210 ldm_sequence_number: Some(593),
211 message: "NTXX98 PHEB 040123\nPTWC REDUNDANT-SIDE TEST FROM IRC\nRZRZRZRZRZRZRZRZRZRZRZRZRZRZRZRZRZRZRZRZRZRZ\nRZRZRZRZRZRZRZRZRZRZRZRZRZRZRZRZRZRZRZRZRZRZ\n".into(),
212 })
213 );
214 }
215}