diff --git a/Cargo.lock b/Cargo.lock index 8fd4b8a..647e607 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -75,7 +75,6 @@ dependencies = [ "lazy_static", "log", "log4rs", - "once_cell", "serde", "uuid", ] diff --git a/Cargo.toml b/Cargo.toml index 880eb59..05b2867 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,6 @@ chrono = "0.4.39" lazy_static = "1.5.0" log = "0.4.22" log4rs = "1.3.0" -once_cell = "1.19.0" serde = { version = "1.0.210", features = ["derive"] } [dependencies.uuid] diff --git a/addons/main/functions/api/fn_send_digital_pointer_cot.sqf b/addons/main/functions/api/fn_send_digital_pointer_cot.sqf index e1be95e..f1ab107 100644 --- a/addons/main/functions/api/fn_send_digital_pointer_cot.sqf +++ b/addons/main/functions/api/fn_send_digital_pointer_cot.sqf @@ -10,5 +10,5 @@ if (!isNull _digitalPointer) then { _dpCot = [_link_uid, _contact_callsign, _digitalPointerPosition select 1, _digitalPointerPosition select 2, _digitalPointerPosition select 3]; - "armatak" callExtension ["tcp_socket:send_digital_pointer_cot", [_dpCot]]; + "armatak" callExtension ["tcp_socket:cot:digital_pointer", [_dpCot]]; }; diff --git a/addons/main/functions/api/fn_send_enemy_cot.sqf b/addons/main/functions/api/fn_send_enemy_cot.sqf index fd2f528..bc3a335 100644 --- a/addons/main/functions/api/fn_send_enemy_cot.sqf +++ b/addons/main/functions/api/fn_send_enemy_cot.sqf @@ -12,4 +12,4 @@ _callsign = _unit call armatak_fnc_extract_marker_callsign; _marker_cot = [_uuid, _type, _unit_position select 1, _unit_position select 2, _unit_position select 3, _callsign, _unit_position select 5, _unit_position select 6]; -"armatak" callExtension ["tcp_socket:send_marker_cot", [_marker_cot]]; +"armatak" callExtension ["tcp_socket:cot:marker", [_marker_cot]]; diff --git a/addons/main/functions/api/fn_send_eud_cot.sqf b/addons/main/functions/api/fn_send_eud_cot.sqf index a235757..436679d 100644 --- a/addons/main/functions/api/fn_send_eud_cot.sqf +++ b/addons/main/functions/api/fn_send_eud_cot.sqf @@ -9,4 +9,4 @@ _position = _unit call armatak_client_fnc_extractClientPosition; _uuid = _unit call armatak_fnc_extract_uuid; _eud_cot = [_uuid, _position select 1, _position select 2, _position select 3, _callsign, _group_name, _group_role, _position select 5, _position select 6]; -"armatak" callExtension ["tcp_socket:send_eud_cot", [_eud_cot]]; +"armatak" callExtension ["tcp_socket:cot:eud", [_eud_cot]]; diff --git a/addons/main/functions/api/fn_send_marker_cot.sqf b/addons/main/functions/api/fn_send_marker_cot.sqf index 6d48e03..77ccae7 100644 --- a/addons/main/functions/api/fn_send_marker_cot.sqf +++ b/addons/main/functions/api/fn_send_marker_cot.sqf @@ -10,4 +10,4 @@ _uuid = _unit call armatak_fnc_extract_uuid; _marker_cot = [_uuid, _type, _unit_position select 1, _unit_position select 2, _unit_position select 3, _callsign, _unit_position select 5, _unit_position select 6]; -"armatak" callExtension ["tcp_socket:send_marker_cot", [_marker_cot]]; +"armatak" callExtension ["tcp_socket:cot:marker", [_marker_cot]]; diff --git a/addons/main/functions/extract_data/fn_extract_role.sqf b/addons/main/functions/extract_data/fn_extract_role.sqf index d211fb7..ace9204 100644 --- a/addons/main/functions/extract_data/fn_extract_role.sqf +++ b/addons/main/functions/extract_data/fn_extract_role.sqf @@ -9,8 +9,10 @@ private _type = "G"; private _role = "a-f-G-U-C-I"; private _side = side _unit; -if (isNil {_unit getVariable "armatak_current_side"}) then { - _side = _unit getVariable "armatak_current_side"; +if (isNil { + _unit getVariable "armatak_current_side" +}) then { + _side = _unit getVariable "armatak_current_side"; }; switch (str _side) do { @@ -27,7 +29,7 @@ switch (str _side) do { _affiliation = "u"; }; default { - _affiliation = "f"; + _affiliation = "u"; }; }; diff --git a/addons/main/functions/extract_data/fn_extract_sensor_data.sqf b/addons/main/functions/extract_data/fn_extract_sensor_data.sqf index e9f7227..5b1e047 100644 --- a/addons/main/functions/extract_data/fn_extract_sensor_data.sqf +++ b/addons/main/functions/extract_data/fn_extract_sensor_data.sqf @@ -3,15 +3,25 @@ params["_unit"]; _target = getSensorTargets (_unit); { - _unit = _x select 0; - _position = _x select 1; - _status = _x select 2; + _unit = _x select 0; + _position = _x select 1; + _status = _x select 2; - if (isNil {_unit getVariable "armatak_current_side"}) then { - _unit setVariable ["armatak_current_side", side _unit]; - }; + if (isNil { + _unit getVariable "armatak_current_side" + }) then { + _unit setVariable ["armatak_current_side", side _unit]; + }; - if (_status != "destroyed") then { - _unit call armatak_fnc_send_enemy_cot; - }; + if (_status != "destroyed" && !(_unit in armatak_server_syncedUnits)) then { + _unit_position = _unit call armatak_client_fnc_extractClientPosition; + + _uuid = _unit call armatak_fnc_extract_uuid; + _type = _unit call armatak_fnc_extract_role; + _callsign = getText (configOf _unit >> "displayName"); + + _marker_cot = [_uuid, _type, _unit_position select 1, _unit_position select 2, _unit_position select 3, _callsign, _unit_position select 5, _unit_position select 6]; + + "armatak" callExtension ["tcp_socket:cot:marker", [_marker_cot]]; + }; } forEach _target; diff --git a/src/cot/draws/circle.rs b/src/cot/draws/circle.rs new file mode 100644 index 0000000..ff76dc7 --- /dev/null +++ b/src/cot/draws/circle.rs @@ -0,0 +1,126 @@ +use arma_rs::{FromArma, FromArmaError}; + +pub struct CircleCoTPayload { + pub uuid: String, + pub center_lat: f64, + pub center_lon: f64, + pub center_hae: f32, + pub major: f64, + pub minor: f64, + pub angle: f32, + pub callsign: String, + pub creator_uid: String, + pub creator_callsign: String, +} + +impl FromArma for CircleCoTPayload { + fn from_arma(data: String) -> Result { + let ( + uuid, + center_lat, + center_lon, + center_hae, + major, + minor, + angle, + callsign, + creator_uid, + creator_callsign, + ) = <(String, f64, f64, f32, f64, f64, f32, String, String, String)>::from_arma(data)?; + + Ok(Self { + uuid, + center_lat, + center_lon, + center_hae, + major, + minor, + angle, + callsign, + creator_uid, + creator_callsign, + }) + } +} + +pub struct ShapeCircleCoT { + pub uid: String, + pub lat: f64, + pub lon: f64, + pub hae: f32, + pub major: f64, + pub minor: f64, + pub angle: f32, + pub callsign: String, + pub creator_uid: String, + pub creator_callsign: String, +} + +impl CircleCoTPayload { + pub fn to_cot(&self) -> ShapeCircleCoT { + ShapeCircleCoT { + uid: self.uuid.clone(), + lat: self.center_lat, + lon: self.center_lon, + hae: self.center_hae, + major: self.major, + minor: self.minor, + angle: self.angle, + callsign: self.callsign.clone(), + creator_uid: self.creator_uid.clone(), + creator_callsign: self.creator_callsign.clone(), + } + } +} + +impl ShapeCircleCoT { + pub fn to_xml(&self, now: &str, stale: &str) -> String { + format!( + r#" + + + + + + + + + + <__shapeExtras cpvis="true" editable="true" /> + + + + + + + + + + + +"#, + uid = self.uid, + t = now, + stale = stale, + lat = self.lat, + lon = self.lon, + hae = self.hae, + major = self.major, + minor = self.minor, + angle = self.angle, + callsign = self.callsign, + creator_uid = self.creator_uid, + creator_callsign = self.creator_callsign + ) + } +} diff --git a/src/cot/draws/mod.rs b/src/cot/draws/mod.rs new file mode 100644 index 0000000..44ca87b --- /dev/null +++ b/src/cot/draws/mod.rs @@ -0,0 +1 @@ +pub mod circle; \ No newline at end of file diff --git a/src/cot/message.rs b/src/cot/message.rs new file mode 100644 index 0000000..84b4c84 --- /dev/null +++ b/src/cot/message.rs @@ -0,0 +1,153 @@ +use arma_rs::{FromArma, FromArmaError}; +use chrono::{Utc, Duration, SecondsFormat}; +use uuid::Uuid; + +pub struct MessagePayload { + pub sender_callsign: String, + pub chatroom: String, + pub message_text: String, + pub point_lat: f64, + pub point_lon: f64, + pub point_hae: f32, + pub sender_uid: String, +} + +impl FromArma for MessagePayload { + fn from_arma(data: String) -> Result { + let (sender_callsign, chatroom, message_text, + point_lat, point_lon, point_hae, sender_uid) = + <(String, String, String, f64, f64, f32, String)>::from_arma(data)?; + + Ok(Self { + sender_callsign, + chatroom, + message_text, + point_lat, + point_lon, + point_hae, + sender_uid, + }) + } +} + +pub struct MessageCot { + pub sender_callsign: String, + pub chatroom: String, + pub message_text: String, + pub point_lat: f64, + pub point_lon: f64, + pub point_hae: f32, + pub sender_uid: String, +} + +impl MessageCot { + pub fn from_payload(p: MessagePayload) -> Self { + Self { + sender_callsign: p.sender_callsign, + chatroom: p.chatroom, + message_text: p.message_text, + point_lat: p.point_lat, + point_lon: p.point_lon, + point_hae: p.point_hae, + sender_uid: p.sender_uid, + } + } + + pub fn to_xml(&self) -> String { + let created_time = Utc::now().to_rfc3339_opts(SecondsFormat::Millis, true); + let stale_time = (Utc::now() + Duration::days(1)) + .to_rfc3339_opts(SecondsFormat::Millis, true); + + // MESSAGE ID (random UUID) + let message_uuid = Uuid::new_v4().to_string(); + + // FULL EVENT UID + // format: GeoChat.{sender}.{chatroom}.{uuid} + let event_uid = format!( + "GeoChat.{}.{}.{}", + self.sender_uid, + self.chatroom.replace(" ", "_"), + message_uuid, + ); + + let mut xml = String::new(); + + xml.push_str(""); + + xml.push_str( + format!( + "", + event_uid, created_time, created_time, stale_time + ) + .as_str(), + ); + + xml.push_str( + format!( + "", + self.point_lat, self.point_lon, self.point_hae + ) + .as_str(), + ); + + xml.push_str(""); + + // ========== CHAT OBJECT ========== + + xml.push_str( + format!( + "<__chat parent=\"RootContactGroup\" groupOwner=\"false\" \ + messageId=\"{}\" chatroom=\"{}\" id=\"{}\" senderCallsign=\"{}\">", + message_uuid, + self.chatroom, + self.chatroom, + self.sender_callsign, + ) + .as_str(), + ); + + xml.push_str( + format!( + "", + self.sender_uid, + self.chatroom, + self.chatroom + ) + .as_str(), + ); + + xml.push_str(""); + + // ========== LINK ELEMENT ========== + xml.push_str( + format!( + "", + self.sender_uid + ) + .as_str(), + ); + + // ========== SERVER DEST ========== + // This is optional — you may remove or customize it + xml.push_str( + format!( + "<__serverdestination destinations=\"0.0.0.0:0:tcp:{}\" />", + self.sender_uid + ) + .as_str(), + ); + + // ========== MESSAGE REMARKS ========== + xml.push_str( + format!( + "{}", + self.sender_uid, self.chatroom, created_time, self.message_text + ) + .as_str(), + ); + + xml.push_str(""); + + xml + } +} diff --git a/src/cot/mod.rs b/src/cot/mod.rs index 1ae8b47..6c8ca30 100644 --- a/src/cot/mod.rs +++ b/src/cot/mod.rs @@ -1,5 +1,7 @@ +pub mod draws; pub mod cot; pub mod digital_pointer; pub mod eud; pub mod gps; +pub mod message; pub mod nato; \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index d5700cf..61aca55 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,8 @@ use arma_rs::{arma, Extension, Group}; mod structs; +mod tcp; mod tests; mod udp_socket; -mod tcp_socket; mod video_stream; mod cot; @@ -35,28 +35,43 @@ pub fn init() -> Extension { .command("local_ip", utils::address::get_local_address) .command("uuid", utils::uuid::get_uuid) .command("log", utils::log::log_info) - .group("udp_socket", + .group( + "udp_socket", Group::new() .command("start", udp_socket::start) .command("send_payload", udp_socket::send_payload) .command("send_gps_cot", udp_socket::send_gps_cot) - .command("stop", udp_socket::stop) + .command("stop", udp_socket::stop), ) .group( "tcp_socket", Group::new() - .command("start", tcp_socket::start) - .command("send_payload", tcp_socket::send_payload) - .command("send_eud_cot", tcp_socket::send_eud_cot) - .command("send_marker_cot", tcp_socket::send_marker_cot) - .command("send_digital_pointer_cot", tcp_socket::send_digital_pointer_cot) - .command("stop", tcp_socket::stop) + .command("start", tcp::start) + .command("stop", tcp::stop) + .command("send_payload", tcp::send_payload) + .group( + "cot", + Group::new() + .command("eud", tcp::cot::send_eud_cot) + .command("marker", tcp::cot::send_marker_cot) + .command("digital_pointer", tcp::cot::send_digital_pointer_cot) + .command("chat", tcp::cot::send_message_cot), + ) + .group( + "draw", + Group::new() + .command("circle", tcp::draw::send_circle_cot) + .command("ellipse", tcp::draw::send_ellipse_cot) + .command("rectangle", tcp::draw::send_rectangle_cot) + .command("free", tcp::draw::send_freedraw_cot) + .command("vector", tcp::draw::send_vectordraw_cot), + ), ) .group( "video_stream", Group::new() .command("start", video_stream::start_stream) - .command("stop", video_stream::stop_stream) + .command("stop", video_stream::stop_stream), ) .finish() } diff --git a/src/tcp/cot.rs b/src/tcp/cot.rs new file mode 100644 index 0000000..c5086bc --- /dev/null +++ b/src/tcp/cot.rs @@ -0,0 +1,32 @@ +use arma_rs::Context; + +use crate::{cot, tcp::send_payload}; + +pub fn send_eud_cot(ctx: Context, cursor_over_time: cot::eud::EudCoTPayload) -> &'static str { + let payload = cursor_over_time.to_cot().convert_to_xml(); + send_payload(ctx, payload); + + "Sending End User Device Cursor Over Time to TCP server" +} + +pub fn send_marker_cot(ctx: Context, cursor_over_time: cot::nato::MarkerCoTPayload) -> &'static str { + let payload = cursor_over_time.to_cot().convert_to_xml(); + send_payload(ctx, payload); + + "Sending Marker Cursor Over Time to TCP server" +} + +pub fn send_digital_pointer_cot(ctx: Context, cursor_over_time: cot::digital_pointer::DigitalPointerPayload) -> &'static str { + let payload = cursor_over_time.to_cot().convert_to_xml(); + send_payload(ctx, payload); + + "Sending Digital Pointer Cursor Over Time to TCP server" +} + +pub fn send_message_cot(ctx: Context, message_payload: cot::message::MessagePayload) -> &'static str { + let message_cot = cot::message::MessageCot::from_payload(message_payload); + let payload = message_cot.to_xml(); + send_payload(ctx, payload); + + "Sending Message CoT to TCP server" +} diff --git a/src/tcp/draw.rs b/src/tcp/draw.rs new file mode 100644 index 0000000..c3f859b --- /dev/null +++ b/src/tcp/draw.rs @@ -0,0 +1,34 @@ +use arma_rs::Context; + +use crate::{cot, tcp::send_payload}; + +pub fn send_circle_cot(ctx: Context, circle_payload: cot::draws::circle::CircleCoTPayload) -> &'static str { + let shape_circle_cot = circle_payload.to_cot(); + let now = chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Millis, true); + let stale = (chrono::Utc::now() + chrono::Duration::days(1)) + .to_rfc3339_opts(chrono::SecondsFormat::Millis, true); + let payload = shape_circle_cot.to_xml(&now, &stale); + send_payload(ctx, payload); + + "Sending Circle CoT to TCP server" +} + +pub fn send_ellipse_cot(ctx: Context) -> &'static str { + let _ = ctx; + "Not implemented: send_ellipse_cot" +} + +pub fn send_rectangle_cot(ctx: Context) -> &'static str { + let _ = ctx; + "Not implemented: send_ellipse_cot" +} + +pub fn send_freedraw_cot(ctx: Context) -> &'static str { + let _ = ctx; + "Not implemented: send_ellipse_cot" +} + +pub fn send_vectordraw_cot(ctx: Context) -> &'static str { + let _ = ctx; + "Not implemented: send_ellipse_cot" +} \ No newline at end of file diff --git a/src/tcp_socket.rs b/src/tcp/mod.rs similarity index 84% rename from src/tcp_socket.rs rename to src/tcp/mod.rs index 98b99f5..df7b490 100644 --- a/src/tcp_socket.rs +++ b/src/tcp/mod.rs @@ -7,7 +7,8 @@ use std::sync::mpsc::{self, Receiver, Sender}; use std::sync::{Arc, Mutex}; use std::thread; -use crate::cot; +pub mod cot; +pub mod draw; pub enum TcpCommand { SendMessage(String, Context), @@ -123,27 +124,6 @@ pub fn send_payload(ctx: Context, payload: String) -> &'static str { "Sending payload to TCP server" } -pub fn send_eud_cot(ctx: Context, cursor_over_time: cot::eud::EudCoTPayload) -> &'static str { - let payload = cursor_over_time.to_cot().convert_to_xml(); - send_payload(ctx, payload); - - "Sending End User Device Cursor Over Time to TCP server" -} - -pub fn send_marker_cot(ctx: Context, cursor_over_time: cot::nato::MarkerCoTPayload) -> &'static str { - let payload = cursor_over_time.to_cot().convert_to_xml(); - send_payload(ctx, payload); - - "Sending Marker Cursor Over Time to TCP server" -} - -pub fn send_digital_pointer_cot(ctx: Context, cursor_over_time: cot::digital_pointer::DigitalPointerPayload) -> &'static str { - let payload = cursor_over_time.to_cot().convert_to_xml(); - send_payload(ctx, payload); - - "Sending Digital Pointer Cursor Over Time to TCP server" -} - pub fn stop(ctx: Context) -> &'static str { if let Some(ref client) = *TCP_CLIENT.lock().unwrap() { client.stop();