cot_proto/
base.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
use chrono::{DateTime, Utc};
use quick_xml::Reader;
use serde::de::Error as DeError;
use serde::{Deserialize, Deserializer, Serialize, Serializer};

use crate::Error;

/// Base schema structure and ser/de.

// See References section in README.md
pub const COT_BASE_EXAMPLE: &str = r#"
<?xml version='1.0' standalone='yes'?>
<event version="2.0"
 uid="J-01334"
 type="a-h-A-M-F-U-M"
 time="2005-04-05T11:43:38.07Z"
 start="2005-04-05T11:43:38.07Z"
 stale="2005-04-05T11:45:38.07Z" >
 <detail>
 </detail>
 <point lat="30.0090027" lon="-85.9578735" ce="45.3"
 hae="-42.6" le="99.5" />
</event>
"#;

#[derive(Serialize, Deserialize, Debug, PartialEq)]
#[serde(rename = "event")]
pub struct Cot<D> {
    #[serde(rename = "@version")]
    pub version: String,
    #[serde(rename = "@uid")]
    pub uid: String,
    #[serde(rename = "@type")]
    pub cot_type: String,
    #[serde(
        rename = "@time",
        serialize_with = "serialize_date",
        deserialize_with = "deserialize_date"
    )]
    pub time: DateTime<Utc>,
    #[serde(
        rename = "@start",
        serialize_with = "serialize_date",
        deserialize_with = "deserialize_date"
    )]
    pub start: DateTime<Utc>,
    #[serde(
        rename = "@stale",
        serialize_with = "serialize_date",
        deserialize_with = "deserialize_date"
    )]
    pub stale: DateTime<Utc>,
    #[serde(rename = "detail")]
    pub detail: D,
    #[serde(rename = "point")]
    pub point: Point,
}

/// Parse `type` attribute from a CoT message XML string.
pub fn parse_cot_msg_type(text: &str) -> Result<String, Error> {
    match xml_first_element_w_attr(text, "event", "type") {
        Ok(Some(val)) => Ok(val),
        _ => Err(Error::BadField("No element 'event' with attribute 'type'")),
    }
}

/// XML parsing convenience
pub fn xml_first_element_w_attr(
    text: &str,
    elt_name: &str,
    attr_name: &str,
) -> Result<Option<String>, Error> {
    let mut reader = Reader::from_str(text);
    reader.config_mut().trim_text(true);
    loop {
        match reader.read_event()? {
            // Parse attribute `type` in the `event` element.
            quick_xml::events::Event::Start(ref e) => {
                if e.name().into_inner() == elt_name.as_bytes() {
                    for attr in e.attributes() {
                        let attr = attr?;
                        if attr.key.into_inner() == attr_name.as_bytes() {
                            return Ok(Some(String::from_utf8_lossy(&attr.value).to_string()));
                        }
                    }
                }
            }
            quick_xml::events::Event::Eof => break,
            _ => {}
        }
    }
    Ok(None)
}

pub(crate) fn serialize_date<S>(date: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
where
    S: Serializer,
{
    let s = date.to_rfc3339();
    serializer.serialize_str(&s)
}

pub(crate) fn deserialize_date<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
where
    D: Deserializer<'de>,
{
    let s = String::deserialize(deserializer)?;
    DateTime::parse_from_rfc3339(&s)
        .map_err(DeError::custom)
        .map(|dt| dt.with_timezone(&Utc))
}

pub type CotBase = Cot<NoDetail>;

#[derive(Serialize, Deserialize, Debug, PartialEq)]
pub struct NoDetail {}

#[derive(Serialize, Deserialize, Debug, PartialEq)]
pub struct Point {
    #[serde(rename = "@lat")]
    pub lat: f64,
    #[serde(rename = "@lon")]
    pub lon: f64,
    #[serde(rename = "@ce")]
    pub ce: f32,
    #[serde(rename = "@hae")]
    pub hae: f32,
    #[serde(rename = "@le")]
    pub le: f32,
}

impl Point {
    pub fn north_pole() -> Self {
        Self {
            lat: 90.0,
            lon: 0.0,
            ce: 0.0,
            hae: 0.0,
            le: 0.0,
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;
    #[test]
    fn test_serde_roundtrip() {
        // Create two Cot objects, one from example string and another from a round trip from that
        // to a string and back again. Validate values match.
        let cot0: CotBase = quick_xml::de::from_str(COT_BASE_EXAMPLE).unwrap();
        let cot_str = quick_xml::se::to_string(&cot0).unwrap();
        let cot1: CotBase = quick_xml::de::from_str(&cot_str).unwrap();
        assert_eq!(cot0, cot1);
    }
}