takproto 0.4.2

Rust library for TAK (Team Awareness Kit) Protocol - send CoT messages to TAK servers with mTLS support
Documentation
use crate::proto::CotEvent;
use std::time::{SystemTime, UNIX_EPOCH};

/// Convert milliseconds since Unix epoch to TAK time format
fn ms_to_tak_time(ms: u64) -> String {
    let secs = ms / 1000;
    let dt = chrono::DateTime::from_timestamp(secs as i64, 0)
        .unwrap_or_else(|| chrono::Utc::now());
    dt.format("%Y-%m-%dT%H:%M:%S.%3fZ").to_string()
}

/// Encode a CotEvent as XML (Protocol Version 0)
pub fn encode_cot_event_xml(event: &CotEvent) -> String {
    let send_time = ms_to_tak_time(event.send_time);
    let start_time = ms_to_tak_time(event.start_time);
    let stale_time = ms_to_tak_time(event.stale_time);

    let mut xml = format!(
        r#"<?xml version="1.0" encoding="UTF-8"?>
<event version="2.0" uid="{}" type="{}" time="{}" start="{}" stale="{}" how="{}">"#,
        event.uid, event.r#type, send_time, start_time, stale_time, event.how
    );

    // Add point element
    xml.push_str(&format!(
        r#"
  <point lat="{}" lon="{}" hae="{}" ce="{}" le="{}"/>"#,
        event.lat, event.lon, event.hae, event.ce, event.le
    ));

    // Add detail element if present
    if let Some(ref detail) = event.detail {
        xml.push_str("\n  <detail>");

        // Add contact if present
        if let Some(ref contact) = detail.contact {
            xml.push_str(&format!(
                r#"
    <contact callsign="{}" endpoint="{}"/>"#,
                contact.callsign, contact.endpoint
            ));
        }

        // Add group if present
        if let Some(ref group) = detail.group {
            xml.push_str(&format!(
                r#"
    <__group name="{}" role="{}"/>"#,
                group.name, group.role
            ));
        }

        // Add takv if present
        if let Some(ref takv) = detail.takv {
            xml.push_str(&format!(
                r#"
    <takv version="{}" platform="{}" os="{}" device="{}"/>"#,
                takv.version, takv.platform, takv.os, takv.device
            ));
        }

        // Add raw XML detail if present
        if !detail.xml_detail.is_empty() {
            xml.push_str("\n    ");
            xml.push_str(&detail.xml_detail);
        }

        xml.push_str("\n  </detail>");
    }

    xml.push_str("\n</event>");
    xml
}

/// Create a TAK protocol negotiation request message (XML)
pub fn create_protocol_request(version: u32) -> String {
    let now = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_millis() as u64;
    let time_str = ms_to_tak_time(now);

    format!(
        r#"<?xml version="1.0" encoding="UTF-8"?>
<event version="2.0" uid="protouid" type="t-x-takp-q" time="{}" start="{}" stale="{}" how="m-g">
  <point lat="0.0" lon="0.0" hae="0.0" ce="999999" le="999999"/>
  <detail>
    <TakControl>
      <TakRequest version="{}"/>
    </TakControl>
  </detail>
</event>"#,
        time_str, time_str, time_str, version
    )
}

/// Parse a simple XML message to check if it's a protocol support message
pub fn is_protocol_support(xml: &str) -> bool {
    xml.contains("t-x-takp-v") && xml.contains("<TakProtocolSupport")
}

/// Parse a simple XML message to check if it's a protocol response
pub fn is_protocol_response(xml: &str) -> bool {
    xml.contains("t-x-takp-r") && xml.contains("<TakResponse")
}

/// Check if protocol response is successful
pub fn is_protocol_response_success(xml: &str) -> bool {
    xml.contains(r#"status="true""#)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_encode_basic_event() {
        let event = CotEvent {
            r#type: "a-f-G-U-C".to_string(),
            uid: "TEST-1".to_string(),
            send_time: 1234567890000,
            start_time: 1234567890000,
            stale_time: 1234567950000,
            how: "m-g".to_string(),
            lat: 37.7749,
            lon: -122.4194,
            hae: 10.0,
            ce: 9.9,
            le: 9.9,
            ..Default::default()
        };

        let xml = encode_cot_event_xml(&event);
        assert!(xml.contains(r#"uid="TEST-1""#));
        assert!(xml.contains(r#"type="a-f-G-U-C""#));
        assert!(xml.contains(r#"lat="37.7749""#));
    }

    #[test]
    fn test_protocol_request() {
        let xml = create_protocol_request(1);
        assert!(xml.contains(r#"type="t-x-takp-q""#));
        assert!(xml.contains(r#"<TakRequest version="1"/>"#));
    }
}