mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
TAKPacket V2 support
This commit is contained in:
parent
af77d7a5a5
commit
f25575b6d6
5 changed files with 1475 additions and 5 deletions
|
|
@ -11,6 +11,7 @@
|
|||
102B5EAD2E172F41003D191E /* DatadogCrashReporting in Frameworks */ = {isa = PBXBuildFile; productRef = 102B5EAC2E172F41003D191E /* DatadogCrashReporting */; };
|
||||
102B5EAF2E172F41003D191E /* DatadogLogs in Frameworks */ = {isa = PBXBuildFile; productRef = 102B5EAE2E172F41003D191E /* DatadogLogs */; };
|
||||
102B5EB12E172F41003D191E /* DatadogRUM in Frameworks */ = {isa = PBXBuildFile; productRef = 102B5EB02E172F41003D191E /* DatadogRUM */; };
|
||||
10794B232F8713CB000CA23F /* MeshtasticTAK in Frameworks */ = {isa = PBXBuildFile; productRef = 10794B222F8713CB000CA23F /* MeshtasticTAK */; };
|
||||
108FFECB2DD3F43C00BFAA81 /* ShareContactQRDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 108FFECA2DD3F43C00BFAA81 /* ShareContactQRDialog.swift */; };
|
||||
108FFECD2DD4005600BFAA81 /* NodeInfoEntityToNodeInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 108FFECC2DD4005600BFAA81 /* NodeInfoEntityToNodeInfo.swift */; };
|
||||
10D109F22E2047D600536CE6 /* DatadogSessionReplay in Frameworks */ = {isa = PBXBuildFile; productRef = 10D109F12E2047D600536CE6 /* DatadogSessionReplay */; };
|
||||
|
|
@ -102,8 +103,8 @@
|
|||
8EED425B7820DA4FEB40C375 /* CoTXMLParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 748E4806582595DE80D455CD /* CoTXMLParser.swift */; };
|
||||
9604373EEB96801AA89DF48C /* EXICodec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D0A8ABAEF1E587683970927 /* EXICodec.swift */; };
|
||||
A5339E2F74E83F8FC41EEE33 /* TAKServerConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0618E6D0DF90B74EE32E6C06 /* TAKServerConfig.swift */; };
|
||||
AA0001032F07A4B000600001 /* TAKModuleConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA0001042F07A4B000600001 /* TAKModuleConfig.swift */; };
|
||||
AA0001012E2730EC00600001 /* ConnectViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA00010022E2730EC0060000 /* ConnectViewTests.swift */; };
|
||||
AA0001032F07A4B000600001 /* TAKModuleConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA0001042F07A4B000600001 /* TAKModuleConfig.swift */; };
|
||||
ABA8E6402E2F2A2300E27791 /* AppIconButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABA8E63F2E2F2A2300E27791 /* AppIconButton.swift */; };
|
||||
ABB99DEB2E2EA1C500CFBD05 /* AppIconPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABB99DEA2E2EA1C500CFBD05 /* AppIconPicker.swift */; };
|
||||
B16C760DB291CFAB5335EADB /* TAKCertificateManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09936BEBD6D82479B2360FDC /* TAKCertificateManager.swift */; };
|
||||
|
|
@ -352,7 +353,6 @@
|
|||
01028778B8BFD81F7A039593 /* TAKConnection.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TAKConnection.swift; sourceTree = "<group>"; };
|
||||
0618E6D0DF90B74EE32E6C06 /* TAKServerConfig.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TAKServerConfig.swift; sourceTree = "<group>"; };
|
||||
09936BEBD6D82479B2360FDC /* TAKCertificateManager.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TAKCertificateManager.swift; sourceTree = "<group>"; };
|
||||
AA0001042F07A4B000600001 /* TAKModuleConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TAKModuleConfig.swift; sourceTree = "<group>"; };
|
||||
108FFECA2DD3F43C00BFAA81 /* ShareContactQRDialog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareContactQRDialog.swift; sourceTree = "<group>"; };
|
||||
108FFECC2DD4005600BFAA81 /* NodeInfoEntityToNodeInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeInfoEntityToNodeInfo.swift; sourceTree = "<group>"; };
|
||||
1D5AD8037A0D583C614B0597 /* Tools.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tools.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -440,6 +440,7 @@
|
|||
9155703C39B55FC9DDF3E4C1 /* TAKDataPackageGenerator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TAKDataPackageGenerator.swift; sourceTree = "<group>"; };
|
||||
AA00010022E2730EC0060000 /* ConnectViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectViewTests.swift; sourceTree = "<group>"; };
|
||||
AA0001022F07A4B000600001 /* MeshtasticDataModelV 57.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 57.xcdatamodel"; sourceTree = "<group>"; };
|
||||
AA0001042F07A4B000600001 /* TAKModuleConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TAKModuleConfig.swift; sourceTree = "<group>"; };
|
||||
ABA8E63F2E2F2A2300E27791 /* AppIconButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIconButton.swift; sourceTree = "<group>"; };
|
||||
ABB99DEA2E2EA1C500CFBD05 /* AppIconPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIconPicker.swift; sourceTree = "<group>"; };
|
||||
B399E8A32B6F486400E4488E /* RetryButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetryButton.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -730,6 +731,7 @@
|
|||
102B5EAB2E172F41003D191E /* DatadogCore in Frameworks */,
|
||||
10D109F22E2047D600536CE6 /* DatadogSessionReplay in Frameworks */,
|
||||
102B5EAF2E172F41003D191E /* DatadogLogs in Frameworks */,
|
||||
10794B232F8713CB000CA23F /* MeshtasticTAK in Frameworks */,
|
||||
102B5EB12E172F41003D191E /* DatadogRUM in Frameworks */,
|
||||
DD0D3D222A55CEB10066DB71 /* CocoaMQTT in Frameworks */,
|
||||
10D109F42E2047D600536CE6 /* DatadogTrace in Frameworks */,
|
||||
|
|
@ -1524,6 +1526,7 @@
|
|||
102B5EB02E172F41003D191E /* DatadogRUM */,
|
||||
10D109F12E2047D600536CE6 /* DatadogSessionReplay */,
|
||||
10D109F32E2047D600536CE6 /* DatadogTrace */,
|
||||
10794B222F8713CB000CA23F /* MeshtasticTAK */,
|
||||
);
|
||||
productName = MeshtasticClient;
|
||||
productReference = DDC2E15426CE248E0042C5E4 /* Meshtastic.app */;
|
||||
|
|
@ -1595,6 +1598,7 @@
|
|||
25A978B82C13F8ED0003AAE7 /* XCLocalSwiftPackageReference "MeshtasticProtobufs" */,
|
||||
259792242C2F10B600AD1659 /* XCRemoteSwiftPackageReference "swift-protobuf" */,
|
||||
102B5EA92E172F41003D191E /* XCRemoteSwiftPackageReference "dd-sdk-ios" */,
|
||||
10794B212F8713CB000CA23F /* XCLocalSwiftPackageReference "../TAKPacket-SDK/swift" */,
|
||||
);
|
||||
productRefGroup = DDC2E15526CE248E0042C5E4 /* Products */;
|
||||
projectDirPath = "";
|
||||
|
|
@ -2360,6 +2364,10 @@
|
|||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCLocalSwiftPackageReference section */
|
||||
10794B212F8713CB000CA23F /* XCLocalSwiftPackageReference "../TAKPacket-SDK/swift" */ = {
|
||||
isa = XCLocalSwiftPackageReference;
|
||||
relativePath = "../TAKPacket-SDK/swift";
|
||||
};
|
||||
25A978B82C13F8ED0003AAE7 /* XCLocalSwiftPackageReference "MeshtasticProtobufs" */ = {
|
||||
isa = XCLocalSwiftPackageReference;
|
||||
relativePath = MeshtasticProtobufs;
|
||||
|
|
@ -2415,6 +2423,10 @@
|
|||
package = 102B5EA92E172F41003D191E /* XCRemoteSwiftPackageReference "dd-sdk-ios" */;
|
||||
productName = DatadogRUM;
|
||||
};
|
||||
10794B222F8713CB000CA23F /* MeshtasticTAK */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = MeshtasticTAK;
|
||||
};
|
||||
10D109F12E2047D600536CE6 /* DatadogSessionReplay */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 102B5EA92E172F41003D191E /* XCRemoteSwiftPackageReference "dd-sdk-ios" */;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
import MeshtasticProtobufs
|
||||
import MeshtasticTAK
|
||||
import OSLog
|
||||
|
||||
extension AccessoryManager {
|
||||
|
|
@ -55,7 +56,7 @@ extension AccessoryManager {
|
|||
/// - Parameters:
|
||||
/// - takPacket: The TAKPacket protobuf to send
|
||||
/// - channel: Channel to send on (0 = default/primary)
|
||||
func sendTAKPacket(_ takPacket: TAKPacket, channel: UInt32 = 0) async throws {
|
||||
func sendTAKPacket(_ takPacket: MeshtasticProtobufs.TAKPacket, channel: UInt32 = 0) async throws {
|
||||
Logger.tak.debug("=== Sending TAKPacket to Mesh ===")
|
||||
|
||||
guard let activeConnection else {
|
||||
|
|
@ -160,7 +161,7 @@ extension AccessoryManager {
|
|||
}
|
||||
|
||||
// Parse uncompressed TAKPacket protobuf
|
||||
let takPacket: TAKPacket
|
||||
let takPacket: MeshtasticProtobufs.TAKPacket
|
||||
do {
|
||||
takPacket = try TAKPacket(serializedBytes: payload)
|
||||
} catch {
|
||||
|
|
@ -180,6 +181,86 @@ extension AccessoryManager {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - Receive TAK V2 Packet from Mesh (Port 78)
|
||||
|
||||
/// Handle incoming ATAK Plugin V2 packet from the mesh network
|
||||
/// Wire format: [flags byte][zstd-compressed TAKPacketV2 protobuf]
|
||||
/// Uses TAKPacket-SDK for decompression
|
||||
func handleATAKPluginV2Packet(_ packet: MeshPacket) {
|
||||
guard case let .decoded(data) = packet.payloadVariant else {
|
||||
Logger.tak.warning("Received ATAK V2 packet without decoded payload")
|
||||
return
|
||||
}
|
||||
|
||||
Logger.tak.debug("Received ATAK V2 packet: \(data.payload.count) bytes from node \(packet.from)")
|
||||
|
||||
let wirePayload = data.payload
|
||||
guard wirePayload.count >= 2 else {
|
||||
Logger.tak.warning("ATAK V2 payload too short: \(wirePayload.count) bytes")
|
||||
return
|
||||
}
|
||||
|
||||
// Decompress using TAKPacket-SDK
|
||||
do {
|
||||
let compressor = MeshtasticTAK.TakCompressor()
|
||||
let takPacketV2 = try compressor.decompress(wirePayload)
|
||||
|
||||
Logger.tak.info("Decompressed ATAK V2 packet from node \(packet.from): \(takPacketV2.callsign)")
|
||||
|
||||
// Convert TAKPacketV2 → CoT XML → parse to CoTMessage → broadcast
|
||||
let builder = MeshtasticTAK.CotXmlBuilder()
|
||||
let cotXml = builder.build(takPacketV2)
|
||||
|
||||
if let cotData = cotXml.data(using: .utf8) {
|
||||
let cotMessage = try CoTMessage.parseData(cotData)
|
||||
Task {
|
||||
await TAKServerManager.shared.broadcast(cotMessage)
|
||||
}
|
||||
Logger.tak.info("Forwarded ATAK V2 to TAK clients: \(cotMessage.type)")
|
||||
}
|
||||
} catch {
|
||||
Logger.tak.error("Failed to decompress ATAK V2 packet: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Send TAK V2 Packet to Mesh
|
||||
|
||||
/// Send a compressed TAK V2 wire payload to the mesh
|
||||
func sendTAKV2Packet(_ wirePayload: Data, channel: UInt32 = 0) async throws {
|
||||
guard let activeConnection else {
|
||||
throw AccessoryError.connectionFailed("Not connected to Meshtastic device")
|
||||
}
|
||||
guard let deviceNum = activeConnection.device.num else {
|
||||
throw AccessoryError.connectionFailed("No device number available")
|
||||
}
|
||||
|
||||
var dataMessage = DataMessage()
|
||||
dataMessage.portnum = .atakPluginV2 // Port 78
|
||||
dataMessage.payload = wirePayload
|
||||
|
||||
var meshPacket = MeshPacket()
|
||||
meshPacket.to = 0xFFFFFFFF // Broadcast
|
||||
meshPacket.from = UInt32(deviceNum)
|
||||
meshPacket.channel = channel
|
||||
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
|
||||
meshPacket.decoded = dataMessage
|
||||
|
||||
var toRadio = ToRadio()
|
||||
toRadio.packet = meshPacket
|
||||
|
||||
try await send(toRadio, debugDescription: "Sending TAKPacket V2 to mesh")
|
||||
Logger.tak.info("Sent TAK V2 packet to mesh (port=78, channel=\(channel), size=\(wirePayload.count) bytes)")
|
||||
}
|
||||
|
||||
/// Send a CoT message to the mesh using the V2 protocol
|
||||
func sendCoTToMeshV2(_ cotXml: String, channel: UInt32 = 0) async throws {
|
||||
let parser = MeshtasticTAK.CotXmlParser()
|
||||
let packet = parser.parse(cotXml)
|
||||
let compressor = MeshtasticTAK.TakCompressor()
|
||||
let wirePayload = try compressor.compress(packet)
|
||||
try await sendTAKV2Packet(wirePayload, channel: channel)
|
||||
}
|
||||
|
||||
// MARK: - Handle ATAK Forwarder Packet (Port 257)
|
||||
|
||||
/// Handle incoming ATAK_FORWARDER packet for generic CoT events
|
||||
|
|
|
|||
|
|
@ -644,6 +644,8 @@ class AccessoryManager: ObservableObject, MqttClientProxyManagerDelegate {
|
|||
Logger.services.info("MAX PORT NUM OF 511")
|
||||
case .atakPlugin:
|
||||
handleATAKPluginPacket(packet)
|
||||
case .atakPluginV2:
|
||||
handleATAKPluginV2Packet(packet)
|
||||
case .powerstressApp:
|
||||
Logger.mesh.info("🕸️ MESH PACKET received for Power Stress App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure", privacy: .public)")
|
||||
case .reticulumTunnelApp:
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -227,6 +227,12 @@ public enum PortNum: SwiftProtobuf.Enum, Swift.CaseIterable {
|
|||
/// ENCODING: CayenneLLP
|
||||
case cayenneApp // = 77
|
||||
|
||||
///
|
||||
/// ATAK Plugin V2
|
||||
/// Portnum for payloads from the official Meshtastic ATAK plugin using
|
||||
/// TAKPacketV2 with zstd dictionary compression.
|
||||
case atakPluginV2 // = 78
|
||||
|
||||
///
|
||||
/// GroupAlarm integration
|
||||
/// Used for transporting GroupAlarm-related messages between Meshtastic nodes
|
||||
|
|
@ -287,6 +293,7 @@ public enum PortNum: SwiftProtobuf.Enum, Swift.CaseIterable {
|
|||
case 75: self = .lorawanBridge
|
||||
case 76: self = .reticulumTunnelApp
|
||||
case 77: self = .cayenneApp
|
||||
case 78: self = .atakPluginV2
|
||||
case 112: self = .groupalarmApp
|
||||
case 256: self = .privateApp
|
||||
case 257: self = .atakForwarder
|
||||
|
|
@ -329,6 +336,7 @@ public enum PortNum: SwiftProtobuf.Enum, Swift.CaseIterable {
|
|||
case .lorawanBridge: return 75
|
||||
case .reticulumTunnelApp: return 76
|
||||
case .cayenneApp: return 77
|
||||
case .atakPluginV2: return 78
|
||||
case .groupalarmApp: return 112
|
||||
case .privateApp: return 256
|
||||
case .atakForwarder: return 257
|
||||
|
|
@ -371,6 +379,7 @@ public enum PortNum: SwiftProtobuf.Enum, Swift.CaseIterable {
|
|||
.lorawanBridge,
|
||||
.reticulumTunnelApp,
|
||||
.cayenneApp,
|
||||
.atakPluginV2,
|
||||
.groupalarmApp,
|
||||
.privateApp,
|
||||
.atakForwarder,
|
||||
|
|
@ -382,5 +391,5 @@ public enum PortNum: SwiftProtobuf.Enum, Swift.CaseIterable {
|
|||
// MARK: - Code below here is support for the SwiftProtobuf runtime.
|
||||
|
||||
extension PortNum: SwiftProtobuf._ProtoNameProviding {
|
||||
public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0UNKNOWN_APP\0\u{1}TEXT_MESSAGE_APP\0\u{1}REMOTE_HARDWARE_APP\0\u{1}POSITION_APP\0\u{1}NODEINFO_APP\0\u{1}ROUTING_APP\0\u{1}ADMIN_APP\0\u{1}TEXT_MESSAGE_COMPRESSED_APP\0\u{1}WAYPOINT_APP\0\u{1}AUDIO_APP\0\u{1}DETECTION_SENSOR_APP\0\u{1}ALERT_APP\0\u{1}KEY_VERIFICATION_APP\0\u{2}\u{14}REPLY_APP\0\u{1}IP_TUNNEL_APP\0\u{1}PAXCOUNTER_APP\0\u{1}STORE_FORWARD_PLUSPLUS_APP\0\u{1}NODE_STATUS_APP\0\u{2}\u{1c}SERIAL_APP\0\u{1}STORE_FORWARD_APP\0\u{1}RANGE_TEST_APP\0\u{1}TELEMETRY_APP\0\u{1}ZPS_APP\0\u{1}SIMULATOR_APP\0\u{1}TRACEROUTE_APP\0\u{1}NEIGHBORINFO_APP\0\u{1}ATAK_PLUGIN\0\u{1}MAP_REPORT_APP\0\u{1}POWERSTRESS_APP\0\u{1}LORAWAN_BRIDGE\0\u{1}RETICULUM_TUNNEL_APP\0\u{1}CAYENNE_APP\0\u{2}#GROUPALARM_APP\0\u{2}P\u{2}PRIVATE_APP\0\u{1}ATAK_FORWARDER\0\u{2}~\u{3}MAX\0")
|
||||
public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0UNKNOWN_APP\0\u{1}TEXT_MESSAGE_APP\0\u{1}REMOTE_HARDWARE_APP\0\u{1}POSITION_APP\0\u{1}NODEINFO_APP\0\u{1}ROUTING_APP\0\u{1}ADMIN_APP\0\u{1}TEXT_MESSAGE_COMPRESSED_APP\0\u{1}WAYPOINT_APP\0\u{1}AUDIO_APP\0\u{1}DETECTION_SENSOR_APP\0\u{1}ALERT_APP\0\u{1}KEY_VERIFICATION_APP\0\u{2}\u{14}REPLY_APP\0\u{1}IP_TUNNEL_APP\0\u{1}PAXCOUNTER_APP\0\u{1}STORE_FORWARD_PLUSPLUS_APP\0\u{1}NODE_STATUS_APP\0\u{2}\u{1c}SERIAL_APP\0\u{1}STORE_FORWARD_APP\0\u{1}RANGE_TEST_APP\0\u{1}TELEMETRY_APP\0\u{1}ZPS_APP\0\u{1}SIMULATOR_APP\0\u{1}TRACEROUTE_APP\0\u{1}NEIGHBORINFO_APP\0\u{1}ATAK_PLUGIN\0\u{1}MAP_REPORT_APP\0\u{1}POWERSTRESS_APP\0\u{1}LORAWAN_BRIDGE\0\u{1}RETICULUM_TUNNEL_APP\0\u{1}CAYENNE_APP\0\u{1}ATAK_PLUGIN_V2\0\u{2}\"GROUPALARM_APP\0\u{2}P\u{2}PRIVATE_APP\0\u{1}ATAK_FORWARDER\0\u{2}~\u{3}MAX\0")
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue