diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 67778f10..ec788769 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -352,6 +352,7 @@ DD007BAD2AA4E91200F5FA12 /* MyInfoEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyInfoEntityExtension.swift; sourceTree = ""; }; DD007BAF2AA5981000F5FA12 /* NodeInfoEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeInfoEntityExtension.swift; sourceTree = ""; }; DD05296F2B77F454008E44CD /* MeshtasticDataModelV 26.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 26.xcdatamodel"; sourceTree = ""; }; + DD0836AB2DE7C7CB00A3A973 /* MeshtasticDataModelV 52.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 52.xcdatamodel"; sourceTree = ""; }; DD0BE30C2CB785D8000BA445 /* MeshtasticDataModelV 46.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 46.xcdatamodel"; sourceTree = ""; }; DD0BE30F2CB9FDC4000BA445 /* DetectionSensorEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectionSensorEnums.swift; sourceTree = ""; }; DD0E20FF2B892E1300F2D100 /* MeshtasticDataModelV 28.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 28.xcdatamodel"; sourceTree = ""; }; @@ -1806,7 +1807,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.6.3; + MARKETING_VERSION = 2.6.4; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1839,7 +1840,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.6.3; + MARKETING_VERSION = 2.6.4; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1870,7 +1871,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.6.3; + MARKETING_VERSION = 2.6.4; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1902,7 +1903,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.6.3; + MARKETING_VERSION = 2.6.4; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2001,6 +2002,7 @@ DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + DD0836AB2DE7C7CB00A3A973 /* MeshtasticDataModelV 52.xcdatamodel */, DD63CB4E2DD4FBEA00AFCAE2 /* MeshtasticDataModelV 51.xcdatamodel */, 233E99B32D84969500CC3A77 /* MeshtasticDataModelV 50.xcdatamodel */, 8D3F8A3D2D44B137009EAAA4 /* MeshtasticDataModelV 49.xcdatamodel */, @@ -2053,7 +2055,7 @@ DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */, DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */, ); - currentVersion = DD63CB4E2DD4FBEA00AFCAE2 /* MeshtasticDataModelV 51.xcdatamodel */; + currentVersion = DD0836AB2DE7C7CB00A3A973 /* MeshtasticDataModelV 52.xcdatamodel */; name = Meshtastic.xcdatamodeld; path = Meshtastic/Meshtastic.xcdatamodeld; sourceTree = ""; diff --git a/Meshtastic/Extensions/CoreData/ChannelEntityExtension.swift b/Meshtastic/Extensions/CoreData/ChannelEntityExtension.swift index 57babf4a..c85eef4a 100644 --- a/Meshtastic/Extensions/CoreData/ChannelEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/ChannelEntityExtension.swift @@ -32,6 +32,7 @@ extension ChannelEntity { channel.settings.psk = self.psk ?? Data() channel.role = Channel.Role(rawValue: Int(self.role)) ?? Channel.Role.secondary channel.settings.moduleSettings.positionPrecision = UInt32(self.positionPrecision) + channel.settings.moduleSettings.isClientMuted = self.mute return channel } } diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 050958e0..ff822e90 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -659,7 +659,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate manager.notifications = [ Notification( id: UUID().uuidString, - title: "Firmware Notification", + title: "Firmware Notification".localized, subtitle: "\(decodedInfo.clientNotification.level)".capitalized, content: decodedInfo.clientNotification.message, target: "settings", @@ -667,7 +667,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate ) ] manager.schedule() - Logger.data.error("⚠️ Client Notification \((try? decodedInfo.clientNotification.jsonString()) ?? "JSON Decode Failure")") + Logger.data.error("⚠️ Client Notification: \(decodedInfo.clientNotification.message, privacy: .public)") } switch decodedInfo.packet.decoded.portnum { @@ -978,6 +978,8 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate Logger.mesh.info("🕸️ MESH PACKET received for Power Stress App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure", privacy: .public)") case .reticulumTunnelApp: Logger.mesh.info("🕸️ MESH PACKET received for Reticulum Tunnel App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure", privacy: .public)") + case .keyVerificationApp: + Logger.mesh.warning("🕸️ MESH PACKET received for Key Verification App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure", privacy: .public)") } if decodedInfo.configCompleteID != 0 && decodedInfo.configCompleteID == configNonce { diff --git a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion index 1853a00f..160ee4b2 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion +++ b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - MeshtasticDataModelV 51.xcdatamodel + MeshtasticDataModelV 52.xcdatamodel diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 52.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 52.xcdatamodel/contents new file mode 100644 index 00000000..c36266d8 --- /dev/null +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 52.xcdatamodel/contents @@ -0,0 +1,506 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index 53da7355..a286b08e 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -283,17 +283,16 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) fetchedNode[0].telemetries? = NSOrderedSet(array: newTelemetries) } if nodeInfoMessage.hasUser { - /// Seeing Some crashes here ? - fetchedNode[0].user!.userId = nodeInfoMessage.user.id - fetchedNode[0].user!.num = Int64(nodeInfoMessage.num) - fetchedNode[0].user!.longName = nodeInfoMessage.user.longName - fetchedNode[0].user!.shortName = nodeInfoMessage.user.shortName - fetchedNode[0].user!.role = Int32(nodeInfoMessage.user.role.rawValue) - fetchedNode[0].user!.hwModel = String(describing: nodeInfoMessage.user.hwModel).uppercased() - fetchedNode[0].user!.hwModelId = Int32(nodeInfoMessage.user.hwModel.rawValue) + fetchedNode[0].user?.userId = nodeInfoMessage.user.id + fetchedNode[0].user?.num = Int64(nodeInfoMessage.num) + fetchedNode[0].user?.longName = nodeInfoMessage.user.longName + fetchedNode[0].user?.shortName = nodeInfoMessage.user.shortName + fetchedNode[0].user?.role = Int32(nodeInfoMessage.user.role.rawValue) + fetchedNode[0].user?.hwModel = String(describing: nodeInfoMessage.user.hwModel).uppercased() + fetchedNode[0].user?.hwModelId = Int32(nodeInfoMessage.user.hwModel.rawValue) /// For nodes that have the optional isUnmessagable boolean use that, otherwise excluded roles that are unmessagable by default if nodeInfoMessage.user.hasIsUnmessagable { - fetchedNode[0].user!.unmessagable = nodeInfoMessage.user.isUnmessagable + fetchedNode[0].user?.unmessagable = nodeInfoMessage.user.isUnmessagable } else { let roles = [-1, 2, 4, 5, 6, 7, 10, 11] let containsRole = roles.contains(Int(fetchedNode[0].user?.role ?? -1)) @@ -304,13 +303,13 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) } } if !nodeInfoMessage.user.publicKey.isEmpty { - fetchedNode[0].user!.pkiEncrypted = true - fetchedNode[0].user!.publicKey = nodeInfoMessage.user.publicKey + fetchedNode[0].user?.pkiEncrypted = true + fetchedNode[0].user?.publicKey = nodeInfoMessage.user.publicKey } Task { Api().loadDeviceHardwareData { (hw) in let dh = hw.first(where: { $0.hwModel == fetchedNode[0].user?.hwModelId ?? 0 }) - fetchedNode[0].user!.hwDisplayName = dh?.displayName + fetchedNode[0].user?.hwDisplayName = dh?.displayName } } } @@ -384,7 +383,7 @@ func upsertPositionPacket (packet: MeshPacket, context: NSManagedObjectContext) } else { position.time = Date(timeIntervalSince1970: TimeInterval(Int64(positionMessage.time))) } - guard let mutablePositions = fetchedNode[0].positions!.mutableCopy() as? NSMutableOrderedSet else { + guard let mutablePositions = fetchedNode[0].positions?.mutableCopy() as? NSMutableOrderedSet else { return } /// Don't save nearly the same position over and over. If the next position is less than 10 meters from the new position, delete the previous position and save the new one. @@ -1231,6 +1230,7 @@ func upsertMqttModuleConfigPacket(config: ModuleConfig.MQTTConfig, nodeNum: Int6 newMQTTConfig.jsonEnabled = config.jsonEnabled newMQTTConfig.tlsEnabled = config.tlsEnabled newMQTTConfig.mapReportingEnabled = config.mapReportingEnabled + newMQTTConfig.mapReportingShouldReportLocation = config.mapReportSettings.shouldReportLocation newMQTTConfig.mapPositionPrecision = Int32(config.mapReportSettings.positionPrecision) newMQTTConfig.mapPublishIntervalSecs = Int32(config.mapReportSettings.publishIntervalSecs) fetchedNode[0].mqttConfig = newMQTTConfig diff --git a/Meshtastic/Views/Helpers/MeshtasticLogo.swift b/Meshtastic/Views/Helpers/MeshtasticLogo.swift index 84040d92..c6906f15 100644 --- a/Meshtastic/Views/Helpers/MeshtasticLogo.swift +++ b/Meshtastic/Views/Helpers/MeshtasticLogo.swift @@ -11,27 +11,22 @@ struct MeshtasticLogo: View { @Environment(\.colorScheme) var colorScheme var body: some View { - #if targetEnvironment(macCatalyst) VStack { Image("logo-white") .resizable() - .renderingMode(.template) .foregroundColor(.accentColor) .scaledToFit() } .padding(.bottom, 5) .padding(.top, 5) - .offset(x: -15) #else VStack { Image(colorScheme == .dark ? "logo-white" : "logo-black") .resizable() - .renderingMode(.template) .scaledToFit() } .padding(.bottom, 5) - .offset(x: -15) #endif } } diff --git a/Meshtastic/Views/Messages/ChannelList.swift b/Meshtastic/Views/Messages/ChannelList.swift index 1123c4ab..61f4fe00 100644 --- a/Meshtastic/Views/Messages/ChannelList.swift +++ b/Meshtastic/Views/Messages/ChannelList.swift @@ -26,6 +26,12 @@ struct ChannelList: View { var restrictedChannels = ["gpio", "mqtt", "serial", "admin"] + @FetchRequest( + sortDescriptors: [NSSortDescriptor(keyPath: \ChannelEntity.index, ascending: true)], + predicate: nil, + animation: .default + ) private var channels: FetchedResults + @ViewBuilder private func makeChannelRow( myInfo: MyInfoEntity, @@ -87,6 +93,9 @@ struct ChannelList: View { .foregroundColor(.secondary) } } + if channel.mute { + Image(systemName: "bell.slash") + } } if channel.allPrivateMessages.count > 0 { @@ -103,7 +112,7 @@ struct ChannelList: View { var body: some View { VStack { // Display Contacts for the rest of the non admin channels - if let node, let myInfo = node.myInfo, let channels = myInfo.channels?.array as? [ChannelEntity] { + if let node, let myInfo = node.myInfo { List(selection: $channelSelection) { ForEach(channels) { (channel: ChannelEntity) in if !restrictedChannels.contains(channel.name?.lowercased() ?? "") { @@ -119,7 +128,7 @@ struct ChannelList: View { } } Button { - channel.mute = !channel.mute + channel.mute.toggle() do { let adminMessageId = bleManager.saveChannel(channel: channel.protoBuf, fromUser: node.user!, toUser: node.user!) if adminMessageId > 0 { diff --git a/Meshtastic/Views/Messages/UserList.swift b/Meshtastic/Views/Messages/UserList.swift index a74e3fd2..41642582 100644 --- a/Meshtastic/Views/Messages/UserList.swift +++ b/Meshtastic/Views/Messages/UserList.swift @@ -21,7 +21,6 @@ struct UserList: View { @State private var isPkiEncrypted = false @State private var isFavorite = false @State private var isIgnored = false - @State private var isUnmessagable = false @State private var isEnvironment = false @State private var distanceFilter = false @State private var maxDistance: Double = 800000 @@ -40,18 +39,6 @@ struct UserList: View { roleFilter ]} - @FetchRequest( - sortDescriptors: [NSSortDescriptor(key: "lastMessage", ascending: false), - NSSortDescriptor(key: "userNode.favorite", ascending: false), - NSSortDescriptor(key: "pkiEncrypted", ascending: false), - NSSortDescriptor(key: "userNode.lastHeard", ascending: false), - NSSortDescriptor(key: "longName", ascending: true)], - predicate: NSPredicate( - format: "userNode.ignored == NO AND unmessagable = NO" - ), animation: .spring - ) - var users: FetchedResults - @Binding var node: NodeInfoEntity? @Binding var userSelection: UserEntity? @@ -61,196 +48,161 @@ struct UserList: View { let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMdd", options: 0, locale: Locale.current) let dateFormatString = (localeDateFormat ?? "MM/dd/YY") VStack { - List(users, selection: $userSelection) { (user: UserEntity) in - let mostRecent = user.messageList.last - let lastMessageTime = Date(timeIntervalSince1970: TimeInterval(Int64((mostRecent?.messageTimestamp ?? 0 )))) - let lastMessageDay = Calendar.current.dateComponents([.day], from: lastMessageTime).day ?? 0 - let currentDay = Calendar.current.dateComponents([.day], from: Date()).day ?? 0 - if user.num != bleManager.connectedPeripheral?.num ?? 0 { - NavigationLink(value: user) { - ZStack { - Image(systemName: "circle.fill") - .opacity(user.unreadMessages > 0 ? 1 : 0) - .font(.system(size: 10)) - .foregroundColor(.accentColor) - .brightness(0.2) - } + FilteredUserList( + searchText: searchText, + viaLora: viaLora, + viaMqtt: viaMqtt, + isOnline: isOnline, + isPkiEncrypted: isPkiEncrypted, + isFavorite: isFavorite, + isIgnored: isIgnored, + isEnvironment: isEnvironment, + distanceFilter: distanceFilter, + maxDistance: maxDistance, + hopsAway: hopsAway, + roleFilter: roleFilter, + deviceRoles: deviceRoles, + userSelection: $userSelection + ) { users in + List(users, selection: $userSelection) { (user: UserEntity) in + let mostRecent = user.messageList.last + let lastMessageTime = Date(timeIntervalSince1970: TimeInterval(Int64((mostRecent?.messageTimestamp ?? 0 )))) + let lastMessageDay = Calendar.current.dateComponents([.day], from: lastMessageTime).day ?? 0 + let currentDay = Calendar.current.dateComponents([.day], from: Date()).day ?? 0 + if user.num != bleManager.connectedPeripheral?.num ?? 0 { + NavigationLink(value: user) { + ZStack { + Image(systemName: "circle.fill") + .opacity(user.unreadMessages > 0 ? 1 : 0) + .font(.system(size: 10)) + .foregroundColor(.accentColor) + .brightness(0.2) + } - CircleText(text: user.shortName ?? "?", color: Color(UIColor(hex: UInt32(user.num)))) + CircleText(text: user.shortName ?? "?", color: Color(UIColor(hex: UInt32(user.num)))) - VStack(alignment: .leading) { - HStack { - if user.pkiEncrypted { - if !user.keyMatch { - /// Public Key on the User and the Public Key on the Last Message don't match - Image(systemName: "key.slash") - .foregroundColor(.red) + VStack(alignment: .leading) { + HStack { + if user.pkiEncrypted { + if !user.keyMatch { + /// Public Key on the User and the Public Key on the Last Message don't match + Image(systemName: "key.slash") + .foregroundColor(.red) + } else { + Image(systemName: "lock.fill") + .foregroundColor(.green) + } } else { - Image(systemName: "lock.fill") - .foregroundColor(.green) + Image(systemName: "lock.open.fill") + .foregroundColor(.yellow) + } + Text(user.longName ?? "Unknown".localized) + .font(.headline) + .allowsTightening(true) + Spacer() + if user.userNode?.favorite ?? false { + Image(systemName: "star.fill") + .foregroundColor(.yellow) + } + if user.messageList.count > 0 { + if lastMessageDay == currentDay { + Text(lastMessageTime, style: .time ) + .font(.footnote) + .foregroundColor(.secondary) + } else if lastMessageDay == (currentDay - 1) { + Text("Yesterday") + .font(.footnote) + .foregroundColor(.secondary) + } else if lastMessageDay < (currentDay - 1) && lastMessageDay > (currentDay - 5) { + Text(lastMessageTime.formattedDate(format: dateFormatString)) + .font(.footnote) + .foregroundColor(.secondary) + } else if lastMessageDay < (currentDay - 1800) { + Text(lastMessageTime.formattedDate(format: dateFormatString)) + .font(.footnote) + .foregroundColor(.secondary) + } + } + } + + if user.messageList.count > 0 { + HStack(alignment: .top) { + Text("\(mostRecent != nil ? mostRecent!.messagePayload! : " ")") + .font(.footnote) + .foregroundColor(.secondary) + } + } + } + } + .frame(height: 62) + .contextMenu { + Button { + if node != nil && !(user.userNode?.favorite ?? false) { + let success = bleManager.setFavoriteNode(node: user.userNode!, connectedNodeNum: Int64(node!.num)) + if success { + user.userNode?.favorite = !(user.userNode?.favorite ?? false) + Logger.data.info("Favorited a node") } } else { - Image(systemName: "lock.open.fill") - .foregroundColor(.yellow) - } - Text(user.longName ?? "Unknown".localized) - .font(.headline) - .allowsTightening(true) - Spacer() - if user.userNode?.favorite ?? false { - Image(systemName: "star.fill") - .foregroundColor(.yellow) - } - if user.messageList.count > 0 { - if lastMessageDay == currentDay { - Text(lastMessageTime, style: .time ) - .font(.footnote) - .foregroundColor(.secondary) - } else if lastMessageDay == (currentDay - 1) { - Text("Yesterday") - .font(.footnote) - .foregroundColor(.secondary) - } else if lastMessageDay < (currentDay - 1) && lastMessageDay > (currentDay - 5) { - Text(lastMessageTime.formattedDate(format: dateFormatString)) - .font(.footnote) - .foregroundColor(.secondary) - } else if lastMessageDay < (currentDay - 1800) { - Text(lastMessageTime.formattedDate(format: dateFormatString)) - .font(.footnote) - .foregroundColor(.secondary) + let success = bleManager.removeFavoriteNode(node: user.userNode!, connectedNodeNum: Int64(node!.num)) + if success { + user.userNode?.favorite = !(user.userNode?.favorite ?? false) + Logger.data.info("Unfavorited a node") } } - } - - if user.messageList.count > 0 { - HStack(alignment: .top) { - Text("\(mostRecent != nil ? mostRecent!.messagePayload! : " ")") - .font(.footnote) - .foregroundColor(.secondary) + context.refresh(user, mergeChanges: true) + do { + try context.save() + } catch { + context.rollback() + Logger.data.error("Save Node Favorite Error") } - } - } - } - .frame(height: 62) - .contextMenu { - Button { - - if node != nil && !(user.userNode?.favorite ?? false) { - let success = bleManager.setFavoriteNode(node: user.userNode!, connectedNodeNum: Int64(node!.num)) - if success { - user.userNode?.favorite = !(user.userNode?.favorite ?? true) - Logger.data.info("Favorited a node") - } - } else { - let success = bleManager.removeFavoriteNode(node: user.userNode!, connectedNodeNum: Int64(node!.num)) - if success { - user.userNode?.favorite = !(user.userNode?.favorite ?? true) - Logger.data.info("Un Favorited a node") - } - } - context.refresh(user, mergeChanges: true) - do { - try context.save() - } catch { - context.rollback() - Logger.data.error("Save Node Favorite Error") - } - } label: { - Label((user.userNode?.favorite ?? false) ? "Un-Favorite" : "Favorite", systemImage: (user.userNode?.favorite ?? false) ? "star.slash.fill" : "star.fill") - } - Button { - user.mute = !user.mute - do { - try context.save() - } catch { - context.rollback() - Logger.data.error("Save User Mute Error") - } - } label: { - Label(user.mute ? "Show Alerts" : "Hide Alerts", systemImage: user.mute ? "bell" : "bell.slash") - } - if user.messageList.count > 0 { - Button(role: .destructive) { - isPresentingDeleteUserMessagesConfirm = true - userSelection = user } label: { - Label("Delete Messages", systemImage: "trash") + Label((user.userNode?.favorite ?? false) ? "Un-Favorite" : "Favorite", systemImage: (user.userNode?.favorite ?? false) ? "star.slash.fill" : "star.fill") + } + Button { + user.mute = !user.mute + do { + try context.save() + } catch { + context.rollback() + Logger.data.error("Save User Mute Error") + } + } label: { + Label(user.mute ? "Show Alerts" : "Hide Alerts", systemImage: user.mute ? "bell" : "bell.slash") + } + if user.messageList.count > 0 { + Button(role: .destructive) { + isPresentingDeleteUserMessagesConfirm = true + userSelection = user + } label: { + Label("Delete Messages", systemImage: "trash") + } } } - } - .confirmationDialog( - "This conversation will be deleted.", - isPresented: $isPresentingDeleteUserMessagesConfirm, - titleVisibility: .visible - ) { - Button(role: .destructive) { - deleteUserMessages(user: userSelection!, context: context) - context.refresh(node!.user!, mergeChanges: true) - } label: { - Text("Delete") + .confirmationDialog( + "This conversation will be deleted.", + isPresented: $isPresentingDeleteUserMessagesConfirm, + titleVisibility: .visible + ) { + Button(role: .destructive) { + deleteUserMessages(user: userSelection!, context: context) + context.refresh(node!.user!, mergeChanges: true) + } label: { + Text("Delete") + } } } } + .listStyle(.plain) + .navigationTitle(String.localizedStringWithFormat("Contacts (%@)", String(users.count))) } - .listStyle(.plain) - .navigationTitle(String.localizedStringWithFormat("Contacts (%@)".localized, String(users.count == 0 ? 0 : users.count - 1))) .sheet(isPresented: $editingFilters) { NodeListFilter(filterTitle: "Contact Filters", viaLora: $viaLora, viaMqtt: $viaMqtt, isOnline: $isOnline, isPkiEncrypted: $isPkiEncrypted, isFavorite: $isFavorite, isIgnored: $isIgnored, isEnvironment: $isEnvironment, distanceFilter: $distanceFilter, maximumDistance: $maxDistance, hopsAway: $hopsAway, roleFilter: $roleFilter, deviceRoles: $deviceRoles) } .sheet(isPresented: $showingHelp) { DirectMessagesHelp() } - .onChange(of: searchText) { - Task { - await searchUserList() - } - } - .onChange(of: viaLora) { - if !viaLora && !viaMqtt { - viaMqtt = true - } - Task { - await searchUserList() - } - } - .onChange(of: viaMqtt) { - if !viaLora && !viaMqtt { - viaLora = true - } - Task { - await searchUserList() - } - } - .onChange(of: [deviceRoles]) { - Task { - await searchUserList() - } - } - .onChange(of: hopsAway) { - Task { - await searchUserList() - } - } - .onChange(of: [boolFilters]) { - Task { - await searchUserList() - } - } - .onChange(of: maxDistance) { - Task { - await searchUserList() - } - } - .onChange(of: isPkiEncrypted) { - Task { - await searchUserList() - } - } - .onFirstAppear { - Task { - await searchUserList() - } - } .safeAreaInset(edge: .bottom, alignment: .leading) { HStack { Button(action: { @@ -281,23 +233,50 @@ struct UserList: View { .padding(5) } .padding(.bottom, 5) - .padding(.bottom, 5) - .searchable(text: $searchText, placement: users.count > 10 ? .navigationBarDrawer(displayMode: .always) : .automatic, prompt: "Find a contact") + .searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always), prompt: "Find a contact") .disableAutocorrection(true) .scrollDismissesKeyboard(.immediately) } } - private func searchUserList() async { +} - /// Case Insensitive Search Text Predicates - let searchPredicates = ["userId", "numString", "hwModel", "hwDisplayName", "longName", "shortName"].map { property in - return NSPredicate(format: "%K CONTAINS[c] %@", property, searchText) - } - /// Create a compound predicate using each text search preicate as an OR - let textSearchPredicate = NSCompoundPredicate(type: .or, subpredicates: searchPredicates) - /// Create an array of predicates to hold our AND predicates +struct FilteredUserList: View { + @FetchRequest var fetchRequest: FetchedResults + let content: (FetchedResults) -> Content + + var body: some View { + content(fetchRequest) + } + + init( + searchText: String, + viaLora: Bool, + viaMqtt: Bool, + isOnline: Bool, + isPkiEncrypted: Bool, + isFavorite: Bool, + isIgnored: Bool, + isEnvironment: Bool, + distanceFilter: Bool, + maxDistance: Double, + hopsAway: Double, + roleFilter: Bool, + deviceRoles: Set, + userSelection: Binding, + @ViewBuilder content: @escaping (FetchedResults) -> Content + ) { + self.content = content + // Build predicates based on filter variables var predicates: [NSPredicate] = [] - /// Mqtt and lora + // Search text predicates + if !searchText.isEmpty { + let searchPredicates = ["userId", "numString", "hwModel", "hwDisplayName", "longName", "shortName"].map { property in + return NSPredicate(format: "%K CONTAINS[c] %@", property, searchText) + } + let textSearchPredicate = NSCompoundPredicate(type: .or, subpredicates: searchPredicates) + predicates.append(textSearchPredicate) + } + // Mqtt and lora if !(viaLora && viaMqtt) { if viaLora { let loraPredicate = NSPredicate(format: "userNode.viaMqtt == NO") @@ -306,11 +285,8 @@ struct UserList: View { let mqttPredicate = NSPredicate(format: "userNode.viaMqtt == YES") predicates.append(mqttPredicate) } - } else { - let mqttPredicate = NSPredicate(format: "NOT (userNode.viaMqtt == YES)") - predicates.append(mqttPredicate) } - /// Roles + // Roles if roleFilter && deviceRoles.count > 0 { var rolesArray: [NSPredicate] = [] for dr in deviceRoles { @@ -320,40 +296,32 @@ struct UserList: View { let compoundPredicate = NSCompoundPredicate(type: .or, subpredicates: rolesArray) predicates.append(compoundPredicate) } - /// Hops Away - if hopsAway == 0.0 { + // Hops Away + if hopsAway == 0 { let hopsAwayPredicate = NSPredicate(format: "userNode.hopsAway == %i", Int32(hopsAway)) predicates.append(hopsAwayPredicate) } else if hopsAway > -1.0 { let hopsAwayPredicate = NSPredicate(format: "userNode.hopsAway > 0 AND userNode.hopsAway <= %i", Int32(hopsAway)) predicates.append(hopsAwayPredicate) } - /// Online + // Online if isOnline { let isOnlinePredicate = NSPredicate(format: "userNode.lastHeard >= %@", Calendar.current.date(byAdding: .minute, value: -120, to: Date())! as NSDate) predicates.append(isOnlinePredicate) } - /// Encrypted + // Encrypted if isPkiEncrypted { let isPkiEncryptedPredicate = NSPredicate(format: "pkiEncrypted == YES") predicates.append(isPkiEncryptedPredicate) } - /// Favorites + // Favorites if isFavorite { let isFavoritePredicate = NSPredicate(format: "userNode.favorite == YES") predicates.append(isFavoritePredicate) } - /// Ignored - let isIgnoredPredicate = NSPredicate(format: "userNode.ignored == NO") - predicates.append(isIgnoredPredicate) - /// Unmessagable - let isUnmessagablePredicate = NSPredicate(format: "unmessagable == NO") - predicates.append(isUnmessagablePredicate) - - /// Distance + // Distance if distanceFilter { let pointOfInterest = LocationsHandler.currentLocation - if pointOfInterest.latitude != LocationsHandler.DefaultLocation.latitude && pointOfInterest.longitude != LocationsHandler.DefaultLocation.longitude { let d: Double = maxDistance * 1.1 let r: Double = 6371009 @@ -368,11 +336,26 @@ struct UserList: View { predicates.append(distancePredicate) } } - if !searchText.isEmpty { - let filterPredicates = NSCompoundPredicate(type: .and, subpredicates: predicates) - users.nsPredicate = NSCompoundPredicate(type: .and, subpredicates: [textSearchPredicate, filterPredicates]) - } else { - users.nsPredicate = NSCompoundPredicate(type: .and, subpredicates: predicates) - } + // Always apply unmessagable and connected node filters + let isUnmessagablePredicate = NSPredicate(format: "unmessagable == NO") + predicates.append(isUnmessagablePredicate) + let isIgnoredPredicate = NSPredicate(format: "userNode.ignored == NO") + predicates.append(isIgnoredPredicate) + let isConnectedNodePredicate = NSPredicate(format: "NOT (numString CONTAINS %@)", String(UserDefaults.preferredPeripheralNum)) + predicates.append(isConnectedNodePredicate) + // Combine all predicates + let finalPredicate = predicates.isEmpty ? NSPredicate(value: true) : NSCompoundPredicate(type: .and, subpredicates: predicates) + // Initialize the fetch request with the combined predicate + _fetchRequest = FetchRequest( + sortDescriptors: [ + NSSortDescriptor(key: "lastMessage", ascending: false), + NSSortDescriptor(key: "userNode.favorite", ascending: false), + NSSortDescriptor(key: "pkiEncrypted", ascending: false), + NSSortDescriptor(key: "userNode.lastHeard", ascending: false), + NSSortDescriptor(key: "longName", ascending: true) + ], + predicate: finalPredicate, + animation: .spring + ) } } diff --git a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift index b2c97074..a420b8bf 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift @@ -486,7 +486,7 @@ struct NodeDetail: View { let connectedNode, self.bleManager.connectedPeripheral != nil { Section("Administration") { - if connectedNode.myInfo?.hasAdmin ?? false { + if UserDefaults.enableAdministration { Button { let adminMessageId = bleManager.requestDeviceMetadata( fromUser: connectedNode.user!, diff --git a/Meshtastic/Views/Nodes/Helpers/NodeListFilter.swift b/Meshtastic/Views/Nodes/Helpers/NodeListFilter.swift index 063e073a..1a02cfd7 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeListFilter.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeListFilter.swift @@ -91,20 +91,21 @@ struct NodeListFilter: View { } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) .listRowSeparator(.visible) - Toggle(isOn: $isIgnored) { - - Label { - Text("Ignored") - } icon: { - - Image(systemName: "minus.circle.fill") - .symbolRenderingMode(.multicolor) - } - } - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - .listRowSeparator(.visible) if filterTitle == "Node Filters" { + Toggle(isOn: $isIgnored) { + + Label { + Text("Ignored") + } icon: { + + Image(systemName: "minus.circle.fill") + .symbolRenderingMode(.multicolor) + } + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + .listRowSeparator(.visible) + Toggle(isOn: $isEnvironment) { Label { Text("Environment") diff --git a/Meshtastic/Views/Settings/Firmware.swift b/Meshtastic/Views/Settings/Firmware.swift index ad885d86..2380b677 100644 --- a/Meshtastic/Views/Settings/Firmware.swift +++ b/Meshtastic/Views/Settings/Firmware.swift @@ -196,6 +196,9 @@ struct Firmware: View { } } Api().loadFirmwareReleaseData { (fw) in + latestStable = fw.releases.stable.first + let archString = currentDevice?.architecture.rawValue ?? "" + let ls = fw.releases.stable.first(where: { $0.zipURL.contains(archString) == true }) latestStable = fw.releases.stable.first latestAlpha = fw.releases.alpha.first } diff --git a/MeshtasticProtobufs/Sources/meshtastic/admin.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/admin.pb.swift index 3f259682..188799b9 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/admin.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/admin.pb.swift @@ -498,6 +498,16 @@ public struct AdminMessage: @unchecked Sendable { set {payloadVariant = .addContact(newValue)} } + /// + /// Initiate or respond to a key verification request + public var keyVerification: KeyVerificationAdmin { + get { + if case .keyVerification(let v)? = payloadVariant {return v} + return KeyVerificationAdmin() + } + set {payloadVariant = .keyVerification(newValue)} + } + /// /// Tell the node to factory reset config everything; all device state and configuration will be returned to factory defaults and BLE bonds will be cleared. public var factoryResetDevice: Int32 { @@ -719,6 +729,9 @@ public struct AdminMessage: @unchecked Sendable { /// Add a contact (User) to the nodedb case addContact(SharedContact) /// + /// Initiate or respond to a key verification request + case keyVerification(KeyVerificationAdmin) + /// /// Tell the node to factory reset config everything; all device state and configuration will be returned to factory defaults and BLE bonds will be cleared. case factoryResetDevice(Int32) /// @@ -1077,6 +1090,98 @@ public struct SharedContact: Sendable { fileprivate var _user: User? = nil } +/// +/// This message is used by a client to initiate or complete a key verification +public struct KeyVerificationAdmin: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var messageType: KeyVerificationAdmin.MessageType = .initiateVerification + + /// + /// The nodenum we're requesting + public var remoteNodenum: UInt32 = 0 + + /// + /// The nonce is used to track the connection + public var nonce: UInt64 = 0 + + /// + /// The 4 digit code generated by the remote node, and communicated outside the mesh + public var securityNumber: UInt32 { + get {return _securityNumber ?? 0} + set {_securityNumber = newValue} + } + /// Returns true if `securityNumber` has been explicitly set. + public var hasSecurityNumber: Bool {return self._securityNumber != nil} + /// Clears the value of `securityNumber`. Subsequent reads from it will return its default value. + public mutating func clearSecurityNumber() {self._securityNumber = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + /// + /// Three stages of this request. + public enum MessageType: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + + /// + /// This is the first stage, where a client initiates + case initiateVerification // = 0 + + /// + /// After the nonce has been returned over the mesh, the client prompts for the security number + /// And uses this message to provide it to the node. + case provideSecurityNumber // = 1 + + /// + /// Once the user has compared the verification message, this message notifies the node. + case doVerify // = 2 + + /// + /// This is the cancel path, can be taken at any point + case doNotVerify // = 3 + case UNRECOGNIZED(Int) + + public init() { + self = .initiateVerification + } + + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .initiateVerification + case 1: self = .provideSecurityNumber + case 2: self = .doVerify + case 3: self = .doNotVerify + default: self = .UNRECOGNIZED(rawValue) + } + } + + public var rawValue: Int { + switch self { + case .initiateVerification: return 0 + case .provideSecurityNumber: return 1 + case .doVerify: return 2 + case .doNotVerify: return 3 + case .UNRECOGNIZED(let i): return i + } + } + + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [KeyVerificationAdmin.MessageType] = [ + .initiateVerification, + .provideSecurityNumber, + .doVerify, + .doNotVerify, + ] + + } + + public init() {} + + fileprivate var _securityNumber: UInt32? = nil +} + // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" @@ -1130,6 +1235,7 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat 64: .standard(proto: "begin_edit_settings"), 65: .standard(proto: "commit_edit_settings"), 66: .standard(proto: "add_contact"), + 67: .standard(proto: "key_verification"), 94: .standard(proto: "factory_reset_device"), 95: .standard(proto: "reboot_ota_seconds"), 96: .standard(proto: "exit_simulator"), @@ -1585,6 +1691,19 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat self.payloadVariant = .addContact(v) } }() + case 67: try { + var v: KeyVerificationAdmin? + var hadOneofValue = false + if let current = self.payloadVariant { + hadOneofValue = true + if case .keyVerification(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.payloadVariant = .keyVerification(v) + } + }() case 94: try { var v: Int32? try decoder.decodeSingularInt32Field(value: &v) @@ -1833,6 +1952,10 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat guard case .addContact(let v)? = self.payloadVariant else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 66) }() + case .keyVerification?: try { + guard case .keyVerification(let v)? = self.payloadVariant else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 67) + }() case .factoryResetDevice?: try { guard case .factoryResetDevice(let v)? = self.payloadVariant else { preconditionFailure() } try visitor.visitSingularInt32Field(value: v, fieldNumber: 94) @@ -2040,3 +2163,66 @@ extension SharedContact: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa return true } } + +extension KeyVerificationAdmin: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".KeyVerificationAdmin" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "message_type"), + 2: .standard(proto: "remote_nodenum"), + 3: .same(proto: "nonce"), + 4: .standard(proto: "security_number"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularEnumField(value: &self.messageType) }() + case 2: try { try decoder.decodeSingularUInt32Field(value: &self.remoteNodenum) }() + case 3: try { try decoder.decodeSingularUInt64Field(value: &self.nonce) }() + case 4: try { try decoder.decodeSingularUInt32Field(value: &self._securityNumber) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if self.messageType != .initiateVerification { + try visitor.visitSingularEnumField(value: self.messageType, fieldNumber: 1) + } + if self.remoteNodenum != 0 { + try visitor.visitSingularUInt32Field(value: self.remoteNodenum, fieldNumber: 2) + } + if self.nonce != 0 { + try visitor.visitSingularUInt64Field(value: self.nonce, fieldNumber: 3) + } + try { if let v = self._securityNumber { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 4) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: KeyVerificationAdmin, rhs: KeyVerificationAdmin) -> Bool { + if lhs.messageType != rhs.messageType {return false} + if lhs.remoteNodenum != rhs.remoteNodenum {return false} + if lhs.nonce != rhs.nonce {return false} + if lhs._securityNumber != rhs._securityNumber {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension KeyVerificationAdmin.MessageType: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "INITIATE_VERIFICATION"), + 1: .same(proto: "PROVIDE_SECURITY_NUMBER"), + 2: .same(proto: "DO_VERIFY"), + 3: .same(proto: "DO_NOT_VERIFY"), + ] +} diff --git a/MeshtasticProtobufs/Sources/meshtastic/config.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/config.pb.swift index 55e6e5f4..12a57c69 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/config.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/config.pb.swift @@ -756,6 +756,10 @@ public struct Config: Sendable { /// Flags for enabling/disabling network protocols public var enabledProtocols: UInt32 = 0 + /// + /// Enable/Disable ipv6 support + public var ipv6Enabled: Bool = false + public var unknownFields = SwiftProtobuf.UnknownStorage() public enum AddressMode: SwiftProtobuf.Enum, Swift.CaseIterable { @@ -2385,6 +2389,7 @@ extension Config.NetworkConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImp 8: .standard(proto: "ipv4_config"), 9: .standard(proto: "rsyslog_server"), 10: .standard(proto: "enabled_protocols"), + 11: .standard(proto: "ipv6_enabled"), ] public mutating func decodeMessage(decoder: inout D) throws { @@ -2402,6 +2407,7 @@ extension Config.NetworkConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImp case 8: try { try decoder.decodeSingularMessageField(value: &self._ipv4Config) }() case 9: try { try decoder.decodeSingularStringField(value: &self.rsyslogServer) }() case 10: try { try decoder.decodeSingularUInt32Field(value: &self.enabledProtocols) }() + case 11: try { try decoder.decodeSingularBoolField(value: &self.ipv6Enabled) }() default: break } } @@ -2439,6 +2445,9 @@ extension Config.NetworkConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImp if self.enabledProtocols != 0 { try visitor.visitSingularUInt32Field(value: self.enabledProtocols, fieldNumber: 10) } + if self.ipv6Enabled != false { + try visitor.visitSingularBoolField(value: self.ipv6Enabled, fieldNumber: 11) + } try unknownFields.traverse(visitor: &visitor) } @@ -2452,6 +2461,7 @@ extension Config.NetworkConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImp if lhs._ipv4Config != rhs._ipv4Config {return false} if lhs.rsyslogServer != rhs.rsyslogServer {return false} if lhs.enabledProtocols != rhs.enabledProtocols {return false} + if lhs.ipv6Enabled != rhs.ipv6Enabled {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } diff --git a/MeshtasticProtobufs/Sources/meshtastic/deviceonly.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/deviceonly.pb.swift index cbcbda13..acbc9682 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/deviceonly.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/deviceonly.pb.swift @@ -227,6 +227,14 @@ public struct NodeInfoLite: @unchecked Sendable { set {_uniqueStorage()._nextHop = newValue} } + /// + /// Bitfield for storing booleans. + /// LSB 0 is_key_manually_verified + public var bitfield: UInt32 { + get {return _storage._bitfield} + set {_uniqueStorage()._bitfield = newValue} + } + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} @@ -608,6 +616,7 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat 10: .standard(proto: "is_favorite"), 11: .standard(proto: "is_ignored"), 12: .standard(proto: "next_hop"), + 13: .same(proto: "bitfield"), ] fileprivate class _StorageClass { @@ -623,6 +632,7 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat var _isFavorite: Bool = false var _isIgnored: Bool = false var _nextHop: UInt32 = 0 + var _bitfield: UInt32 = 0 #if swift(>=5.10) // This property is used as the initial default value for new instances of the type. @@ -649,6 +659,7 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat _isFavorite = source._isFavorite _isIgnored = source._isIgnored _nextHop = source._nextHop + _bitfield = source._bitfield } } @@ -679,6 +690,7 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat case 10: try { try decoder.decodeSingularBoolField(value: &_storage._isFavorite) }() case 11: try { try decoder.decodeSingularBoolField(value: &_storage._isIgnored) }() case 12: try { try decoder.decodeSingularUInt32Field(value: &_storage._nextHop) }() + case 13: try { try decoder.decodeSingularUInt32Field(value: &_storage._bitfield) }() default: break } } @@ -727,6 +739,9 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat if _storage._nextHop != 0 { try visitor.visitSingularUInt32Field(value: _storage._nextHop, fieldNumber: 12) } + if _storage._bitfield != 0 { + try visitor.visitSingularUInt32Field(value: _storage._bitfield, fieldNumber: 13) + } } try unknownFields.traverse(visitor: &visitor) } @@ -748,6 +763,7 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat if _storage._isFavorite != rhs_storage._isFavorite {return false} if _storage._isIgnored != rhs_storage._isIgnored {return false} if _storage._nextHop != rhs_storage._nextHop {return false} + if _storage._bitfield != rhs_storage._bitfield {return false} return true } if !storagesAreEqual {return false} diff --git a/MeshtasticProtobufs/Sources/meshtastic/mesh.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/mesh.pb.swift index d59ec2ed..407d395f 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/mesh.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/mesh.pb.swift @@ -442,6 +442,22 @@ public enum HardwareModel: SwiftProtobuf.Enum, Swift.CaseIterable { /// Elecrow CrowPanel Advance models, ESP32-S3 and TFT with SX1262 radio plugin case crowpanel // = 97 + ///* + /// Lilygo LINK32 board with sensors + case link32 // = 98 + + ///* + /// Seeed Tracker L1 + case seeedWioTrackerL1 // = 99 + + ///* + /// Seeed Tracker L1 EINK driver + case seeedWioTrackerL1Eink // = 100 + + /// + /// Reserved ID for future and past use + case qwantzTinyArms // = 101 + /// /// ------------------------------------------------------------------------------------------------------------------------------------------ /// Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. @@ -553,6 +569,10 @@ public enum HardwareModel: SwiftProtobuf.Enum, Swift.CaseIterable { case 95: self = .seeedSolarNode case 96: self = .nomadstarMeteorPro case 97: self = .crowpanel + case 98: self = .link32 + case 99: self = .seeedWioTrackerL1 + case 100: self = .seeedWioTrackerL1Eink + case 101: self = .qwantzTinyArms case 255: self = .privateHw default: self = .UNRECOGNIZED(rawValue) } @@ -658,6 +678,10 @@ public enum HardwareModel: SwiftProtobuf.Enum, Swift.CaseIterable { case .seeedSolarNode: return 95 case .nomadstarMeteorPro: return 96 case .crowpanel: return 97 + case .link32: return 98 + case .seeedWioTrackerL1: return 99 + case .seeedWioTrackerL1Eink: return 100 + case .qwantzTinyArms: return 101 case .privateHw: return 255 case .UNRECOGNIZED(let i): return i } @@ -763,6 +787,10 @@ public enum HardwareModel: SwiftProtobuf.Enum, Swift.CaseIterable { .seeedSolarNode, .nomadstarMeteorPro, .crowpanel, + .link32, + .seeedWioTrackerL1, + .seeedWioTrackerL1Eink, + .qwantzTinyArms, .privateHw, ] @@ -1820,6 +1848,31 @@ public struct DataMessage: @unchecked Sendable { fileprivate var _bitfield: UInt32? = nil } +/// +/// The actual over-the-mesh message doing KeyVerification +public struct KeyVerification: @unchecked Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// + /// random value Selected by the requesting node + public var nonce: UInt64 = 0 + + /// + /// The final authoritative hash, only to be sent by NodeA at the end of the handshake + public var hash1: Data = Data() + + /// + /// The intermediary hash (actually derived from hash1), + /// sent from NodeB to NodeA in response to the initial message. + public var hash2: Data = Data() + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + /// /// Waypoint message, used to share arbitrary locations across the mesh public struct Waypoint: Sendable { @@ -2441,6 +2494,15 @@ public struct NodeInfo: @unchecked Sendable { set {_uniqueStorage()._isIgnored = newValue} } + /// + /// True if node public key has been verified. + /// Persists between NodeDB internal clean ups + /// LSB 0 of the bitfield + public var isKeyManuallyVerified: Bool { + get {return _storage._isKeyManuallyVerified} + set {_uniqueStorage()._isKeyManuallyVerified = newValue} + } + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} @@ -2903,13 +2965,94 @@ public struct ClientNotification: Sendable { /// The message body of the notification public var message: String = String() + public var payloadVariant: ClientNotification.OneOf_PayloadVariant? = nil + + public var keyVerificationNumberInform: KeyVerificationNumberInform { + get { + if case .keyVerificationNumberInform(let v)? = payloadVariant {return v} + return KeyVerificationNumberInform() + } + set {payloadVariant = .keyVerificationNumberInform(newValue)} + } + + public var keyVerificationNumberRequest: KeyVerificationNumberRequest { + get { + if case .keyVerificationNumberRequest(let v)? = payloadVariant {return v} + return KeyVerificationNumberRequest() + } + set {payloadVariant = .keyVerificationNumberRequest(newValue)} + } + + public var keyVerificationFinal: KeyVerificationFinal { + get { + if case .keyVerificationFinal(let v)? = payloadVariant {return v} + return KeyVerificationFinal() + } + set {payloadVariant = .keyVerificationFinal(newValue)} + } + public var unknownFields = SwiftProtobuf.UnknownStorage() + public enum OneOf_PayloadVariant: Equatable, Sendable { + case keyVerificationNumberInform(KeyVerificationNumberInform) + case keyVerificationNumberRequest(KeyVerificationNumberRequest) + case keyVerificationFinal(KeyVerificationFinal) + + } + public init() {} fileprivate var _replyID: UInt32? = nil } +public struct KeyVerificationNumberInform: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var nonce: UInt64 = 0 + + public var remoteLongname: String = String() + + public var securityNumber: UInt32 = 0 + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +public struct KeyVerificationNumberRequest: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var nonce: UInt64 = 0 + + public var remoteLongname: String = String() + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +public struct KeyVerificationFinal: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var nonce: UInt64 = 0 + + public var remoteLongname: String = String() + + public var isSender: Bool = false + + public var verificationCharacters: String = String() + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + /// /// Individual File info for the device public struct FileInfo: Sendable { @@ -3431,6 +3574,10 @@ extension HardwareModel: SwiftProtobuf._ProtoNameProviding { 95: .same(proto: "SEEED_SOLAR_NODE"), 96: .same(proto: "NOMADSTAR_METEOR_PRO"), 97: .same(proto: "CROWPANEL"), + 98: .same(proto: "LINK_32"), + 99: .same(proto: "SEEED_WIO_TRACKER_L1"), + 100: .same(proto: "SEEED_WIO_TRACKER_L1_EINK"), + 101: .same(proto: "QWANTZ_TINY_ARMS"), 255: .same(proto: "PRIVATE_HW"), ] } @@ -4075,6 +4222,50 @@ extension DataMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati } } +extension KeyVerification: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".KeyVerification" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "nonce"), + 2: .same(proto: "hash1"), + 3: .same(proto: "hash2"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt64Field(value: &self.nonce) }() + case 2: try { try decoder.decodeSingularBytesField(value: &self.hash1) }() + case 3: try { try decoder.decodeSingularBytesField(value: &self.hash2) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if self.nonce != 0 { + try visitor.visitSingularUInt64Field(value: self.nonce, fieldNumber: 1) + } + if !self.hash1.isEmpty { + try visitor.visitSingularBytesField(value: self.hash1, fieldNumber: 2) + } + if !self.hash2.isEmpty { + try visitor.visitSingularBytesField(value: self.hash2, fieldNumber: 3) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: KeyVerification, rhs: KeyVerification) -> Bool { + if lhs.nonce != rhs.nonce {return false} + if lhs.hash1 != rhs.hash1 {return false} + if lhs.hash2 != rhs.hash2 {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + extension Waypoint: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".Waypoint" public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ @@ -4511,6 +4702,7 @@ extension NodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB 9: .standard(proto: "hops_away"), 10: .standard(proto: "is_favorite"), 11: .standard(proto: "is_ignored"), + 12: .standard(proto: "is_key_manually_verified"), ] fileprivate class _StorageClass { @@ -4525,6 +4717,7 @@ extension NodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB var _hopsAway: UInt32? = nil var _isFavorite: Bool = false var _isIgnored: Bool = false + var _isKeyManuallyVerified: Bool = false #if swift(>=5.10) // This property is used as the initial default value for new instances of the type. @@ -4550,6 +4743,7 @@ extension NodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB _hopsAway = source._hopsAway _isFavorite = source._isFavorite _isIgnored = source._isIgnored + _isKeyManuallyVerified = source._isKeyManuallyVerified } } @@ -4579,6 +4773,7 @@ extension NodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB case 9: try { try decoder.decodeSingularUInt32Field(value: &_storage._hopsAway) }() case 10: try { try decoder.decodeSingularBoolField(value: &_storage._isFavorite) }() case 11: try { try decoder.decodeSingularBoolField(value: &_storage._isIgnored) }() + case 12: try { try decoder.decodeSingularBoolField(value: &_storage._isKeyManuallyVerified) }() default: break } } @@ -4624,6 +4819,9 @@ extension NodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB if _storage._isIgnored != false { try visitor.visitSingularBoolField(value: _storage._isIgnored, fieldNumber: 11) } + if _storage._isKeyManuallyVerified != false { + try visitor.visitSingularBoolField(value: _storage._isKeyManuallyVerified, fieldNumber: 12) + } } try unknownFields.traverse(visitor: &visitor) } @@ -4644,6 +4842,7 @@ extension NodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB if _storage._hopsAway != rhs_storage._hopsAway {return false} if _storage._isFavorite != rhs_storage._isFavorite {return false} if _storage._isIgnored != rhs_storage._isIgnored {return false} + if _storage._isKeyManuallyVerified != rhs_storage._isKeyManuallyVerified {return false} return true } if !storagesAreEqual {return false} @@ -5146,6 +5345,9 @@ extension ClientNotification: SwiftProtobuf.Message, SwiftProtobuf._MessageImple 2: .same(proto: "time"), 3: .same(proto: "level"), 4: .same(proto: "message"), + 11: .standard(proto: "key_verification_number_inform"), + 12: .standard(proto: "key_verification_number_request"), + 13: .standard(proto: "key_verification_final"), ] public mutating func decodeMessage(decoder: inout D) throws { @@ -5158,6 +5360,45 @@ extension ClientNotification: SwiftProtobuf.Message, SwiftProtobuf._MessageImple case 2: try { try decoder.decodeSingularFixed32Field(value: &self.time) }() case 3: try { try decoder.decodeSingularEnumField(value: &self.level) }() case 4: try { try decoder.decodeSingularStringField(value: &self.message) }() + case 11: try { + var v: KeyVerificationNumberInform? + var hadOneofValue = false + if let current = self.payloadVariant { + hadOneofValue = true + if case .keyVerificationNumberInform(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.payloadVariant = .keyVerificationNumberInform(v) + } + }() + case 12: try { + var v: KeyVerificationNumberRequest? + var hadOneofValue = false + if let current = self.payloadVariant { + hadOneofValue = true + if case .keyVerificationNumberRequest(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.payloadVariant = .keyVerificationNumberRequest(v) + } + }() + case 13: try { + var v: KeyVerificationFinal? + var hadOneofValue = false + if let current = self.payloadVariant { + hadOneofValue = true + if case .keyVerificationFinal(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.payloadVariant = .keyVerificationFinal(v) + } + }() default: break } } @@ -5180,6 +5421,21 @@ extension ClientNotification: SwiftProtobuf.Message, SwiftProtobuf._MessageImple if !self.message.isEmpty { try visitor.visitSingularStringField(value: self.message, fieldNumber: 4) } + switch self.payloadVariant { + case .keyVerificationNumberInform?: try { + guard case .keyVerificationNumberInform(let v)? = self.payloadVariant else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 11) + }() + case .keyVerificationNumberRequest?: try { + guard case .keyVerificationNumberRequest(let v)? = self.payloadVariant else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 12) + }() + case .keyVerificationFinal?: try { + guard case .keyVerificationFinal(let v)? = self.payloadVariant else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 13) + }() + case nil: break + } try unknownFields.traverse(visitor: &visitor) } @@ -5188,6 +5444,139 @@ extension ClientNotification: SwiftProtobuf.Message, SwiftProtobuf._MessageImple if lhs.time != rhs.time {return false} if lhs.level != rhs.level {return false} if lhs.message != rhs.message {return false} + if lhs.payloadVariant != rhs.payloadVariant {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension KeyVerificationNumberInform: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".KeyVerificationNumberInform" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "nonce"), + 2: .standard(proto: "remote_longname"), + 3: .standard(proto: "security_number"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt64Field(value: &self.nonce) }() + case 2: try { try decoder.decodeSingularStringField(value: &self.remoteLongname) }() + case 3: try { try decoder.decodeSingularUInt32Field(value: &self.securityNumber) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if self.nonce != 0 { + try visitor.visitSingularUInt64Field(value: self.nonce, fieldNumber: 1) + } + if !self.remoteLongname.isEmpty { + try visitor.visitSingularStringField(value: self.remoteLongname, fieldNumber: 2) + } + if self.securityNumber != 0 { + try visitor.visitSingularUInt32Field(value: self.securityNumber, fieldNumber: 3) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: KeyVerificationNumberInform, rhs: KeyVerificationNumberInform) -> Bool { + if lhs.nonce != rhs.nonce {return false} + if lhs.remoteLongname != rhs.remoteLongname {return false} + if lhs.securityNumber != rhs.securityNumber {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension KeyVerificationNumberRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".KeyVerificationNumberRequest" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "nonce"), + 2: .standard(proto: "remote_longname"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt64Field(value: &self.nonce) }() + case 2: try { try decoder.decodeSingularStringField(value: &self.remoteLongname) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if self.nonce != 0 { + try visitor.visitSingularUInt64Field(value: self.nonce, fieldNumber: 1) + } + if !self.remoteLongname.isEmpty { + try visitor.visitSingularStringField(value: self.remoteLongname, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: KeyVerificationNumberRequest, rhs: KeyVerificationNumberRequest) -> Bool { + if lhs.nonce != rhs.nonce {return false} + if lhs.remoteLongname != rhs.remoteLongname {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension KeyVerificationFinal: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".KeyVerificationFinal" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "nonce"), + 2: .standard(proto: "remote_longname"), + 3: .same(proto: "isSender"), + 4: .standard(proto: "verification_characters"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt64Field(value: &self.nonce) }() + case 2: try { try decoder.decodeSingularStringField(value: &self.remoteLongname) }() + case 3: try { try decoder.decodeSingularBoolField(value: &self.isSender) }() + case 4: try { try decoder.decodeSingularStringField(value: &self.verificationCharacters) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if self.nonce != 0 { + try visitor.visitSingularUInt64Field(value: self.nonce, fieldNumber: 1) + } + if !self.remoteLongname.isEmpty { + try visitor.visitSingularStringField(value: self.remoteLongname, fieldNumber: 2) + } + if self.isSender != false { + try visitor.visitSingularBoolField(value: self.isSender, fieldNumber: 3) + } + if !self.verificationCharacters.isEmpty { + try visitor.visitSingularStringField(value: self.verificationCharacters, fieldNumber: 4) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: KeyVerificationFinal, rhs: KeyVerificationFinal) -> Bool { + if lhs.nonce != rhs.nonce {return false} + if lhs.remoteLongname != rhs.remoteLongname {return false} + if lhs.isSender != rhs.isSender {return false} + if lhs.verificationCharacters != rhs.verificationCharacters {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } diff --git a/MeshtasticProtobufs/Sources/meshtastic/portnums.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/portnums.pb.swift index cac96bc4..182e233c 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/portnums.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/portnums.pb.swift @@ -111,6 +111,10 @@ public enum PortNum: SwiftProtobuf.Enum, Swift.CaseIterable { /// Same as Text Message but used for critical alerts. case alertApp // = 11 + /// + /// Module/port for handling key verification requests. + case keyVerificationApp // = 12 + /// /// Provides a 'ping' service that replies to any packet it receives. /// Also serves as a small example module. @@ -232,6 +236,7 @@ public enum PortNum: SwiftProtobuf.Enum, Swift.CaseIterable { case 9: self = .audioApp case 10: self = .detectionSensorApp case 11: self = .alertApp + case 12: self = .keyVerificationApp case 32: self = .replyApp case 33: self = .ipTunnelApp case 34: self = .paxcounterApp @@ -268,6 +273,7 @@ public enum PortNum: SwiftProtobuf.Enum, Swift.CaseIterable { case .audioApp: return 9 case .detectionSensorApp: return 10 case .alertApp: return 11 + case .keyVerificationApp: return 12 case .replyApp: return 32 case .ipTunnelApp: return 33 case .paxcounterApp: return 34 @@ -304,6 +310,7 @@ public enum PortNum: SwiftProtobuf.Enum, Swift.CaseIterable { .audioApp, .detectionSensorApp, .alertApp, + .keyVerificationApp, .replyApp, .ipTunnelApp, .paxcounterApp, @@ -342,6 +349,7 @@ extension PortNum: SwiftProtobuf._ProtoNameProviding { 9: .same(proto: "AUDIO_APP"), 10: .same(proto: "DETECTION_SENSOR_APP"), 11: .same(proto: "ALERT_APP"), + 12: .same(proto: "KEY_VERIFICATION_APP"), 32: .same(proto: "REPLY_APP"), 33: .same(proto: "IP_TUNNEL_APP"), 34: .same(proto: "PAXCOUNTER_APP"), diff --git a/MeshtasticProtobufs/Sources/meshtastic/telemetry.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/telemetry.pb.swift index ccf4cfb4..2b89d4bd 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/telemetry.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/telemetry.pb.swift @@ -180,6 +180,10 @@ public enum TelemetrySensorType: SwiftProtobuf.Enum, Swift.CaseIterable { /// /// MAX17261 lipo battery gauge case max17261 // = 38 + + /// + /// PCT2075 Temperature Sensor + case pct2075 // = 39 case UNRECOGNIZED(Int) public init() { @@ -227,6 +231,7 @@ public enum TelemetrySensorType: SwiftProtobuf.Enum, Swift.CaseIterable { case 36: self = .dps310 case 37: self = .rak12035 case 38: self = .max17261 + case 39: self = .pct2075 default: self = .UNRECOGNIZED(rawValue) } } @@ -272,6 +277,7 @@ public enum TelemetrySensorType: SwiftProtobuf.Enum, Swift.CaseIterable { case .dps310: return 36 case .rak12035: return 37 case .max17261: return 38 + case .pct2075: return 39 case .UNRECOGNIZED(let i): return i } } @@ -317,6 +323,7 @@ public enum TelemetrySensorType: SwiftProtobuf.Enum, Swift.CaseIterable { .dps310, .rak12035, .max17261, + .pct2075, ] } @@ -959,6 +966,14 @@ public struct LocalStats: Sendable { /// This will always be zero for ROUTERs/REPEATERs. If this number is high, some other node(s) is/are relaying faster than you. public var numTxRelayCanceled: UInt32 = 0 + /// + /// Number of bytes used in the heap + public var heapTotalBytes: UInt32 = 0 + + /// + /// Number of bytes free in the heap + public var heapFreeBytes: UInt32 = 0 + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} @@ -1013,6 +1028,80 @@ public struct HealthMetrics: Sendable { fileprivate var _temperature: Float? = nil } +/// +/// Linux host metrics +public struct HostMetrics: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// + /// Host system uptime + public var uptimeSeconds: UInt32 = 0 + + /// + /// Host system free memory + public var freememBytes: UInt64 = 0 + + /// + /// Host system disk space free for / + public var diskfree1Bytes: UInt64 = 0 + + /// + /// Secondary system disk space free + public var diskfree2Bytes: UInt64 { + get {return _diskfree2Bytes ?? 0} + set {_diskfree2Bytes = newValue} + } + /// Returns true if `diskfree2Bytes` has been explicitly set. + public var hasDiskfree2Bytes: Bool {return self._diskfree2Bytes != nil} + /// Clears the value of `diskfree2Bytes`. Subsequent reads from it will return its default value. + public mutating func clearDiskfree2Bytes() {self._diskfree2Bytes = nil} + + /// + /// Tertiary disk space free + public var diskfree3Bytes: UInt64 { + get {return _diskfree3Bytes ?? 0} + set {_diskfree3Bytes = newValue} + } + /// Returns true if `diskfree3Bytes` has been explicitly set. + public var hasDiskfree3Bytes: Bool {return self._diskfree3Bytes != nil} + /// Clears the value of `diskfree3Bytes`. Subsequent reads from it will return its default value. + public mutating func clearDiskfree3Bytes() {self._diskfree3Bytes = nil} + + /// + /// Host system one minute load in 1/100ths + public var load1: UInt32 = 0 + + /// + /// Host system five minute load in 1/100ths + public var load5: UInt32 = 0 + + /// + /// Host system fifteen minute load in 1/100ths + public var load15: UInt32 = 0 + + /// + /// Optional User-provided string for arbitrary host system information + /// that doesn't make sense as a dedicated entry. + public var userString: String { + get {return _userString ?? String()} + set {_userString = newValue} + } + /// Returns true if `userString` has been explicitly set. + public var hasUserString: Bool {return self._userString != nil} + /// Clears the value of `userString`. Subsequent reads from it will return its default value. + public mutating func clearUserString() {self._userString = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _diskfree2Bytes: UInt64? = nil + fileprivate var _diskfree3Bytes: UInt64? = nil + fileprivate var _userString: String? = nil +} + /// /// Types of Measurements the telemetry module is equipped to handle public struct Telemetry: Sendable { @@ -1086,6 +1175,16 @@ public struct Telemetry: Sendable { set {variant = .healthMetrics(newValue)} } + /// + /// Linux host metrics + public var hostMetrics: HostMetrics { + get { + if case .hostMetrics(let v)? = variant {return v} + return HostMetrics() + } + set {variant = .hostMetrics(newValue)} + } + public var unknownFields = SwiftProtobuf.UnknownStorage() public enum OneOf_Variant: Equatable, Sendable { @@ -1107,6 +1206,9 @@ public struct Telemetry: Sendable { /// /// Health telemetry metrics case healthMetrics(HealthMetrics) + /// + /// Linux host metrics + case hostMetrics(HostMetrics) } @@ -1178,6 +1280,7 @@ extension TelemetrySensorType: SwiftProtobuf._ProtoNameProviding { 36: .same(proto: "DPS310"), 37: .same(proto: "RAK12035"), 38: .same(proto: "MAX17261"), + 39: .same(proto: "PCT2075"), ] } @@ -1673,6 +1776,8 @@ extension LocalStats: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio 9: .standard(proto: "num_rx_dupe"), 10: .standard(proto: "num_tx_relay"), 11: .standard(proto: "num_tx_relay_canceled"), + 12: .standard(proto: "heap_total_bytes"), + 13: .standard(proto: "heap_free_bytes"), ] public mutating func decodeMessage(decoder: inout D) throws { @@ -1692,6 +1797,8 @@ extension LocalStats: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio case 9: try { try decoder.decodeSingularUInt32Field(value: &self.numRxDupe) }() case 10: try { try decoder.decodeSingularUInt32Field(value: &self.numTxRelay) }() case 11: try { try decoder.decodeSingularUInt32Field(value: &self.numTxRelayCanceled) }() + case 12: try { try decoder.decodeSingularUInt32Field(value: &self.heapTotalBytes) }() + case 13: try { try decoder.decodeSingularUInt32Field(value: &self.heapFreeBytes) }() default: break } } @@ -1731,6 +1838,12 @@ extension LocalStats: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio if self.numTxRelayCanceled != 0 { try visitor.visitSingularUInt32Field(value: self.numTxRelayCanceled, fieldNumber: 11) } + if self.heapTotalBytes != 0 { + try visitor.visitSingularUInt32Field(value: self.heapTotalBytes, fieldNumber: 12) + } + if self.heapFreeBytes != 0 { + try visitor.visitSingularUInt32Field(value: self.heapFreeBytes, fieldNumber: 13) + } try unknownFields.traverse(visitor: &visitor) } @@ -1746,6 +1859,8 @@ extension LocalStats: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio if lhs.numRxDupe != rhs.numRxDupe {return false} if lhs.numTxRelay != rhs.numTxRelay {return false} if lhs.numTxRelayCanceled != rhs.numTxRelayCanceled {return false} + if lhs.heapTotalBytes != rhs.heapTotalBytes {return false} + if lhs.heapFreeBytes != rhs.heapFreeBytes {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -1799,6 +1914,90 @@ extension HealthMetrics: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa } } +extension HostMetrics: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".HostMetrics" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "uptime_seconds"), + 2: .standard(proto: "freemem_bytes"), + 3: .standard(proto: "diskfree1_bytes"), + 4: .standard(proto: "diskfree2_bytes"), + 5: .standard(proto: "diskfree3_bytes"), + 6: .same(proto: "load1"), + 7: .same(proto: "load5"), + 8: .same(proto: "load15"), + 9: .standard(proto: "user_string"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self.uptimeSeconds) }() + case 2: try { try decoder.decodeSingularUInt64Field(value: &self.freememBytes) }() + case 3: try { try decoder.decodeSingularUInt64Field(value: &self.diskfree1Bytes) }() + case 4: try { try decoder.decodeSingularUInt64Field(value: &self._diskfree2Bytes) }() + case 5: try { try decoder.decodeSingularUInt64Field(value: &self._diskfree3Bytes) }() + case 6: try { try decoder.decodeSingularUInt32Field(value: &self.load1) }() + case 7: try { try decoder.decodeSingularUInt32Field(value: &self.load5) }() + case 8: try { try decoder.decodeSingularUInt32Field(value: &self.load15) }() + case 9: try { try decoder.decodeSingularStringField(value: &self._userString) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if self.uptimeSeconds != 0 { + try visitor.visitSingularUInt32Field(value: self.uptimeSeconds, fieldNumber: 1) + } + if self.freememBytes != 0 { + try visitor.visitSingularUInt64Field(value: self.freememBytes, fieldNumber: 2) + } + if self.diskfree1Bytes != 0 { + try visitor.visitSingularUInt64Field(value: self.diskfree1Bytes, fieldNumber: 3) + } + try { if let v = self._diskfree2Bytes { + try visitor.visitSingularUInt64Field(value: v, fieldNumber: 4) + } }() + try { if let v = self._diskfree3Bytes { + try visitor.visitSingularUInt64Field(value: v, fieldNumber: 5) + } }() + if self.load1 != 0 { + try visitor.visitSingularUInt32Field(value: self.load1, fieldNumber: 6) + } + if self.load5 != 0 { + try visitor.visitSingularUInt32Field(value: self.load5, fieldNumber: 7) + } + if self.load15 != 0 { + try visitor.visitSingularUInt32Field(value: self.load15, fieldNumber: 8) + } + try { if let v = self._userString { + try visitor.visitSingularStringField(value: v, fieldNumber: 9) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: HostMetrics, rhs: HostMetrics) -> Bool { + if lhs.uptimeSeconds != rhs.uptimeSeconds {return false} + if lhs.freememBytes != rhs.freememBytes {return false} + if lhs.diskfree1Bytes != rhs.diskfree1Bytes {return false} + if lhs._diskfree2Bytes != rhs._diskfree2Bytes {return false} + if lhs._diskfree3Bytes != rhs._diskfree3Bytes {return false} + if lhs.load1 != rhs.load1 {return false} + if lhs.load5 != rhs.load5 {return false} + if lhs.load15 != rhs.load15 {return false} + if lhs._userString != rhs._userString {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + extension Telemetry: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".Telemetry" public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ @@ -1809,6 +2008,7 @@ extension Telemetry: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation 5: .standard(proto: "power_metrics"), 6: .standard(proto: "local_stats"), 7: .standard(proto: "health_metrics"), + 8: .standard(proto: "host_metrics"), ] public mutating func decodeMessage(decoder: inout D) throws { @@ -1896,6 +2096,19 @@ extension Telemetry: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation self.variant = .healthMetrics(v) } }() + case 8: try { + var v: HostMetrics? + var hadOneofValue = false + if let current = self.variant { + hadOneofValue = true + if case .hostMetrics(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.variant = .hostMetrics(v) + } + }() default: break } } @@ -1934,6 +2147,10 @@ extension Telemetry: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation guard case .healthMetrics(let v)? = self.variant else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 7) }() + case .hostMetrics?: try { + guard case .hostMetrics(let v)? = self.variant else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 8) + }() case nil: break } try unknownFields.traverse(visitor: &visitor) diff --git a/scripts/gen_protos.sh b/scripts/gen_protos.sh index d07bc798..1ce1d1e9 100755 --- a/scripts/gen_protos.sh +++ b/scripts/gen_protos.sh @@ -5,6 +5,10 @@ if [ ! -x "$(which protoc)" ]; then brew install swift-protobuf fi +git submodule update --init --recursive + +git submodule foreach --recursive git pull origin master + protoc --proto_path=./protobufs --swift_opt=Visibility=Public --swift_out=./MeshtasticProtobufs/Sources ./protobufs/meshtastic/*.proto echo "Done generating the swift files from the proto files."