TAKPacket V2 support

This commit is contained in:
Ben Meadors 2026-04-08 19:25:30 -05:00
parent af77d7a5a5
commit f25575b6d6
5 changed files with 1475 additions and 5 deletions

View file

@ -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" */;

View file

@ -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

View file

@ -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

View file

@ -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")
}