cot_proto/tak/
detail.rs

1use crate::base::serialize_date;
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4
5use crate::base::deserialize_date;
6
7/// Type definitions for CoT detail sections for TAK messages.
8///
9/// Limited message types supported so far.
10
11/// `<detail>` section for a Marker message, with reasonable defaults to put a dot on a map (i.e.
12/// when sent to TAK).
13/// Note: ATAK's "Marker*.xsd" schemas don't list these elements as optional,
14/// (i.e. missing `maxOccurs="0"`) but I was told by a dev that many are optional.
15#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
16pub struct TakMarkerDetail {
17    pub status: Status,
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub link: Option<Link>,
20    pub contact: Contact,
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub remarks: Option<Remarks>,
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub color: Option<Color>,
25    pub precisionlocation: PrecisionLocation,
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub usericon: Option<UserIcon>,
28}
29
30// TODO move these common definitions
31#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
32pub struct Status {
33    #[serde(rename = "@readiness")]
34    pub readiness: bool,
35}
36impl Default for Status {
37    fn default() -> Self {
38        Status { readiness: true }
39    }
40}
41
42#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
43pub struct Link {
44    #[serde(rename = "@uid")]
45    uid: String,
46    #[serde(
47        rename = "@production_time",
48        serialize_with = "serialize_date",
49        deserialize_with = "deserialize_date"
50    )]
51    pub production_time: DateTime<Utc>,
52    #[serde(rename = "@type")]
53    pub cot_type: String,
54    #[serde(rename = "@parent_callsign")]
55    pub parent_callsign: String,
56    #[serde(rename = "@relation")]
57    pub relation: String,
58}
59
60#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
61pub struct Contact {
62    #[serde(rename = "@callsign")]
63    pub callsign: String,
64    #[serde(rename = "@emailAddress", skip_serializing_if = "Option::is_none")]
65    pub email_address: Option<String>,
66    #[serde(rename = "@endpoint", skip_serializing_if = "Option::is_none")]
67    pub endpoint: Option<String>,
68    #[serde(rename = "@phone", skip_serializing_if = "Option::is_none")]
69    pub phone: Option<u32>,
70    #[serde(rename = "@xmppUsername", skip_serializing_if = "Option::is_none")]
71    pub xmpp_username: Option<String>,
72}
73impl Default for Contact {
74    fn default() -> Self {
75        Contact {
76            callsign: "???".to_string(),
77            email_address: None,
78            endpoint: None,
79            phone: None,
80            xmpp_username: None,
81        }
82    }
83}
84
85#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
86pub struct Remarks {
87    // TODO is is probably not right
88    #[serde(rename = "$value")]
89    pub source: Option<Vec<String>>,
90}
91
92#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
93pub struct Color {
94    #[serde(rename = "@argb")]
95    pub argb: i32,
96}
97
98#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
99pub struct PrecisionLocation {
100    #[serde(rename = "@altsrc")]
101    pub altsrc: String,
102    #[serde(rename = "@geopointsrc", skip_serializing_if = "Option::is_none")]
103    pub geopointsrc: Option<String>,
104    #[serde(
105        rename = "@PRECISE_IMAGE_FILE",
106        skip_serializing_if = "Option::is_none"
107    )]
108    pub pi_file: Option<String>,
109    #[serde(
110        rename = "@PRECISE_IMAGE_FILE_X",
111        skip_serializing_if = "Option::is_none"
112    )]
113    pub pi_file_x: Option<String>,
114    #[serde(
115        rename = "@PRECISE_IMAGE_FILE_Y",
116        skip_serializing_if = "Option::is_none"
117    )]
118    pub pi_file_y: Option<String>,
119}
120
121impl Default for PrecisionLocation {
122    fn default() -> Self {
123        Self {
124            altsrc: "???".to_string(),
125            geopointsrc: None,
126            pi_file: None,
127            pi_file_x: None,
128            pi_file_y: None,
129        }
130    }
131}
132
133#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
134pub struct UserIcon {
135    #[serde(rename = "@iconsetpath")]
136    iconsetpath: String,
137}
138
139#[cfg(test)]
140mod test {
141    use crate::base::Cot;
142
143    use super::*;
144    #[test]
145    fn test_deserialize_tak_marker() {
146        let xml_path = format!(
147            "{}/src/tak/examples/marker-2525.cot",
148            env!("CARGO_MANIFEST_DIR")
149        );
150        let xml_text = std::fs::read_to_string(&xml_path).unwrap();
151        let marker: Cot<TakMarkerDetail> = quick_xml::de::from_str(&xml_text).unwrap();
152        assert_eq!(marker.version, "2.0");
153    }
154}