diff --git a/AddrHistory+CoreDataClass.swift b/AddrHistory+CoreDataClass.swift new file mode 100644 index 0000000..c571689 --- /dev/null +++ b/AddrHistory+CoreDataClass.swift @@ -0,0 +1,15 @@ +// +// AddrHistory+CoreDataClass.swift +// SubnetCalc +// +// Created by Julien Mulot on 11/03/2022. +// +// + +import Foundation +import CoreData + + +public class AddrHistory: NSManagedObject { + +} diff --git a/AddrHistory+CoreDataProperties.swift b/AddrHistory+CoreDataProperties.swift new file mode 100644 index 0000000..2dacaea --- /dev/null +++ b/AddrHistory+CoreDataProperties.swift @@ -0,0 +1,21 @@ +// +// AddrHistory+CoreDataProperties.swift +// SubnetCalc +// +// Created by Julien Mulot on 11/03/2022. +// +// + +import Foundation +import CoreData + + +extension AddrHistory { + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "AddrHistory") + } + + @NSManaged public var address: String + +} diff --git a/Base.lproj/MainMenu.xib b/Base.lproj/MainMenu.xib index c471e3c..23a58db 100644 --- a/Base.lproj/MainMenu.xib +++ b/Base.lproj/MainMenu.xib @@ -1,8 +1,8 @@ - + - + @@ -142,6 +142,12 @@ + + + + + + @@ -164,7 +170,7 @@ - + @@ -222,7 +228,7 @@ - + @@ -1018,10 +1024,10 @@ - + - + @@ -1127,7 +1133,7 @@ - + @@ -1467,18 +1473,6 @@ CA - - - - - - - - - - - - @@ -1520,7 +1514,7 @@ CA - + @@ -1532,11 +1526,23 @@ CA + + + + + + + + + + + + - + diff --git a/IPSubnetcalc.swift b/IPSubnetcalc.swift index 09b5f08..942b2eb 100644 --- a/IPSubnetcalc.swift +++ b/IPSubnetcalc.swift @@ -8,6 +8,15 @@ import Foundation import Cocoa +//********************* +//Errors for IP format +//********************* +enum SubnetCalcError: Error { + case invalidIPv4(_ info: String) + case invalidIPv4Mask(_ info: String) + case invalidIPv6(_ info: String) + case invalidIPv6Mask(_ info: String) +} class IPSubnetCalc: NSObject { //********* @@ -97,15 +106,23 @@ class IPSubnetCalc: NSObject { the binary representation of the given IP address */ - static func binarize(ipAddress: String, space: Bool = false, dotted: Bool = true) -> String { + static func binarize(ipAddress: String, space: Bool = false, dotted: Bool = true) -> String? { var ipAddressBin = [String]() var binStr = String() var ipDigits = [String]() ipDigits = ipAddress.components(separatedBy: ".") + if ipDigits.count != 4 { + return nil + } for index in 0...3 { - ipAddressBin.append(String(Int(ipDigits[index])!, radix: 2)) + if let ipDigit = Int(ipDigits[index]) { + ipAddressBin.append(String(ipDigit, radix: 2)) + } + else { + return nil + } while (ipAddressBin[index].count < 8) { ipAddressBin[index].insert("0", at: ipAddressBin[index].startIndex) } @@ -139,7 +156,7 @@ class IPSubnetCalc: NSObject { */ func binaryMap(dotted: Bool = true) -> String { - return (IPSubnetCalc.binarize(ipAddress: ipv4Address, space: false, dotted: dotted)) + return (IPSubnetCalc.binarize(ipAddress: ipv4Address, space: false, dotted: dotted)!) } /** @@ -153,14 +170,22 @@ class IPSubnetCalc: NSObject { the hexadecimal representation of the given IP address */ - static func hexarize(ipAddress: String, dotted: Bool = true) -> String { + static func hexarize(ipAddress: String, dotted: Bool = true) -> String? { var ipDigits = [String]() var hexIP = String() var hex4: String ipDigits = ipAddress.components(separatedBy: ".") + if ipDigits.count != 4 { + return nil + } for index in 0...3 { - hex4 = String(format: "%X", Int(ipDigits[index])!) + if let ipDigit = Int(ipDigits[index]) { + hex4 = String(format: "%X", ipDigit) + } + else { + return nil + } if (hex4.count == 1) { hex4 = "0" + hex4 } @@ -184,7 +209,7 @@ class IPSubnetCalc: NSObject { */ func hexaMap(dotted: Bool = true) -> String { - return (IPSubnetCalc.hexarize(ipAddress: ipv4Address, dotted: dotted)) + return (IPSubnetCalc.hexarize(ipAddress: ipv4Address, dotted: dotted)!) } /** @@ -196,14 +221,22 @@ class IPSubnetCalc: NSObject { a digital IP address in UInt32 format */ - static func digitize(ipAddress: String) -> UInt32 { + static func digitize(ipAddress: String) -> UInt32? { var ipAddressNum: UInt32 = 0 var ipDigits = [String]() + //var ipDigit: Unit32 ipDigits = ipAddress.components(separatedBy: ".") - + if ipDigits.count != 4 { + return nil + } for index in 0...3 { - ipAddressNum += UInt32(ipDigits[index])! << (32 - 8 * (index + 1)) + if let ipDigit = UInt32(ipDigits[index]) { + ipAddressNum += ipDigit << (32 - 8 * (index + 1)) + } + else { + return nil + } } return (ipAddressNum & Constants.addr32Full) } @@ -229,8 +262,11 @@ class IPSubnetCalc: NSObject { a digital subnet mask in UInt32 format */ - static func digitize(maskbits: Int) -> UInt32 { - return ((Constants.addr32Full << (32 - maskbits)) & Constants.addr32Full) + static func digitize(maskbits: Int) -> UInt32? { + if (maskbits <= Constants.NETWORK_BITS_MAX && maskbits >= Constants.NETWORK_BITS_MIN_CLASSLESS) { + return ((Constants.addr32Full << (32 - maskbits)) & Constants.addr32Full) + } + return nil } /** @@ -260,11 +296,10 @@ class IPSubnetCalc: NSObject { - mask: Optionnal subnet mask - classless: enable class less checks of the given IP address/mask - - Returns: - Boolean if the given IP address is valid or not + - Throws: an invalid IP or invalid mask error with a message explaining the reason */ - static func isValidIP(ipAddress: String, mask: String?, classless: Bool = false) -> Bool { + static func validateIPv4(ipAddress: String, mask: String?, classless: Bool = false) throws { var ip4Digits = [String]() ip4Digits = ipAddress.components(separatedBy: ".") @@ -273,41 +308,47 @@ class IPSubnetCalc: NSObject { if let digit = Int(item, radix: 10) { if (digit > 255) { print("bad IPv4 digit \(digit)") - return false + throw SubnetCalcError.invalidIPv4("IPv4 digit \(digit) is greater than 255") + //return false } } else { print("not digit: \(item)") - return false + throw SubnetCalcError.invalidIPv4("not digit: \(item)") + //return false } } } else { print("bad IPv4 format \(ip4Digits)") - return false + throw SubnetCalcError.invalidIPv4("\(ipAddress) too short or too long") + //return false } if mask != nil { if let maskNum = Int(mask!) { if (classless == true) { if (maskNum < Constants.NETWORK_BITS_MIN_CLASSLESS || maskNum > Constants.NETWORK_BITS_MAX) { print("IPv4 classless mask \(maskNum) invalid") - return false + throw SubnetCalcError.invalidIPv4Mask("IPv4 classless mask \(maskNum) should be between \(Constants.NETWORK_BITS_MIN_CLASSLESS) and \(Constants.NETWORK_BITS_MAX)") + //return false } } else if (maskNum < Constants.NETWORK_BITS_MIN || maskNum > Constants.NETWORK_BITS_MAX) { print("IPv4 mask \(maskNum) invalid") - return false + throw SubnetCalcError.invalidIPv4Mask("IPv4 mask \(maskNum) should be between \(Constants.NETWORK_BITS_MIN) and \(Constants.NETWORK_BITS_MAX)") + //return false } } else { print("IPv4 mask \(mask!) is not digit") - return false + throw SubnetCalcError.invalidIPv4Mask("IPv4 mask \(mask!) is not a digit") + //return false } } else { //print("null mask") } - return true + //return true } /** @@ -319,8 +360,8 @@ class IPSubnetCalc: NSObject { */ func subnetId() -> String { var subnetId: UInt32 = 0 - let ipBits = IPSubnetCalc.digitize(ipAddress: self.ipv4Address) - let maskBits = IPSubnetCalc.digitize(maskbits: self.maskBits) + let ipBits = IPSubnetCalc.digitize(ipAddress: self.ipv4Address)! + let maskBits = IPSubnetCalc.digitize(maskbits: self.maskBits)! subnetId = ipBits & maskBits return (IPSubnetCalc.dottedDecimal(ipAddress: subnetId)) @@ -335,8 +376,8 @@ class IPSubnetCalc: NSObject { */ func subnetBroadcast() -> String { var broadcast: UInt32 = 0 - let ipBits = IPSubnetCalc.digitize(ipAddress: self.ipv4Address) - let maskBits = IPSubnetCalc.digitize(maskbits: self.maskBits) + let ipBits = IPSubnetCalc.digitize(ipAddress: self.ipv4Address)! + let maskBits = IPSubnetCalc.digitize(maskbits: self.maskBits)! broadcast = ipBits & maskBits | (Constants.addr32Full >> self.maskBits) return (IPSubnetCalc.dottedDecimal(ipAddress: broadcast)) @@ -447,12 +488,12 @@ class IPSubnetCalc: NSObject { var lastIP: UInt32 = 0 if (maskBits == 31 || maskBits == 32) { - firstIP = IPSubnetCalc.digitize(ipAddress: subnetId()) - lastIP = IPSubnetCalc.digitize(ipAddress: subnetBroadcast()) + firstIP = IPSubnetCalc.digitize(ipAddress: subnetId())! + lastIP = IPSubnetCalc.digitize(ipAddress: subnetBroadcast())! } else { - firstIP = IPSubnetCalc.digitize(ipAddress: subnetId()) + 1 - lastIP = IPSubnetCalc.digitize(ipAddress: subnetBroadcast()) - 1 + firstIP = IPSubnetCalc.digitize(ipAddress: subnetId())! + 1 + lastIP = IPSubnetCalc.digitize(ipAddress: subnetBroadcast())! - 1 } range = IPSubnetCalc.dottedDecimal(ipAddress: firstIP) + " - " + IPSubnetCalc.dottedDecimal(ipAddress: lastIP) return (range) @@ -470,8 +511,8 @@ class IPSubnetCalc: NSObject { var firstIP: UInt32 = 0 var lastIP: UInt32 = 0 - firstIP = IPSubnetCalc.digitize(ipAddress: subnetId()) - lastIP = IPSubnetCalc.digitize(ipAddress: subnetBroadcast()) + firstIP = IPSubnetCalc.digitize(ipAddress: subnetId())! + lastIP = IPSubnetCalc.digitize(ipAddress: subnetBroadcast())! range = IPSubnetCalc.dottedDecimal(ipAddress: firstIP) + " - " + IPSubnetCalc.dottedDecimal(ipAddress: lastIP) return (range) } @@ -485,8 +526,8 @@ class IPSubnetCalc: NSObject { Network Class conforming to RFC 790 */ - static func netClass(ipAddress: String) -> String { - let ipNum = IPSubnetCalc.digitize(ipAddress: ipAddress) + static func netClass(ipAddress: String) -> String? { + if let ipNum = IPSubnetCalc.digitize(ipAddress: ipAddress) { let addr1stByte = (ipNum & Constants.maskClassA) >> 24 if (addr1stByte < 127) { @@ -502,6 +543,8 @@ class IPSubnetCalc: NSObject { return ("D") } return ("E") + } + return nil } /** @@ -512,7 +555,7 @@ class IPSubnetCalc: NSObject { */ func netClass() -> String { - return (IPSubnetCalc.netClass(ipAddress: ipv4Address)) + return (IPSubnetCalc.netClass(ipAddress: ipv4Address)!) } /** @@ -603,16 +646,18 @@ class IPSubnetCalc: NSObject { the number of bits for the given mask */ - static func maskBits(maskAddr: String) -> Int { + static func maskBits(maskAddr: String) -> Int? { var bits: Int = 0 - var mask:UInt32 = IPSubnetCalc.digitize(ipAddress: maskAddr) + if var mask:UInt32 = IPSubnetCalc.digitize(ipAddress: maskAddr) { while (mask != 0) { bits += 1 mask <<= 1 } //print("maskBits \(maskAddr) bits: \(bits)") return (bits) + } + return nil } /** @@ -766,8 +811,8 @@ class IPSubnetCalc: NSObject { print("CIDR Network (Route) : " + self.subnetId()) print("CIDR Net Notation : " + self.subnetId() + "/" + String(self.maskBits)) print("CIDR Address Range : " + self.subnetCIDRRange()) - print("IP number in binary : " + String(IPSubnetCalc.digitize(ipAddress: self.ipv4Address), radix: 2)) - print("Mask bin : " + String(IPSubnetCalc.digitize(maskbits: self.maskBits), radix: 2)) + print("IP number in binary : " + String(IPSubnetCalc.digitize(ipAddress: self.ipv4Address)!, radix: 2)) + print("Mask bin : " + String(IPSubnetCalc.digitize(maskbits: self.maskBits)!, radix: 2)) //print("Subnet ID bin : " + String(self.subnetId(), radix: 2)) //print("Broadcast bin : " + String(self.subnetBroadcast(), radix: 2)) } @@ -786,14 +831,14 @@ class IPSubnetCalc: NSObject { Boolean if the given IPv6 address is valid or not */ - static func isValidIPv6(ipAddress: String, mask: Int?) -> Bool { + static func validateIPv6(ipAddress: String, mask: Int?) throws { var ip4Hex: [String]? var hex: UInt16? if mask != nil { if (mask! < 1 || mask! > 128) { print("mask \(mask!) invalid") - return false + throw SubnetCalcError.invalidIPv6Mask("mask \(mask!) must be between 1 and 128") } } else { @@ -803,7 +848,7 @@ class IPSubnetCalc: NSObject { ip4Hex = ipAddress.components(separatedBy: ":") if (ip4Hex == nil) { //print("\(ipAddress) invalid") - return false + throw SubnetCalcError.invalidIPv6("IPv6 address must contain :") } if (ip4Hex!.count != 8) { //print("no 8 hex") @@ -811,35 +856,35 @@ class IPSubnetCalc: NSObject { { if (ipAddress.components(separatedBy: "::").count > 2) { //print("too many '::'") - return false + throw SubnetCalcError.invalidIPv6("too many ::") } } else { //print("IPv6 \(ipAddress) bad format") - return false + throw SubnetCalcError.invalidIPv6("short IPv6 address must contain ::") } } for index in 0...(ip4Hex!.count - 1) { //print("Index : \(index) IPHex : \(ip4Hex[index]) Dec : \(String(UInt16(ip4Hex[index], radix: 16)!, radix: 16))") if (ip4Hex![index].count > 4 && ip4Hex![index].count != 0) { //print("\(ip4Hex![index]) too large") - return false + throw SubnetCalcError.invalidIPv6("\(ip4Hex![index]) segment is too large") } hex = UInt16(ip4Hex![index], radix: 16) if hex != nil { if (hex! < 0 || hex! > 0xFFFF) { //print("\(hex!) is invalid") - return false + throw SubnetCalcError.invalidIPv6("\(hex!) segment must be between 0 and 0xFFFF") } } else { if (ip4Hex![index] != "") { //print("\(ip4Hex![index]) not an integer") - return false + throw SubnetCalcError.invalidIPv6("\(ip4Hex![index]) segment is not an integer") } } } - return true + //return true } /** @@ -856,7 +901,7 @@ class IPSubnetCalc: NSObject { static func convertIPv4toIPv6(ipAddress: String, _6to4: Bool = false) -> String { var ipv6str = String() - let addr = digitize(ipAddress: ipAddress) + if let addr = digitize(ipAddress: ipAddress) { ipv6str.append(String((((Constants.addr32Digit1 | Constants.addr32Digit2) & addr) >> 16), radix: 16)) ipv6str.append(":") ipv6str.append(String(((Constants.addr32Digit3 | Constants.addr32Digit4) & addr), radix: 16)) @@ -865,6 +910,10 @@ class IPSubnetCalc: NSObject { return ("2002:" + ipv6str + ":0:0:0:0:0") } return ("0:0:0:0:0:ffff:" + ipv6str) + } + else { + return "" + } } /** @@ -1312,13 +1361,15 @@ class IPSubnetCalc: NSObject { */ init?(ipAddress: String, maskbits: Int) { - if (IPSubnetCalc.isValidIP(ipAddress: ipAddress, mask: String(maskbits), classless: true)) { + do { + try IPSubnetCalc.validateIPv4(ipAddress: ipAddress, mask: String(maskbits), classless: true) self.ipv4Address = ipAddress self.maskBits = maskbits self.ipv6Address = IPSubnetCalc.convertIPv4toIPv6(ipAddress: ipAddress) self.ipv6MaskBits = maskbits + Constants.defaultIPv6to4Mask } - else { + catch { + print("Init error: \(error)") return nil } } @@ -1336,7 +1387,8 @@ class IPSubnetCalc: NSObject { convenience init?(_ ipAddress: String) { var classbit: Int - if (IPSubnetCalc.isValidIP(ipAddress: ipAddress, mask: nil)) { + do { + try IPSubnetCalc.validateIPv4(ipAddress: ipAddress, mask: nil) switch (IPSubnetCalc.netClass(ipAddress: ipAddress)) { case "A": classbit = Constants.classAbits @@ -1349,7 +1401,8 @@ class IPSubnetCalc: NSObject { } self.init(ipAddress: ipAddress, maskbits: classbit) } - else { + catch { + print("Init error: \(error)") return nil } } @@ -1366,7 +1419,8 @@ class IPSubnetCalc: NSObject { */ init?(ipv6: String, maskbits: Int) { - if (IPSubnetCalc.isValidIPv6(ipAddress: ipv6, mask: maskbits)) { + do { + try IPSubnetCalc.validateIPv6(ipAddress: ipv6, mask: maskbits) (self.ipv4Address, _) = IPSubnetCalc.convertIPv6toIPv4(ipAddress: ipv6) if (maskbits >= (Constants.defaultIPv6to4Mask + Constants.classAbits)) { self.maskBits = maskbits - Constants.defaultIPv6to4Mask @@ -1380,7 +1434,8 @@ class IPSubnetCalc: NSObject { self.ipv6MaskBits = maskbits //print("init IPv6 ipv6 addr: \(self.ipv6Address) ipv4 addr: \(self.ipv4Address)") } - else { + catch { + print("Init error: \(error)") return nil } } diff --git a/README.md b/README.md index 642e77b..ff6104d 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,10 @@ # SubnetCalc Subnet Calculator for MacOS -What's New in version 2.4: -- IPv6 address range bug fix - - - +What's New in version 2.5: +- Add history in IP address text field (limited to 30 entries) +- Persisting history between App launches with Core Data +- Better error handling and more explicit alert messages For more information: http://subnetcalc.mulot.org diff --git a/SubnetCalc.xcdatamodeld/SubnetCalc.xcdatamodel/contents b/SubnetCalc.xcdatamodeld/SubnetCalc.xcdatamodel/contents new file mode 100644 index 0000000..1cffdd7 --- /dev/null +++ b/SubnetCalc.xcdatamodeld/SubnetCalc.xcdatamodel/contents @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/SubnetCalc.xcodeproj/project.pbxproj b/SubnetCalc.xcodeproj/project.pbxproj index 84f7848..7be27f1 100644 --- a/SubnetCalc.xcodeproj/project.pbxproj +++ b/SubnetCalc.xcodeproj/project.pbxproj @@ -16,6 +16,9 @@ 94AC2613256AF7C900811156 /* SubnetCalcAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94AC2612256AF7C900811156 /* SubnetCalcAppDelegate.swift */; }; 94B1C507256ABC1B00F992A3 /* IPSubnetcalc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94B1C506256ABC1B00F992A3 /* IPSubnetcalc.swift */; }; 94B6031212F74DC7005AA5EB /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 94B6031112F74DC7005AA5EB /* CoreFoundation.framework */; }; + 94DBA15627DBD0BF00F61918 /* SubnetCalc.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 94DBA15427DBD0BF00F61918 /* SubnetCalc.xcdatamodeld */; }; + 94DBA15E27DBDAC000F61918 /* AddrHistory+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94DBA15C27DBDAC000F61918 /* AddrHistory+CoreDataClass.swift */; }; + 94DBA15F27DBDAC000F61918 /* AddrHistory+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94DBA15D27DBDAC000F61918 /* AddrHistory+CoreDataProperties.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -47,6 +50,9 @@ 94B6031112F74DC7005AA5EB /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; 94CDB5F6255EE11D0010F1E4 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 94D8C5731520F99500B27A3A /* SubnetCalc.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = SubnetCalc.entitlements; sourceTree = ""; }; + 94DBA15527DBD0BF00F61918 /* SubnetCalc.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = SubnetCalc.xcdatamodel; sourceTree = ""; }; + 94DBA15C27DBDAC000F61918 /* AddrHistory+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AddrHistory+CoreDataClass.swift"; sourceTree = ""; }; + 94DBA15D27DBDAC000F61918 /* AddrHistory+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AddrHistory+CoreDataProperties.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -72,7 +78,10 @@ 080E96DDFE201D6D7F000001 /* Classes */ = { isa = PBXGroup; children = ( + 94DBA15C27DBDAC000F61918 /* AddrHistory+CoreDataClass.swift */, + 94DBA15D27DBDAC000F61918 /* AddrHistory+CoreDataProperties.swift */, 94B1C506256ABC1B00F992A3 /* IPSubnetcalc.swift */, + 94DBA15427DBD0BF00F61918 /* SubnetCalc.xcdatamodeld */, 94AC2612256AF7C900811156 /* SubnetCalcAppDelegate.swift */, ); name = Classes; @@ -267,7 +276,10 @@ buildActionMask = 2147483647; files = ( 94B1C507256ABC1B00F992A3 /* IPSubnetcalc.swift in Sources */, + 94DBA15627DBD0BF00F61918 /* SubnetCalc.xcdatamodeld in Sources */, 94AC2613256AF7C900811156 /* SubnetCalcAppDelegate.swift in Sources */, + 94DBA15E27DBDAC000F61918 /* AddrHistory+CoreDataClass.swift in Sources */, + 94DBA15F27DBDAC000F61918 /* AddrHistory+CoreDataProperties.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -401,7 +413,7 @@ CODE_SIGN_IDENTITY = "Mac Developer"; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 10; + CURRENT_PROJECT_VERSION = 11; DEVELOPMENT_TEAM = VNLK894MAE; ENABLE_HARDENED_RUNTIME = YES; GCC_DYNAMIC_NO_PIC = NO; @@ -411,7 +423,7 @@ INFOPLIST_FILE = "SubnetCalc-Info.plist"; INSTALL_PATH = "$(HOME)/Applications"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; - MARKETING_VERSION = 2.4; + MARKETING_VERSION = 2.5; PRODUCT_BUNDLE_IDENTIFIER = net.mulot.subnetcalc; PRODUCT_NAME = SubnetCalc; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -430,7 +442,7 @@ CODE_SIGN_ENTITLEMENTS = SubnetCalc.entitlements; CODE_SIGN_IDENTITY = "Mac Developer"; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 10; + CURRENT_PROJECT_VERSION = 11; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = VNLK894MAE; ENABLE_HARDENED_RUNTIME = YES; @@ -439,7 +451,7 @@ INFOPLIST_FILE = "SubnetCalc-Info.plist"; INSTALL_PATH = /Applications; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; - MARKETING_VERSION = 2.4; + MARKETING_VERSION = 2.5; PRODUCT_BUNDLE_IDENTIFIER = net.mulot.subnetcalc; PRODUCT_NAME = SubnetCalc; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -573,6 +585,19 @@ defaultConfigurationName = Debug; }; /* End XCConfigurationList section */ + +/* Begin XCVersionGroup section */ + 94DBA15427DBD0BF00F61918 /* SubnetCalc.xcdatamodeld */ = { + isa = XCVersionGroup; + children = ( + 94DBA15527DBD0BF00F61918 /* SubnetCalc.xcdatamodel */, + ); + currentVersion = 94DBA15527DBD0BF00F61918 /* SubnetCalc.xcdatamodel */; + path = SubnetCalc.xcdatamodeld; + sourceTree = ""; + versionGroupType = wrapper.xcdatamodel; + }; +/* End XCVersionGroup section */ }; rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; } diff --git a/SubnetCalcAppDelegate.swift b/SubnetCalcAppDelegate.swift index 0c42401..5c0281f 100644 --- a/SubnetCalcAppDelegate.swift +++ b/SubnetCalcAppDelegate.swift @@ -7,6 +7,7 @@ import Foundation import Cocoa +import CoreData @main class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, NSTableViewDataSource { @@ -17,6 +18,7 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, static let defaultIP: String = "10.0.0.0" static let defaultIPv6Mask: String = "64" static let defaultIPv6to4Mask: Int = 96 + static let maxAddrHistory: Int = 30 static let BUFFER_LINES:Int = 200000000 static let NETWORK_BITS_MIN_CLASSLESS:Int = 1 static let NETWORK_BITS_MIN:Int = 8 @@ -26,7 +28,7 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, //General UI elements //******************* @IBOutlet var window: NSWindow! - @IBOutlet var addrField: NSTextField! + @IBOutlet var addrField: NSComboBox! @IBOutlet var exportButton: NSPopUpButton! @IBOutlet var tabView: NSTabView! @IBOutlet var darkModeMenu: NSMenuItem! @@ -94,6 +96,8 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, private var ipsc: IPSubnetCalc? private var subnetsVLSM = [(Int, String, String)]() private var globalMaskVLSM: UInt32! + private var container: NSPersistentContainer! + private var history = [AddrHistory]() //********************** //Private IPv4 functions @@ -224,7 +228,7 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, return (String(ipInfo[0]), String(ipInfo[1])) } else if ipInfo.count > 2 { - print("Bad IP format: \(ipInfo)") + print("Invalid IP format: \(ipInfo)") return ("", nil) } return (address, nil) @@ -315,14 +319,14 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, if (ipsc != nil) { //print("doVLSM") maskBitsVLSMCombo.selectItem(withObjectValue: String(ipsc!.maskBits)) - var maskVLSM = ~IPSubnetCalc.digitize(maskbits: ipsc!.maskBits) + 1 + var maskVLSM = ~IPSubnetCalc.digitize(maskbits: ipsc!.maskBits)! + 1 if (subnetsVLSM.count != 0) { var fitsRequirements = true for index in (0...(subnetsVLSM.count - 1)) { let maskbits = subnetsVLSM[index].0 //print("Mask VLSM: \(IPSubnetCalc.digitize(ipAddress: maskVLSM)) Maskbits: \(IPSubnetCalc.digitize(ipAddress: ~IPSubnetCalc.numerize(maskbits: maskbits)))") - if (maskVLSM > ~IPSubnetCalc.digitize(maskbits: maskbits)) { - maskVLSM = maskVLSM - (~IPSubnetCalc.digitize(maskbits: maskbits) + 1) + if (maskVLSM > ~IPSubnetCalc.digitize(maskbits: maskbits)!) { + maskVLSM = maskVLSM - (~IPSubnetCalc.digitize(maskbits: maskbits)! + 1) //print("Mask AFTER VLSM: \(IPSubnetCalc.digitize(ipAddress: maskVLSM))") } else { @@ -400,8 +404,10 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, Check if there is are current IP address and mask otherwise take the default IP and mask. Check if the IP address and mask are valid. + + - Throws: an invalid IP or invalid mask error with a message explaining the reason */ - private func doIPSubnetCalc() + private func doIPSubnetCalc() throws { var ipaddr: String var ipmask: String? @@ -422,7 +428,8 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, else { (ipaddr, ipmask) = splitAddrMask(address: addrField.stringValue) addrField.stringValue = ipaddr - if (IPSubnetCalc.isValidIPv6(ipAddress: ipaddr, mask: Int(ipmask ?? Constants.defaultIPv6Mask)) == true) { + do { + try IPSubnetCalc.validateIPv6(ipAddress: ipaddr, mask: Int(ipmask ?? Constants.defaultIPv6Mask)) if (ipsc != nil) { ipaddr = ipsc!.ipv4Address } @@ -435,11 +442,14 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, } } } + catch { + } if (ipmask == nil && ipsc != nil) { ipmask = String(ipsc!.maskBits) } } - if (IPSubnetCalc.isValidIP(ipAddress: ipaddr, mask: ipmask) == true) { + do { + try IPSubnetCalc.validateIPv4(ipAddress: ipaddr, mask: ipmask) //print("IP Address: \(ipaddr) mask: \(ipmask)") if (ipmask == nil) { ipsc = IPSubnetCalc(ipaddr) @@ -457,9 +467,79 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, self.doVLSM() } } + catch SubnetCalcError.invalidIPv4(let info) { + myAlert(message: "Invalid IPv4 Address", info: info) + throw SubnetCalcError.invalidIPv4(info) + } + catch SubnetCalcError.invalidIPv4Mask(let info) { + myAlert(message: "Invalid IPv4 Mask", info: info) + throw SubnetCalcError.invalidIPv4Mask(info) + } + catch { + myAlert(message: "Unknown invalid error", info: "\(ipaddr)/\(ipmask ?? "") Error: \(error)") + throw error + } + } + + /** + Compute IPv4 or IPv6 infos depending of IP address format in IP address field + + - Throws: an invalid IP or invalid mask error with a message explaining the reason + */ + private func doCalc() throws + { + if (addrField.stringValue.contains(":")) { + do { + try self.doIPv6SubnetCalc() + tabView.selectTabViewItem(at: 5) + } + catch { + throw error + } + } else { - myAlert(message: "Bad IPv4 Address", info: "Bad format: \(ipaddr)/\(ipmask ?? "")") - return + do { + try self.doIPSubnetCalc() + tabView.selectTabViewItem(at: 0) + } + catch { + throw error + } + } + } + + /** + Save the address IP field history for future App sessions + */ + private func saveHistory() { + if container.viewContext.hasChanges { + do { + try container.viewContext.save() + } catch { + print("An error occurred while saving: \(error)") + } + } + } + + /** + Load in the address IP field the history of previous App sessions + */ + private func loadHistory() { + container = NSPersistentContainer(name: "SubnetCalc") + container.loadPersistentStores { storeDescription, error in + if let error = error { + print("Unresolved error \(error)") + } + } + do { + history = try container.viewContext.fetch(AddrHistory.fetchRequest()) + //print("Got \(history.count) items") + for item in history { + //print(item.address) + addrField.addItem(withObjectValue: item.address) + } + } catch { + print("Fetch history failed") } } @@ -529,20 +609,24 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, Genrate infos also for all IPv4 tabs based on the converted IPv4 address Check if the IPv6 address and mask are valid. + + - Throws: an invalid IP or invalid mask error with a message explaining the reason */ - private func doIPv6SubnetCalc() + private func doIPv6SubnetCalc() throws { var ipaddr: String var ipmask: String? (ipaddr, ipmask) = splitAddrMask(address: addrField.stringValue) addrField.stringValue = ipaddr - if (IPSubnetCalc.isValidIP(ipAddress: ipaddr, mask: ipmask) == true) { + do { + try IPSubnetCalc.validateIPv4(ipAddress: ipaddr, mask: ipmask) if (ipsc != nil) { ipaddr = ipsc!.ipv6Address //print("doIPv6SubnetCalc ipaddr to ipv6 : \(ipsc!.ipv6Address)") } } + catch { } if (ipmask == nil) { if (ipsc != nil) { ipmask = String(ipsc!.ipv6MaskBits) @@ -553,10 +637,11 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, } } else if (Int(ipmask!) == nil) { - myAlert(message: "Bad IPv6 mask", info: "Bad format: \(ipmask!)") + myAlert(message: "Invalid IPv6 mask", info: "\(ipmask!) is not an integer") return } - if (IPSubnetCalc.isValidIPv6(ipAddress: ipaddr, mask: Int(ipmask!)) == true) { + do { + try IPSubnetCalc.validateIPv6(ipAddress: ipaddr, mask: Int(ipmask!)) //print("IP Address: \(ipaddr) mask: \(ipmask)") ipsc = IPSubnetCalc(ipv6: ipaddr, maskbits: Int(ipmask!)!) if (ipsc != nil) { @@ -569,9 +654,17 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, self.doIPv6() } } - else { - myAlert(message: "Bad IPv6 Address", info: "Bad format: \(ipaddr)/\(ipmask ?? "")") - return + catch SubnetCalcError.invalidIPv6(let info) { + myAlert(message: "Invalid IPv6 Address", info: info) + throw SubnetCalcError.invalidIPv6(info) + } + catch SubnetCalcError.invalidIPv6Mask(let info) { + myAlert(message: "Invalid IPv6 Mask", info: info) + throw SubnetCalcError.invalidIPv6Mask(info) + } + catch { + myAlert(message: "Unknown invalid IPv6 error", info: "\(ipaddr)/\(ipmask ?? "") Error: \(error)") + throw error } } @@ -632,10 +725,13 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, } if (sender.indexOfSelectedItem != -1) { ipsc!.maskBits = 31 - sender.indexOfSelectedItem() - self.doIPSubnetCalc() + do { + try self.doIPSubnetCalc() + } + catch {} } else { - myAlert(message: "Bad Max Hosts", info: "Bad selection") + myAlert(message: "Invalid Max Hosts", info: "Bad selection") return } } @@ -654,10 +750,13 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, } if (sender.indexOfSelectedItem != -1) { ipsc!.maskBits = 8 + sender.indexOfSelectedItem() - self.doIPSubnetCalc() + do { + try self.doIPSubnetCalc() + } + catch {} } else { - myAlert(message: "Bad Max Subnets", info: "Bad selection") + myAlert(message: "Invalid Max Subnets", info: "Bad selection") return } } @@ -676,10 +775,13 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, } if (sender.objectValueOfSelectedItem as? String) != nil { ipsc!.maskBits = sender.intValue + ipsc!.netBits() - self.doIPSubnetCalc() + do { + try self.doIPSubnetCalc() + } + catch {} } else { - myAlert(message: "Bad Subnet Bits", info: "Bad selection") + myAlert(message: "Invalid Subnet Bits", info: "Bad selection") return } } @@ -698,7 +800,7 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, } // Check cast as String needed ? if let maskStr = sender.objectValueOfSelectedItem as? String { - let mask:UInt32 = IPSubnetCalc.digitize(ipAddress: maskStr) + if let mask:UInt32 = IPSubnetCalc.digitize(ipAddress: maskStr) { if (wildcard.state == NSControl.StateValue.on) { ipsc!.maskBits = IPSubnetCalc.maskBits(mask: ~mask) } @@ -706,10 +808,18 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, ipsc!.maskBits = IPSubnetCalc.maskBits(mask: mask) //print("changeSubnetMask object value : \(str)") } - self.doIPSubnetCalc() + do { + try self.doIPSubnetCalc() + } + catch {} + } + else { + myAlert(message: "Invalid Subnet Mask", info: "Bad format \(maskStr)") + return + } } else { - myAlert(message: "Bad Subnet Mask", info: "Bad selection") + myAlert(message: "Invalid Subnet Mask", info: "Bad selection") return } } @@ -728,10 +838,13 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, } if (sender.objectValueOfSelectedItem as? String) != nil { ipsc!.maskBits = sender.intValue - self.doIPSubnetCalc() + do { + try self.doIPSubnetCalc() + } + catch {} } else { - myAlert(message: "Bad Mask Bits", info: "Bad selection") + myAlert(message: "Invalid Mask Bits", info: "Bad selection") return } } @@ -763,14 +876,17 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, } if (result >= 0) { ipsc!.maskBits = sender.intValue - self.doIPSubnetCalc() + do { + try self.doIPSubnetCalc() + } + catch {} } else { doCIDR(maskbits: sender.intValue) } } else { - myAlert(message: "Bad CIDR Mask Bits", info: "Bad selection") + myAlert(message: "Invalid CIDR Mask Bits", info: "Bad selection") return } } @@ -788,7 +904,7 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, ipsc = IPSubnetCalc(Constants.defaultIP) } if let maskStr = sender.objectValueOfSelectedItem as? String { - let mask:UInt32 = IPSubnetCalc.digitize(ipAddress: maskStr) + if let mask:UInt32 = IPSubnetCalc.digitize(ipAddress: maskStr) { let maskbits:Int = IPSubnetCalc.maskBits(mask: mask) let classType = ipsc!.netClass() var result: Int = -1 @@ -804,14 +920,22 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, } if (result >= 0) { ipsc!.maskBits = maskbits - self.doIPSubnetCalc() + do { + try self.doIPSubnetCalc() + } + catch {} } else { doCIDR(maskbits: maskbits) } + } + else { + myAlert(message: "Invalid CIDR Mask", info: "Bad format \(maskStr)") + return + } } else { - myAlert(message: "Bad CIDR Mask", info: "Bad selection") + myAlert(message: "Invalid CIDR Mask", info: "Bad selection") return } } @@ -845,12 +969,12 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, doCIDR(maskbits: result) } else { - myAlert(message: "Bad Max Supernets", info: "Value too high") + myAlert(message: "Invalid Max Supernets", info: "Value too high") return } } else { - myAlert(message: "Bad Max Supernets", info: "Bad selection") + myAlert(message: "Invalid Max Supernets", info: "Bad selection") return } } @@ -872,12 +996,12 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, doCIDR(maskbits: (32 - sender.indexOfSelectedItem - 1)) } else { - myAlert(message: "Bad Max Adresses", info: "Bad value") + myAlert(message: "Invalid Max Adresses", info: "Bad value") return } } else { - myAlert(message: "Bad Max Adresses", info: "Bad selection") + myAlert(message: "Invalid Max Adresses", info: "Bad selection") return } } @@ -899,12 +1023,12 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, doCIDR(maskbits: (32 - sender.indexOfSelectedItem)) } else { - myAlert(message: "Bad Max Subnets", info: "Bad value") + myAlert(message: "Invalid Max Subnets", info: "Bad value") return } } else { - myAlert(message: "Bad Max Subnets", info: "Bad selection") + myAlert(message: "Invalid Max Subnets", info: "Bad selection") return } } @@ -981,7 +1105,7 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, if (ipsc != nil) { //print("Refresh TableView: \(String(describing: tableView.identifier))") if (tableView == subnetsHostsView) { - let ipaddr: UInt32 = (((IPSubnetCalc.digitize(ipAddress: ipsc!.ipv4Address) & ipsc!.classMask()) >> (32 - ipsc!.maskBits)) + UInt32(row)) << (32 - ipsc!.maskBits) + let ipaddr: UInt32 = (((IPSubnetCalc.digitize(ipAddress: ipsc!.ipv4Address)! & ipsc!.classMask()) >> (32 - ipsc!.maskBits)) + UInt32(row)) << (32 - ipsc!.maskBits) let ipsc_tmp = IPSubnetCalc(ipAddress: IPSubnetCalc.dottedDecimal(ipAddress: ipaddr), maskbits: ipsc!.maskBits) //print("tableView Row: \(row) IP num : \(ipaddr) IP: \(IPSubnetCalc.digitize(ipAddress: ipaddr)) IP Subnet: \(ipsc_tmp!.subnetId())") if (tableColumn != nil && ipsc_tmp != nil) { @@ -1001,7 +1125,7 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, } else if (tableView == viewFLSM) { //print("refresh View FLSM") - var ipaddr: UInt32 = ((IPSubnetCalc.digitize(ipAddress: ipsc!.ipv4Address) & IPSubnetCalc.digitize(maskbits: ipsc!.maskBits)) >> (32 - ipsc!.maskBits)) << (32 - ipsc!.maskBits) + var ipaddr: UInt32 = ((IPSubnetCalc.digitize(ipAddress: ipsc!.ipv4Address)! & IPSubnetCalc.digitize(maskbits: ipsc!.maskBits)!) >> (32 - ipsc!.maskBits)) << (32 - ipsc!.maskBits) ipaddr = (ipaddr >> (32 - (ipsc!.maskBits + slideFLSM.integerValue)) + UInt32(row)) << (32 - (ipsc!.maskBits + slideFLSM.integerValue)) let ipsc_tmp = IPSubnetCalc(ipAddress: IPSubnetCalc.dottedDecimal(ipAddress: ipaddr), maskbits: (ipsc!.maskBits + slideFLSM.integerValue)) if (tableColumn != nil && ipsc_tmp != nil) { @@ -1029,10 +1153,10 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, return (row + 1) } else if (tableColumn!.identifier.rawValue == "subnetVLSMCol") { - var subnet = IPSubnetCalc.digitize(ipAddress: ipsc!.subnetId()) + var subnet = IPSubnetCalc.digitize(ipAddress: ipsc!.subnetId())! if (row > 0) { for index in (0...(row - 1)) { - subnet = subnet + ~IPSubnetCalc.digitize(maskbits: subnetsVLSM[index].0) + 1 + subnet = subnet + ~IPSubnetCalc.digitize(maskbits: subnetsVLSM[index].0)! + 1 } } return (IPSubnetCalc.dottedDecimal(ipAddress: subnet)) @@ -1047,10 +1171,10 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, return (subnetsVLSM[row].2) } else if (tableColumn!.identifier.rawValue == "rangeVLSMCol") { - var subnet = IPSubnetCalc.digitize(ipAddress: ipsc!.subnetId()) + var subnet = IPSubnetCalc.digitize(ipAddress: ipsc!.subnetId())! if (row > 0) { for index in (0...(row - 1)) { - subnet = subnet + ~IPSubnetCalc.digitize(maskbits: subnetsVLSM[index].0) + 1 + subnet = subnet + ~IPSubnetCalc.digitize(maskbits: subnetsVLSM[index].0)! + 1 } } let ipsc_tmp = IPSubnetCalc(ipAddress: IPSubnetCalc.dottedDecimal(ipAddress: subnet), maskbits: (subnetsVLSM[row].0)) @@ -1060,10 +1184,10 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, } } else if (tableColumn!.identifier.rawValue == "broadcastVLSMCol") { - var subnet = IPSubnetCalc.digitize(ipAddress: ipsc!.subnetId()) + var subnet = IPSubnetCalc.digitize(ipAddress: ipsc!.subnetId())! if (row > 0) { for index in (0...(row - 1)) { - subnet = subnet + ~IPSubnetCalc.digitize(maskbits: subnetsVLSM[index].0) + 1 + subnet = subnet + ~IPSubnetCalc.digitize(maskbits: subnetsVLSM[index].0)! + 1 } } let ipsc_tmp = IPSubnetCalc(ipAddress: IPSubnetCalc.dottedDecimal(ipAddress: subnet), maskbits: (subnetsVLSM[row].0)) @@ -1093,13 +1217,19 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, if (sender.intValue as Int >= Constants.NETWORK_BITS_MIN) { ipsc!.maskBits = sender.intValue as Int - self.doIPSubnetCalc() + do { + try self.doIPSubnetCalc() + } + catch {} //subnetsHostsView.reloadData() } else { let maskbits = sender.intValue as Int ipsc!.maskBits = Constants.NETWORK_BITS_MIN - self.doIPSubnetCalc() + do { + try self.doIPSubnetCalc() + } + catch {} if (tabViewClassLess.state == NSControl.StateValue.on) { ipsc!.maskBits = maskbits self.doSubnetHost() @@ -1155,8 +1285,8 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, if (subnetsVLSM.count != 0) { //print("VLSM subnets NOT empty") //print("Mask VLSM: \(IPSubnetCalc.digitize(ipAddress: globalMaskVLSM)) Maskbits: \(IPSubnetCalc.digitize(ipAddress: ~IPSubnetCalc.numerize(maskbits: maskbits)))") - if (globalMaskVLSM > ~IPSubnetCalc.digitize(maskbits: maskbits)) { - globalMaskVLSM = globalMaskVLSM - (~IPSubnetCalc.digitize(maskbits: maskbits) + 1) + if (globalMaskVLSM > ~IPSubnetCalc.digitize(maskbits: maskbits)!) { + globalMaskVLSM = globalMaskVLSM - (~IPSubnetCalc.digitize(maskbits: maskbits)! + 1) //print("Mask AFTER VLSM: \(IPSubnetCalc.digitize(ipAddress: globalMaskVLSM))") if let index = subnetsVLSM.firstIndex(where: { $0.0 > maskbits }) { subnetsVLSM.insert((maskbits, subnetNameVLSM.stringValue, "\(requiredHostsVLSM.stringValue)/\(hosts) (\(used)%)"), at: index) @@ -1172,10 +1302,10 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, } else { //print("VLSM subnets empty") - globalMaskVLSM = ~IPSubnetCalc.digitize(maskbits: ipsc!.maskBits) + 1 + globalMaskVLSM = ~IPSubnetCalc.digitize(maskbits: ipsc!.maskBits)! + 1 //print("Mask VLSM: \(IPSubnetCalc.digitize(ipAddress: globalMaskVLSM)) Maskbits: \(IPSubnetCalc.digitize(ipAddress: ~IPSubnetCalc.numerize(maskbits: maskbits)))") - if (globalMaskVLSM > ~IPSubnetCalc.digitize(maskbits: maskbits)) { - globalMaskVLSM = globalMaskVLSM - (~IPSubnetCalc.digitize(maskbits: maskbits) + 1) + if (globalMaskVLSM > ~IPSubnetCalc.digitize(maskbits: maskbits)!) { + globalMaskVLSM = globalMaskVLSM - (~IPSubnetCalc.digitize(maskbits: maskbits)! + 1) //print("Mask AFTER VLSM: \(IPSubnetCalc.digitize(ipAddress: globalMaskVLSM))") subnetsVLSM.append((maskbits, subnetNameVLSM.stringValue, "\(requiredHostsVLSM.stringValue)/\(hosts) (\(used)%)")) } @@ -1188,7 +1318,7 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, } } else { - myAlert(message: "Bad VLSM required Hosts number", info: "\(requiredHostsVLSM.integerValue) is not a number") + myAlert(message: "Invalid VLSM required Hosts number", info: "\(requiredHostsVLSM.integerValue) is not a number") } requiredHostsVLSM.stringValue = "" subnetNameVLSM.stringValue = "" @@ -1240,7 +1370,10 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, if (ipsc != nil) { if (ipsc!.maskBits < Constants.NETWORK_BITS_MIN) { ipsc!.maskBits = Constants.NETWORK_BITS_MIN - self.doIPSubnetCalc() + do { + try self.doIPSubnetCalc() + } + catch {} } } } @@ -1321,10 +1454,13 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, } if (sender.objectValueOfSelectedItem as? String) != nil { ipsc!.ipv6MaskBits = sender.intValue - self.doIPv6SubnetCalc() + do { + try self.doIPv6SubnetCalc() + } + catch {} } else { - myAlert(message: "Bad IPv6 Mask Bits", info: "Bad selection") + myAlert(message: "Invalid IPv6 Mask Bits", info: "Bad selection") return } } @@ -1342,7 +1478,10 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, ipsc = IPSubnetCalc(Constants.defaultIP) } ipsc!.ipv6MaskBits -= sender.indexOfSelectedItem() - self.doIPv6SubnetCalc() + do { + try self.doIPv6SubnetCalc() + } + catch {} } /** @@ -1360,10 +1499,13 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, } if (sender.indexOfSelectedItem != -1) { ipsc!.ipv6MaskBits = 128 - sender.indexOfSelectedItem() - self.doIPv6SubnetCalc() + do { + try self.doIPv6SubnetCalc() + } + catch {} } else { - myAlert(message: "Bad Max Hosts", info: "Bad selection") + myAlert(message: "Invalid Max Hosts", info: "Bad selection") return } } @@ -1381,26 +1523,42 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, */ @IBAction func calc(_ sender: AnyObject) { - if (addrField.stringValue.contains(":")) { - self.doIPv6SubnetCalc() - tabView.selectTabViewItem(at: 5) - } - else { - self.doIPSubnetCalc() - tabView.selectTabViewItem(at: 0) - } + self.ipAddrEdit(addrField) } - + /** Triggered when the user hit Enter key in the IP address field - - - Parameter sender: sender of the action + - Parameter sender: selected item of the IP address field */ @IBAction func ipAddrEdit(_ sender: AnyObject) { - self.calc(sender) + //print("ipAddrEdit action") + if ((sender as? NSTextField)?.stringValue) != nil { + if (sender.stringValue != "") { + do { + let addr = sender.stringValue! + try self.doCalc() + if (addrField.indexOfItem(withObjectValue: addr) == NSNotFound) { + if (addrField.numberOfItems >= Constants.maxAddrHistory) { + addrField.removeItem(at: 0) + if (history.count > 0) { + container.viewContext.delete(history[0]) + history.remove(at: 0) + saveHistory() + } + } + addrField.addItem(withObjectValue: addr) + let historyItem = AddrHistory(context: container.viewContext) + historyItem.address = addr + history.append(historyItem) + saveHistory() + } + } + catch {} + } + } } /** @@ -1433,7 +1591,7 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, var cvsStr = "#;Subnet ID;Range;Broadcast\n" for index in (0...(self.ipsc!.maxSubnets() - 1)) { let mask: UInt32 = UInt32(index) << (32 - self.ipsc!.maskBits) - let ipaddr = (IPSubnetCalc.digitize(ipAddress: self.ipsc!.subnetId())) | mask + let ipaddr = (IPSubnetCalc.digitize(ipAddress: self.ipsc!.subnetId())!) | mask let ipsc_tmp = IPSubnetCalc(ipAddress: IPSubnetCalc.dottedDecimal(ipAddress: ipaddr), maskbits: self.ipsc!.maskBits) if (ipsc_tmp != nil) { cvsStr.append("\(index + 1);\(ipsc_tmp!.subnetId());\(ipsc_tmp!.subnetRange());\(ipsc_tmp!.subnetBroadcast())\n") @@ -1479,7 +1637,7 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, let cvsFile = FileHandle(forWritingAtPath: panel.url!.path) if (cvsFile != nil) { var cvsStr = "#;Subnet ID;Mask bits;Range;Broadcast\n" - let subnetid: UInt32 = ((IPSubnetCalc.digitize(ipAddress: self.ipsc!.ipv4Address) & IPSubnetCalc.digitize(maskbits: self.ipsc!.maskBits)) >> (32 - self.ipsc!.maskBits)) << (32 - self.ipsc!.maskBits) + let subnetid: UInt32 = ((IPSubnetCalc.digitize(ipAddress: self.ipsc!.ipv4Address)! & IPSubnetCalc.digitize(maskbits: self.ipsc!.maskBits)!) >> (32 - self.ipsc!.maskBits)) << (32 - self.ipsc!.maskBits) for index in (0...(Int(truncating: NSDecimalNumber(decimal: pow(2, self.slideFLSM.integerValue))) - 1)) { let ipaddr = (subnetid >> (32 - (self.ipsc!.maskBits + self.slideFLSM.integerValue)) + UInt32(index)) << (32 - (self.ipsc!.maskBits + self.slideFLSM.integerValue)) let ipsc_tmp = IPSubnetCalc(ipAddress: IPSubnetCalc.dottedDecimal(ipAddress: ipaddr), maskbits: (self.ipsc!.maskBits + self.slideFLSM.integerValue)) @@ -1531,12 +1689,12 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, let cvsFile = FileHandle(forWritingAtPath: panel.url!.path) if (cvsFile != nil) { var cvsStr = "#;Subnet Name;Subnet ID;Mask bits;Hosts Range;Broadcast;Used\n" - let subnetid = IPSubnetCalc.digitize(ipAddress: self.ipsc!.subnetId()) + let subnetid = IPSubnetCalc.digitize(ipAddress: self.ipsc!.subnetId())! for index in (0...(self.subnetsVLSM.count - 1)) { var subnet = subnetid if (index > 0) { for index2 in (0...(index - 1)) { - subnet = subnet + ~IPSubnetCalc.digitize(maskbits: self.subnetsVLSM[index2].0) + 1 + subnet = subnet + ~IPSubnetCalc.digitize(maskbits: self.subnetsVLSM[index2].0)! + 1 } } let ipsc_tmp = IPSubnetCalc(ipAddress: IPSubnetCalc.dottedDecimal(ipAddress: subnet), maskbits: self.subnetsVLSM[index].0)! @@ -1601,6 +1759,26 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, } } + /** + Clear address IP field history + + Triggered when the user select Clear history in the window menu. + + - Parameter sender: non used + + */ + @IBAction func clearHistory(_ sender: AnyObject) + { + for _ in (0...addrField.numberOfItems-1) { + addrField.removeItem(at: 0) + } + for _ in (0...history.count-1) { + container.viewContext.delete(history[0]) + history.remove(at: 0) + saveHistory() + } + } + /** Auto invoked when the Main Windows has been resized */ @@ -1622,6 +1800,7 @@ class SubnetCalcAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, */ func applicationDidFinishLaunching(_ aNotification: Notification) { // Insert code here to initialize your application + loadHistory() initSubnetsTab() initCIDRTab() initIPv6Tab() diff --git a/SubnetCalcUITests/SubnetCalcUITest.swift b/SubnetCalcUITests/SubnetCalcUITest.swift index 2507db5..dbebbd3 100644 --- a/SubnetCalcUITests/SubnetCalcUITest.swift +++ b/SubnetCalcUITests/SubnetCalcUITest.swift @@ -30,7 +30,7 @@ class SubnetCalcUITest: XCTestCase { // Use XCTAssert and related functions to verify your tests produce the correct results. let subnetcalcWindow = XCUIApplication().windows["SubnetCalc"] - let ipaddrfieldTextField = subnetcalcWindow.textFields["ipaddrfield"] + let ipaddrfieldTextField = subnetcalcWindow.comboBoxes["ipaddrfield"] subnetcalcWindow.tabs["IPv4"].click() ipaddrfieldTextField.click()